Skip to content

Instantly share code, notes, and snippets.

@namandixit
Last active January 8, 2026 07:05
Show Gist options
  • Select an option

  • Save namandixit/22d61e7e416f7e4637730d3e5ff2a479 to your computer and use it in GitHub Desktop.

Select an option

Save namandixit/22d61e7e416f7e4637730d3e5ff2a479 to your computer and use it in GitHub Desktop.
Personal C Standard Library
/*
* Creator: Naman Dixit
* Notice: © Copyright 2024 Naman Dixit
* License: BSD Zero Clause License
* SPDX: 0BSD (https://spdx.org/licenses/0BSD.html)
*/
#if !defined(STD_H_INCLUDE_GUARD)
/* Compiler **************************************************************************/
#if defined(_MSC_VER)
# if defined(__clang__)
# define ENV_COMPILER_CLANG
# define ENV_COMPILER_CLANG_WITH_MSVC
# else
# define ENV_COMPILER_MSVC
# endif
#elif defined (__GNUC__)
# if defined(__clang__)
# define ENV_COMPILER_CLANG
# define ENV_COMPILER_CLANG_WITH_GCC
# else
# define ENV_COMPILER_GCC
# endif
#elif defined(__clang__)
# define ENV_COMPILER_CLANG
#else
# error Compiler not supported
#endif
/* Operating System ******************************************************************/
#if defined(_WIN32)
# define ENV_OS_WINDOWS
#elif defined(__linux__)
# define ENV_OS_LINUX
#else
# error Operating system not supported
#endif
/* CPU Architecture ******************************************************************/
#if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC)
# if defined(_M_IX86)
# define ENV_ARCH_X86
# elif defined(_M_X64)
# define ENV_ARCH_X64
# endif
#elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC)
# if defined(__i386__)
# define ENV_ARCH_X86
# elif defined(__x86_64__)
# define ENV_ARCH_X64
# endif
#endif
#if !defined(ENV_ARCH_X64) // && !defined(ENV_ARCH_X86)
# error Architecture not supported
#endif
/* Word Bit-width ********************************************************************/
#if defined(ENV_ARCH_X86)
# define ENV_BITWIDTH_32
# error "Bitwidth not supported"
#elif defined(ENV_ARCH_X64)
# define ENV_BITWIDTH_64
#else
# error "Bitwidth not supported"
#endif
/* Programming Language **************************************************************/
#if defined(ENV_COMPILER_MSVC)
# if defined(__cplusplus)
# if __cplusplus == 199711L
# define ENV_LANG_CXX 1998
# elif __cplusplus == 201402L
# define ENV_LANG_CXX 2014
# elif __cplusplus == 201703L
# define ENV_LANG_CXX 2017
# elif __cplusplus == 202002L
# define ENV_LANG_CXX 2020
# else
# define ENV_LANG_CXX 2017 // A future C++, assume the last best supported
# endif
# elif defined(__STDC_VERSION__)
# if (__STDC_VERSION__ == 201112L) || (__STDC_VERSION__ == 201710L)
# define ENV_LANG_C 2011
# elif (__STDC_VERSION__ == 202311L) || (__STDC_VERSION__ == 202312L) // 202312L is with /std:clatest before official release of C23 support
# define ENV_LANG_C 2023
# else
# define ENV_LANG_C 2011 // Earliest C version for which MSVC supports __STDC_VERSION__
# endif
# elif defined(__STDC__) // All Microsoft extensions are off [/Za (Disable Language Extensions), similar to pedantic]
# define ENV_LANG_C 1989
# else // /Za (Disable Language Extensions) is not provided, but MS never properly supported C99.
# define ENV_LANG_C 1989
# endif
#elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC)
# if defined(__cplusplus)
# if defined(__OBJC__)
# define ENV_LANG_OBJCPP 1
# endif
# if __cplusplus == 199711L
# define ENV_LANG_CXX 1998
# elif __cplusplus == 201103L
# define ENV_LANG_CXX 2011
# elif __cplusplus == 201402L
# define ENV_LANG_CXX 2014
# elif __cplusplus == 201703L
# define ENV_LANG_CXX 2017
# elif __cplusplus == 202002L
# define ENV_LANG_CXX 2020
# elif __cplusplus == 202302L
# define ENV_LANG_CXX 2023
# else
# define ENV_LANG_CXX 2020 // A future C++, assume the last best supported
# endif
# elif defined(__STDC_VERSION__) // Using C Language >= 1994 (1989)
# if defined(__OBJC__)
# define ENV_LANG_OBJC 1
# endif
# if (__STDC_VERSION__ == 199409L)
# define ENV_LANG_C 1989
# elif (__STDC_VERSION__ == 199901L)
# define ENV_LANG_C 1999
# elif (__STDC_VERSION__ == 201112L) || (__STDC_VERSION__ == 201710L)
# define ENV_LANG_C 2011
# elif (__STDC_VERSION__ == 202311L)
# define ENV_LANG_C 2023
# else
# define ENV_LANG_C 1999 // At least C99 (__STDC_VERSION__ is defined, C94 is too old to fallback on)
# endif
# elif defined(__STDC__) && !defined(__STDC_VERSION__)
# if defined(__OBJC__)
# define ENV_LANG_OBJC 1
# endif
# define ENV_LANG_C 1989 // Since C89 doesn't require definition of __STDC_VERSION__
# endif
#endif
#if !defined(ENV_LANG_C) && !defined(ENV_LANG_CXX)
# error Language not supported
#endif
/* Endianness ************************************************************************/
#if defined(ENV_OS_WINDOWS)
# if defined(ENV_ARCH_X86) || defined(ENV_ARCH_X64)
# define ENV_ENDIAN_LITTLE
# else
# error Could not determine endianness on Windows
# endif
#elif defined(ENV_OS_LINUX)
# include <endian.h>
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ENV_ENDIAN_LITTLE
# elif __BYTE_ORDER == __BIG_ENDIAN
# define ENV_ENDIAN_BIG
# else
# error Could not determine endianness on Linux
# endif
#else
# error Can not determine endianness, unknown environment
#endif
/* Pragmas ***************************************************************************/
// Unique symbol generator
#define macro_gensym2_(prefix, suffix) macro_gensym_cat_(prefix, suffix)
#define macro_gensym_cat_(prefix, suffix) prefix ## suffix
//#define macro_gensym_uniq(prefix) macro_gensym2_(prefix, __COUNTER__) // Commented since I am going to use __COUNTER__ for profile events
#define macro_gensym_line(prefix) macro_gensym2_(prefix, __LINE__)
#define macro_gensym_func(prefix) macro_gensym2_(prefix, __func__)
#define macro_gensym_file(prefix) macro_gensym2_(prefix, __FILE__)
//#define eat_semicolon() static_assert(1, "") /* Doesn't work before C23 */
#define eat_semicolon() struct macro_gensym_line(dummy_struct_designed_for_eating_semicolon)
/* Less noisy pragmas */
#if defined(ENV_COMPILER_CLANG)
# define pragma_msvc(P) eat_semicolon()
# define pragma_clang(P) _Pragma(P) eat_semicolon()
# define pragma_gcc(P) eat_semicolon()
#elif defined(ENV_COMPILER_GCC)
# define pragma_msvc(P) eat_semicolon()
# define pragma_clang(P) eat_semicolon()
# define pragma_gcc(P) _Pragma(P) eat_semicolon()
#elif defined(ENV_COMPILER_MSVC)
# define pragma_msvc(P) _Pragma(P) eat_semicolon()
# define pragma_clang(P) eat_semicolon()
# define pragma_gcc(P) eat_semicolon()
#endif
/* Compiler-specific tokens */
#if defined(ENV_COMPILER_CLANG)
# define with_msvc(P)
# define with_clang(P) P
# define with_gcc(P)
#elif defined(ENV_COMPILER_GCC)
# define with_msvc(P)
# define with_clang(P)
# define with_gcc(P) P
#elif defined(ENV_COMPILER_MSVC)
# define with_msvc(P) P
# define with_clang(P)
# define with_gcc(P)
#endif
#define with_clang_gcc(P) with_clang(P) with_gcc(P)
#define with_clang_msvc(P) with_clang(P) with_msvc(P)
/* Warnings **************************************************************************/
pragma_clang("clang diagnostic push");
#if defined(ENV_LANG_C)
pragma_clang("clang diagnostic ignored \"-Wdeclaration-after-statement\"");
pragma_clang("clang diagnostic ignored \"-Wpre-c23-compat\"");
pragma_clang("clang diagnostic ignored \"-Wcovered-switch-default\"");
pragma_clang("clang diagnostic ignored \"-Wunsafe-buffer-usage\"");
#elif defined(ENV_LANG_CXX)
pragma_clang("clang diagnostic ignored \"-Wc++98-compat-pedantic\"");
pragma_clang("clang diagnostic ignored \"-Wcovered-switch-default\"");
pragma_clang("clang diagnostic ignored \"-Wc99-extensions\"");
pragma_clang("clang diagnostic ignored \"-Wgnu-anonymous-struct\"");
pragma_clang("clang diagnostic ignored \"-Wnested-anon-types\"");
pragma_clang("clang diagnostic ignored \"-Wmissing-field-initializers\"");
pragma_clang("clang diagnostic ignored \"-Wmicrosoft-goto\"");
pragma_clang("clang diagnostic ignored \"-Wunsafe-buffer-usage\"");
#endif
pragma_msvc("warning ( push )");
pragma_msvc("warning ( disable: 4200 )"); // nonstandard extension used: zero-sized array in struct/union
#if defined(ENV_LANG_C)
pragma_msvc("warning ( disable: 6387 )"); // Analyze: INVALID_PARAM_VALUE_1
#elif defined(ENV_LANG_CXX)
pragma_msvc("warning ( disable: 4127 )"); // conditional expression is constant
pragma_msvc("warning ( disable: 4201 )"); // nonstandard extension used: nameless struct/union
pragma_msvc("warning ( disable: 6386 )"); // Analyze: WRITE_OVERRUN
#endif
/* Constants *************************************************************************/
#define KiB(x) ( (x) * 1024ULL)
#define MiB(x) (KiB(x) * 1024ULL)
#define GiB(x) (MiB(x) * 1024ULL)
#define TiB(x) (GiB(x) * 1024ULL)
#define HUNDRED 100L
#define THOUSAND 1000L
#define MILLION 1000000L
#define BILLION 1000000000L
/* Attributes ************************************************************************/
#if defined(ENV_LANG_C) && (ENV_LANG_C < 2023)
# define nullptr NULL
#endif
#if (defined(ENV_LANG_C) && ENV_LANG_C == 2011)
# define static_assert(...) _Static_assert(__VA_ARGS__)
#elif (defined(ENV_LANG_C) && ENV_LANG_C < 2011)
# if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define static_assert__HELPER(expr, msg) \
(!!sizeof (struct { unsigned int macro_gensym_line(STATIC_ASSERTION__): (expr) ? 1 : -1; }))
# define static_assert(expr, msg) \
extern int (*assert_function__(void)) [static_assert__HELPER(expr, msg)]
# elif defined(ENV_COMPILER_MSVC)
# define static_assert(expr, msg) \
extern char macro_gensym_line(STATIC_ASSERTION__1__)[1]; extern char macro_gensym_line(STATIC_ASSERTION__2__)[(expr)?1:2]
# endif
#endif
#define global_variable static
#define global_immutable static const
#define persistent_variable static
#define persistent_immutable static const
#define internal_function static
#define header_function static inline
#if defined(ENV_LANG_C) && defined(ENV_COMPILER_MSVC)
# define nullptr NULL
#endif
#if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC)
# if defined(BUILD_DLL)
# define exported_function __declspec(dllexport)
# else
# define exported_function __declspec(dllimport)
# endif
#elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define exported_function __attribute__((visibility("default")))
#endif
#if defined(ENV_COMPILER_MSVC) || defined(ENV_COMPILER_CLANG_WITH_MSVC)
# define exported_data __declspec(dllexport)
#elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define exported_data __attribute__((visibility("default")))
#endif
#if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
#define packed_data(...) __VA_ARGS__ __attribute__((__packed__))
#elif defined(ENV_COMPILER_MSVC)
#define packed_data(...) __pragma( pack(push, 1) ) __VA_ARGS__ __pragma( pack(pop))
#endif
#if defined(__has_c_attribute)
# if __has_c_attribute(fallthrough)
# define switch_fallthrough [[fallthrough]]
# endif
#elif defined(__cplusplus) && defined(__has_cpp_attribute)
# if __has_cpp_attribute(fallthrough)
# define switch_fallthrough [[fallthrough]]
# endif
#endif
#if !defined(switch_fallthrough)
# if defined(ENV_COMPILER_GCC) && __GNUC__ >= 7
# define switch_fallthrough __attribute__ ((fallthrough))
# elif defined(ENV_COMPILER_CLANG) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 9))
# define switch_fallthrough __attribute__ ((fallthrough))
# else
# define switch_fallthrough
# endif
#endif
/* Macros ****************************************************************************/
#define elemin(array) (sizeof(array)/sizeof((array)[0]))
// Type-casts
#if defined(ENV_LANG_C)
# define cast_bit(T, m) (*((T*)(&(m)))) /* Reading float as int, float has to be stored in a variable */
# define cast_ptr(T, v) ((T)(v)) /* Casting float* to int* */
# define cast_val(T, v) ((T)(v)) /* Convering float to int */
# define cast_const(T, v) ((T)(v)) /* Removing const */
#elif defined(ENV_LANG_CXX)
template<class To, class From>
To cast_bit_func(const From& src) noexcept {
static_assert(sizeof(To) == sizeof(From));
union { From f; To t; } u;
u.f = src;
return u.t; // implementation-defined/UB by strict standard, but works on many compilers
}
# define cast_bit(T, m) (cast_bit_func<T>(m)) /* Reading float as int, float has to be stored in a variable */
# define cast_ptr(T, v) (reinterpret_cast<T>(v)) /* Casting float* to int* */
# define cast_val(T, v) (static_cast<T>(v)) /* Convering float to int */
# define cast_const(T, v) (const_cast<T>(v)) /* Removing const */
#endif
#if defined(ENV_LANG_CXX)
template<typename T> struct typeof_remove_reference { using type = T; };
template<typename T> struct typeof_remove_reference<T&> { using type = T; };
# if defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011)
template<typename T> struct typeof_remove_reference<T&&> { using type = T; };
# endif
template<typename T>
using typeof_remove_reference_t = typename typeof_remove_reference<T>::type;
# define typeof(...) typeof_remove_reference_t<decltype(__VA_ARGS__)>
#endif
// Compound Literal
#if defined(ENV_LANG_C)
# define compound_init(T, ...) (T) __VA_ARGS__
#elif defined(ENV_LANG_CXX)
# define compound_init(T, ...) T __VA_ARGS__
#endif
#define containerof(ptr, type, member) (cast_val(type*, cast_val(void*, (cast_ptr(Byte*, ptr)) - offsetof(type, member))))
#define unused_variable(var) (void)var
// Likely/unlikely for better branch prediction
#if defined(ENV_COMPILER_MSVC)
# define likely(x) (x)
# define unlikely(x) (x)
#elif defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC)
# define likely(x) __builtin_expect(!!(x), 1)
# define unlikely(x) __builtin_expect(!!(x), 0)
#endif
// Defer-like macros
#define macro_entail(...) \
goto macro_gensym_line(jump_to_else); \
\
for (;;) \
if (true) { \
__VA_ARGS__; \
break; \
} else macro_gensym_line(jump_to_else):
#define macro_envelop(cloak_arg_pre_action, cloak_arg_post_action) \
cloak_arg_pre_action; \
goto macro_gensym_line(jump_to_else); \
\
for (;;) \
if (true) { \
cloak_arg_post_action; \
break; \
} else macro_gensym_line(jump_to_else):
/* Error handling macros
*
* Sample Code:
*
* T *ta = nullptr; *tb = nullptr;
* err_try(doing_stuff) {
* ta = tCreate();
* if (!ta) err_throw(doing_stuff);
* tb = tCreate();
* if (!tb) err_throw(doing_stuff);
* stuffDo(ta, tb);
* } err_catch(doing_stuff) {
* if (ta) tDelete(ta);
* if (tb) tDelete(tb);
* }
*/
#define err_try(fail) Bool fail = false; do
#define err_throw(fail) {fail=true;break;}(void)0
#define err_catch(fail) while (0); if (fail)
#if defined(ENV_LANG_C) && ENV_LANG_C >= 1999
# define static_array_size(size) with_clang_gcc(static size)
#else
# define static_array_size(size) (size)
#endif
/* Disable all warnings in headers */
#define disable_warnings() \
pragma_clang("clang diagnostic push"); \
pragma_clang("clang diagnostic ignored \"-Weverything\""); \
pragma_msvc("warning ( push , 0 )")
#define enable_warnings() \
pragma_clang("clang diagnostic pop"); \
pragma_msvc("warning ( pop )")
#define allow_padding_in_type(...) \
pragma_msvc("warning ( push )"); \
pragma_msvc("warning ( disable: 4820 )"); \
pragma_clang("clang diagnostic push"); \
pragma_clang("clang diagnostic ignored \"-Wpadded\""); \
__VA_ARGS__ \
pragma_clang("clang diagnostic pop"); \
pragma_msvc("warning ( pop )");
/* Stringify contents of a macro */
#define stringify(a) stringify_helper_(a)
#define stringify_helper_(a) #a
/* Header Files **********************************************************************/
// Function-less C89 headers
#include <float.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
// Function-less C99 headers
#if (defined(ENV_LANG_C) && (ENV_LANG_C >= 1999)) || (defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011))
# include <inttypes.h>
# include <stdbool.h>
# include <stdint.h>
#endif
// Function-less C11 headers
// stdalign
#if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# if (defined(ENV_LANG_C) && (ENV_LANG_C >= 2011)) || (defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011))
# include <stdalign.h>
# endif
#elif defined(ENV_COMPILER_MSVC)
# if defined(ENV_LANG_C) && (ENV_LANG_C >= 2011)
// Copied from Clang's headers
typedef double max_align_t;
# define alignof(...) __alignof(__VA_ARGS__)
# elif defined(ENV_LANG_CXX) && (ENV_LANG_CXX >= 2011)
# include <cstddef>
typedef std::max_align_t max_align_t;
# endif
#endif
#if defined(ENV_ARCH_X64) || defined(ENV_ARCH_X86)
# include <pmmintrin.h> // SSE3, 100% support as per Steam Hardware Survey
#endif
#if defined(ENV_COMPILER_MSVC)
// WARN(naman): intrin0.inl.h is an internal header. This might break.
// NOTE(naman): We include intrin0.inl.h instead of the documented intrin.h because intrin.h includes
// EVERY possible intrinsic, and we don't want that (the whole point of only include headers
// SSE3 above).
# include <intrin0.inl.h>
#else
# if defined(ENV_ARCH_X64) || defined(ENV_ARCH_X86)
# include <x86intrin.h> // Add with carry
# endif
#endif
#if defined(ENV_COMPILER_CLANG) || defined(ENV_COMPILER_GCC)
# if defined(BUILD_SANITIZERS) && (__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__))
disable_warnings();
# define _DISABLE_STL_ANNOTATION
# include <sanitizer/asan_interface.h>
# undef _DISABLE_STL_ANNOTATION
enable_warnings();
# define ENV_SANITIZER_ADDRESS
# endif
#endif
#if defined(ENV_COMPILER_MSVC)
# if defined(BUILD_SANITIZERS) && defined(__SANITIZE_ADDRESS__)
disable_warnings();
# define _DISABLE_STL_ANNOTATION
# include <sanitizer/asan_interface.h>
# undef _DISABLE_STL_ANNOTATION
enable_warnings();
# define ENV_SANITIZER_ADDRESS
# endif
#endif
#if defined(ENV_SANITIZER_ADDRESS)
# define asan_block(_ptr, _sz) ASAN_POISON_MEMORY_REGION((_ptr), (_sz))
# define asan_allow(_ptr, _sz) ASAN_UNPOISON_MEMORY_REGION((_ptr), (_sz))
#else
// NOTE(naman): Sanitizers are not required for tools since them crashing could take down the
// debugging infrastructure itself. But of course it can be enabled for any of them if needed.
# if defined(BUILD_INTERNAL) && !defined(BUILD_TOOL) && !defined(ENV_COMPILER_MSVC) /* FIXME(naman): MSVC always runs next error */
# error Sanitizers should always be enabled
# endif
# define asan_block(_ptr, _sz) do { (void)(_ptr); (void)(_sz); } while (0)
# define asan_allow(_ptr, _sz) do { (void)(_ptr); (void)(_sz); } while (0)
#endif
/* Primitive Types *******************************************************************/
typedef int Sint;
typedef unsigned int Uint;
#include <stdint.h>
typedef int8_t Sint8;
typedef int16_t Sint16;
typedef int32_t Sint32;
typedef int64_t Sint64;
typedef uint8_t Uint8;
typedef uint16_t Uint16;
typedef uint32_t Uint32;
typedef uint64_t Uint64;
typedef float Float32;
typedef double Float64;
typedef size_t Size;
#if defined(ENV_OS_LINUX)
typedef ssize_t SSize;
#elif defined(ENV_OS_WINDOWS)
# include <basetsd.h>
typedef SSIZE_T SSize;
#endif
typedef uintptr_t Uptr;
typedef intptr_t Sptr;
typedef ptrdiff_t Dptr;
typedef unsigned char Byte;
typedef char Char;
#if defined(ENV_LANG_C) && (ENV_LANG_C >= 1999)
typedef _Bool Bool;
#elif defined(ENV_LANG_C) && (ENV_LANG_C == 2011)
typedef _Bool Bool;
# else
typedef bool Bool;
#endif
// NOTE(naman): We define our own true & false because by default, they are defined as 1 & 0 and that doesn't play well with _Generic.
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wkeyword-macro\"");
#if defined(ENV_LANG_C)
# undef true
# define true ((Bool)+1)
# undef false
# define false ((Bool)+0)
#endif
pragma_clang("clang diagnostic pop");
typedef Float32 Float32x2[2];
typedef Float32 Float32x3[3];
typedef Float32 Float32x4[4];
/* Helper Stuff **********************************************************************/
#if defined(ENV_LANG_C)
# define maximum(x, y) ((x) > (y) ? (x) : (y))
# define minimum(x, y) ((x) < (y) ? (x) : (y))
#elif defined(ENV_LANG_CXX)
template<typename T>
const T& maximum (const T& a, const T& b)
{
return (a < b) ? b : a;
}
template<typename T>
const T& minimum (const T& a, const T& b)
{
return (a > b) ? b : a;
}
#endif
#define clamp_between(_val, _min, _max) maximum(minimum(_val, _max), _min)
#define memzero(_x) memset((_x), 0, sizeof(*(_x)))
#if defined(ENV_LANG_C)
# define valzero(_t) (_t){0}
#elif defined(ENV_LANG_CXX)
# define valzero(_t) _t{}
#endif
#if defined(BUILD_INTERNAL)
# if defined(ENV_OS_WINDOWS)
# define breakpoint() __debugbreak()
# elif defined(ENV_OS_LINUX)
# if defined(ENV_ARCH_X86) || defined(ENV_ARCH_X64)
# if defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define breakpoint() __asm__ volatile("int $0x03")
# endif // !GCC && !Clang
# endif // !x86 && !x64
# endif // !window && !linux
#else
# define breakpoint() do {} while (0)
#endif
#if !defined(debugBreak)
# define debugBreak() breakpoint()
#endif
global_immutable Bool debug_assert_non_constant = true;
#define debugAssert(...) do { if (debug_assert_non_constant && !(__VA_ARGS__)) { debugBreak(); } } while (0)
#if defined(BUILD_INTERNAL)
global_immutable Bool debug_unreachable_code_executed = false;
# define debugUnreachable() debugAssert(debug_unreachable_code_executed == true /* To prevent warnings */)
#else
# if defined(ENV_COMPILER_MSVC)
# define debugUnreachable() __assume(false)
# elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define debugUnreachable() __builtin_unreachable()
# endif
#endif
#define COMPARE_FUNC(name) Sint name (const void *a, const void *b)
#if defined(ENV_OS_WINDOWS)
# if defined(ENV_LANG_CXX)
extern "C" {
# endif
void * __cdecl memset ( void *dest, Sint ch, Size count); _Pragma("intrinsic(memset)")
void * __cdecl memcpy ( void *dest, const void *src, Size count); _Pragma("intrinsic(memcpy)")
int __cdecl memcmp (const void *lhs, const void *rhs, Size count); _Pragma("intrinsic(memcmp)")
Size __cdecl strlen (const char *str); _Pragma("intrinsic(strlen)")
// Many of these functions are copied from https://github.com/DevSolar/pdclib (https://pdclib.rootdirectory.de/)
header_function
Sint strncmp (const Char *s1, const Char *s2, Size n)
{
while (n && *s1 && (*s1 == *s2)) {
++s1;
++s2;
--n;
}
if (n == 0) {
return 0;
} else {
return (*cast_ptr(const Byte*, s1) - *cast_ptr(const Byte*, s2));
}
}
header_function
int strcmp (const Char *s1, const Char *s2)
{
while (*s1 && (*s1 == *s2)) {
++s1;
++s2;
}
return (*cast_ptr(const Byte*, s1) - *cast_ptr(const Byte*,s2));
}
header_function
Bool strleq (const Char *a, Size n, const Char *b)
{
return strncmp(a, b, n) == 0;
}
header_function
Bool streq (const Char *a, const Char *b)
{
return strcmp(a, b) == 0;
}
# if defined(ENV_LANG_CXX)
}
# endif
#else
# include <string.h>
#endif
/* *********************************************************************************
* Base 32 Encoding
* ****************
* Inspired by Crockford's Base 32 encoding (https://www.crockford.com/base32.html)
*
* Changes:
*
* 1. U/u is decoded to the same integer as V/v.
* When encoding, v is the right value to encode to.
*
* 2. For the extra check symbols, use - . _ ~ u, since
* these are the symbols considered unreserved in URLs
* See section 2.3 of https://www.ietf.org/rfc/rfc3986.txt
*
* (Explaination: If checksum is required, the integer
* value of the string is divided by 37 (the next prime
* after 32) and the encoded remainder is appended after
* the string. Since the value will be <= 37, five extra
* symbols are required).
*
* 3. The encoded characters are lower-case, not upper case
* since upper case looks like screaming. Also, this way,
* alphabets and numbers are a bit more distinguishable.
* And it reads like r0x0ring h4x0r 13375p34k, n00bs4uc3!
* (Obligatory: https://megatokyo.com/strip/9)
*/
header_function
Uint8 b32DecodeChar (Char code) // Character to integer
{
switch (code) {
case 'O': case 'o':
case '0': return 0;
case 'I': case 'i':
case 'L': case 'l':
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'A': case 'a': return 10;
case 'B': case 'b': return 11;
case 'C': case 'c': return 12;
case 'D': case 'd': return 13;
case 'E': case 'e': return 14;
case 'F': case 'f': return 15;
case 'G': case 'g': return 16;
case 'H': case 'h': return 17;
case 'J': case 'j': return 18;
case 'K': case 'k': return 19;
case 'M': case 'm': return 20;
case 'N': case 'n': return 21;
case 'P': case 'p': return 22;
case 'Q': case 'q': return 23;
case 'R': case 'r': return 24;
case 'S': case 's': return 25;
case 'T': case 't': return 26;
case 'U': case 'u':
case 'V': case 'v': return 27;
case 'W': case 'w': return 28;
case 'X': case 'x': return 29;
case 'Y': case 'y': return 30;
case 'Z': case 'z': return 31;
default: debugUnreachable(); return 0;
}
}
header_function
Uint8 b32DecodeChecksumChar (Char check) // Character to integer
{
switch (check) {
case '-': return 32;
case '.': return 33;
case '_': return 34;
case '~': return 35;
case 'u': return 36;
default: return b32DecodeChar(check);
}
}
header_function
Char b32EncodeChar (Uint8 val) // Integer to character
{
switch (val) {
case 0: return '0'; case 1: return '1'; case 2: return '2'; case 3: return '3';
case 4: return '4'; case 5: return '5'; case 6: return '6'; case 7: return '7';
case 8: return '8'; case 9: return '9'; case 10: return 'a'; case 11: return 'b';
case 12: return 'c'; case 13: return 'd'; case 14: return 'e'; case 15: return 'f';
case 16: return 'g'; case 17: return 'h'; case 18: return 'j'; case 19: return 'k';
case 20: return 'm'; case 21: return 'n'; case 22: return 'p'; case 23: return 'q';
case 24: return 'r'; case 25: return 's'; case 26: return 't'; case 27: return 'v';
case 28: return 'w'; case 29: return 'x'; case 30: return 'y'; case 31: return 'z';
default: debugUnreachable(); return 0;
}
}
header_function
Char b32EncodeChecksumChar (Uint8 val) // Integer to character
{
switch (val) {
case 32: return '-';
case 33: return '.';
case 34: return '_';
case 35: return '~';
case 36: return 'u';
default: return b32EncodeChar(val);
}
}
header_function
Bool b32VerifyInputString (const Char *str, Size len, Char checksum)
{
Uint8 mod = 0;
for (Size i = 0; i < len; i++) {
Bool valid = false;
valid |= (str[i] >= 0x30) && (str[i] <= 0x39); // 0-9
valid |= (str[i] >= 0x61) && (str[i] <= 0x68); // a-h
valid |= (str[i] == 0x6a); // j
valid |= (str[i] == 0x6b); // k
valid |= (str[i] == 0x6d); // m
valid |= (str[i] == 0x6e); // n
valid |= (str[i] >= 0x70) && (str[i] <= 0x74); // p-t
valid |= (str[i] >= 0x76) && (str[i] <= 0x7a); // v-z
valid |= (str[i] >= 0x41) && (str[i] <= 0x48); // A-H
valid |= (str[i] == 0x4a); // j
valid |= (str[i] == 0x4b); // k
valid |= (str[i] == 0x4d); // m
valid |= (str[i] == 0x4e); // n
valid |= (str[i] >= 0x50) && (str[i] <= 0x54); // p-t
valid |= (str[i] >= 0x56) && (str[i] <= 0x5a); // v-z
if (!valid) return false;
mod = (mod + b32DecodeChar(str[i])) % 37u;
}
if (checksum) {
Uint8 check_car = b32DecodeChecksumChar(checksum);
Uint8 check_val = mod;
if (check_val != check_car) return false;
}
return true;
}
header_function
Char b32GetChecksumChar (const Char *str, Size len)
{
Uint8 mod = 0;
for (Size i = 0; i < len; i++) {
mod = (mod + b32DecodeChar(str[i])) % 37u;
}
Char check = b32EncodeChecksumChar(mod);
return check;
}
/************************************************************************************
* Unicode Processing
*/
// TODO(naman): Go through these security guidelines for safe UTF-8 processing:
// https://security.stackexchange.com/questions/257017/what-are-best-practices-for-handling-user-unicode-in-a-web-application
// TODO(naman): I would like to have my own Unicode variant at some point which will handle all the various weirdnesses of text.
// 1. It should be a multi-byte variable width encoding like UTF-8
// 2. It should support malformed UTF-16 in the same way that WTF-8 does.
// 3. It should use the Perl6's Normalized Form Grapheme (NFG) to make it easy to iterate on strings.
// 4. Conversion to and from various other formats should be as easy as possible.
/*
* First codepoint Last codepoint Byte 1 Byte 2 Byte 3 Byte 4
* U+0000 U+007F 0yyyzzzz
* U+0080 U+07FF 110xxxyy 10yyzzzz
* U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
* U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
*/
// UTF32 to UTF8
header_function
Size unicodeEncode (Uint32 code, Byte buffer[static_array_size(4)])
{
if (buffer == nullptr) return 0;
if (code <= 0x7F) {
buffer[0] = cast_val(Byte, code); // 0yyyzzzz
return 1;
} else if (code <= 0x7FF) {
buffer[0] = cast_val(Byte, cast_val(Byte, 0xC0) | cast_val(Byte, (code >> 6))); // 110xxxyy
buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz
return 2;
} else if (code <= 0xFFFF) {
buffer[0] = cast_val(Byte, cast_val(Byte, 0xE0) | cast_val(Byte, (code >> 12))); // 1110wwww
buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 6) & 0x3F))); // 10xxxxyy
buffer[2] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz
return 3;
} else if (code <= 0x10FFFF) {
buffer[0] = cast_val(Byte, cast_val(Byte, 0xF0) | cast_val(Byte, (code >> 18))); // 11110uvv
buffer[1] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 12) & 0x3F))); // 10vvwwww
buffer[2] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, ((code >> 6) & 0x3F))); // 10xxxxyy
buffer[3] = cast_val(Byte, cast_val(Byte, 0x80) | cast_val(Byte, (code & 0x3F))); // 10yyzzzz
return 4;
} else {
return 0; // Invalid codepoint
}
}
// UTF8 to UTF32
header_function
Bool unicodeDecode (const Byte *buffer, Size len, Size *offset, Uint32 *codepoint)
{
if (buffer == nullptr) return 0;
Size temp1 = 0; if (offset == nullptr) offset = &temp1;
Uint32 temp2 = 0; if (codepoint == nullptr) codepoint = &temp2;
Size o = *offset;
len -= o;
*codepoint = 0;
if (len == 0) return false;
Uint32 b0 = buffer[o+0];
*offset += 1;
if (b0 == 0) return true; // nullptr-byte
if ((b0 & 0x80) == 0) { // 0x80 = 10000000
*codepoint = b0;
return true;
}
if (len == 1) return false;
Uint32 b1 = buffer[o+1];
*offset += 1;
if (b1 == 0) return false;
Uint32 BA1 = b1 & 0xC0; // 0xC0 = 11000000
Uint32 BB1 = b1 & 0x3F; // 0x3F = 00111111
if ((b0 & 0xE0) == 0xC0) { // 0xE0 = 11100000 , 0xC0 = 11000000
if (BA1 != 0x80) return false; // 0x80 = 10000000
Uint32 B = b0 & 0x1F; // 0x1F = 00011111
*codepoint = (B << 6) | BB1;
return true;
}
if (len == 2) return false;
Uint32 b2 = buffer[o+2];
*offset += 1;
if (b2 == 0) return false;
Uint32 BA2 = b2 & 0xC0; // 0xC0 = 11000000
Uint32 BB2 = b2 & 0x3F; // 0x3F = 00111111
if ((b0 & 0xF0) == 0xE0) { // 0xF0 = 11110000 , 0xE0 = 11100000
if (BA1 != 0x80) return false; // 0x80 = 10000000
if (BA2 != 0x80) return false; // 0x80 = 10000000
Uint32 B = b0 & 0x0F; // 0x0F = 00001111
*codepoint = (B << 12) | (BB1 << 6) | BB2;
return true;
}
if (len == 3) return false;
Uint32 b3 = buffer[o+3];
*offset += 1;
if (b3 == 0) return false;
Uint32 BA3 = b3 & 0xC0; // 0xC0 = 11000000
Uint32 BB3 = b3 & 0x3F; // 0x3F = 00111111
if ((b0 & 0xF8) == 0xF0) { // 0xF8 = 11111000 , 0xF0 = 11110000
if (BA1 != 0x80) return false; // 0x80 = 10000000
if (BA2 != 0x80) return false; // 0x80 = 10000000
if (BA3 != 0x80) return false; // 0x80 = 10000000
Uint32 B = b0 & 0x07; // 0x07 = 00000111
Uint32 result = (B << 18) | (BB1 << 12) | (BB2 << 6) | BB3;
if (result > 0x10FFFF) return false;
*codepoint = result;
return true;
}
return false;
}
/**********************************************************************
* Bitwise Delight
*/
// NOTE(naman): Bit vectors are supposed to be zero-indexed.
// NOTE(naman): Base type of bit vectors shouldn't have size > 8 bytes (to prevent shift overflow).
#define bitToBytes(b) (((b)+(CHAR_BIT-1))/(CHAR_BIT))
#define bit_ValInBuf(array, index) ((index)/(CHAR_BIT * sizeof(*(array))))
#define bit_BitInVal(array, index) ((index)%(CHAR_BIT * sizeof(*(array))))
#define bitSet(array, index) \
((array)[bit_ValInBuf(array, index)] |= (1LLU << bit_BitInVal(array, index)))
#define bitReset(array, index) \
((array)[bit_ValInBuf(array, index)] &= ~(1LLU << bit_BitInVal(array, index)))
#define bitToggle(array, index) \
((array)[bit_ValInBuf(array, index)] ^= ~(1LLU << bit_BitInVal(array, index)))
#define bitTest(array, index) \
((array)[bit_ValInBuf(array, index)] & (1LLU << bit_BitInVal(array, index)))
header_function
Uint8 bitMSBU32 (Uint32 x)
{
debugAssert(x);
Uint8 result;
#if defined(ENV_COMPILER_MSVC)
// _BitScanReverse: Search MSB->LSB and load the position of first bit in location (zero-indexed)
unsigned long msb_location = 0;
debugAssert(_BitScanReverse(&msb_location, x));
result = cast_val(Uint8, msb_location);
#else
// __builtin_clz: Returns the number of leading 0-bits starting from MSB
Sint maybe_set_bits = 32 - __builtin_clz(x);
Sint msb_location = maybe_set_bits - 1; // Zero indexing
result = cast_val(Uint8, msb_location);
#endif
return result;
}
header_function
Uint8 bitMSBU64 (Uint64 x)
{
debugAssert(x);
Uint8 result;
#if defined(ENV_COMPILER_MSVC)
// _BitScanReverse64: Search MSB->LSB and load the position of first bit in location (zero-indexed)
unsigned long msb_location = 0;
debugAssert(_BitScanReverse64(&msb_location, x));
result = cast_val(Uint8, msb_location);
#else
// __builtin_clzll: Returns the number of leading 0-bits starting from MSB
Sint maybe_set_bits = 64 - __builtin_clzll(x);
Sint msb_location = maybe_set_bits - 1; // Zero indexing
result = cast_val(Uint8, msb_location);
#endif
return result;
}
header_function
Uint8 bitLSBU32 (Uint32 x)
{
debugAssert(x);
Uint8 result;
#if defined(ENV_COMPILER_MSVC)
// _BitScanForward: Search LSB->MSB and load the position of first bit in location (zero-indexed)
unsigned long lsb_location = 0;
debugAssert(_BitScanForward(&lsb_location, x));
result = cast_val(Uint8, lsb_location);
#else
// __builtin_ctz: Returns the number of trailing 0-bits starting from LSB
Sint lsb_location = __builtin_ctz(x);
result = cast_val(Uint8, lsb_location);
#endif
return result;
}
header_function
Uint8 bitLSBU64 (Uint64 x)
{
debugAssert(x);
Uint8 result;
#if defined(ENV_COMPILER_MSVC)
// _BitScanForward: Search LSB->MSB and load the position of first bit in location (zero-indexed)
unsigned long lsb_location = 0;
debugAssert(_BitScanForward64(&lsb_location, x));
result = cast_val(Uint8, lsb_location);
#else
// __builtin_ctzll: Returns the number of trailing 0-bits starting from LSB
Sint lsb_location = __builtin_ctzll(x);
result = cast_val(Uint8, lsb_location);
#endif
return result;
}
#if defined(ENV_LANG_C) && ENV_LANG_C >= 2011
# define bitMSB(_x) _Generic((_x), Uint32: bitMSBU32, Uint64: bitMSBU64)(_x)
# define bitLSB(_x) _Generic((_x), Uint32: bitLSBU32, Uint64: bitLSBU64)(_x)
#elif defined(ENV_LANG_CXX)
header_function Uint8 bitMSB (Uint32 x) { return bitMSBU32(x); }
header_function Uint8 bitMSB (Uint64 x) { return bitMSBU64(x); }
header_function Uint8 bitLSB (Uint32 x) { return bitLSBU32(x); }
header_function Uint8 bitLSB (Uint64 x) { return bitLSBU64(x); }
#endif // !LANG_C && !LANG_CPP
header_function
Uint64 bitPow2U32 (Uint32 x)
{
Uint64 y = 1ull << x;
return y;
}
header_function
Uint64 bitPow2U64 (Uint64 x)
{
Uint64 y = 1ull << x;
return y;
}
header_function
Uint8 bitLog2U32 (Uint32 x)
{
debugAssert(x);
Uint8 y = x ? bitMSBU32(x) : 0u;
return y;
}
header_function
Uint8 bitLog2U64 (Uint64 x)
{
debugAssert(x);
Uint8 y = x ? bitMSBU64(x) : 0u;
return y;
}
header_function
Bool bitIsPowerOf2U32 (Uint32 x)
{
debugAssert(x);
Bool b = (x & (x - 1)) == 0;
return b;
}
header_function
Bool bitIsPowerOf2U64 (Uint64 x)
{
debugAssert(x);
Bool b = (x & (x - 1)) == 0;
return b;
}
header_function
Uint64 bitPrevPowerOf2U32 (Uint32 x)
{
debugAssert(x);
Uint64 y = bitIsPowerOf2U32(x) ? x: (1u << (bitLog2U32(x) - 1u)) ;
return y;
}
header_function
Uint64 bitPrevPowerOf2U64 (Uint64 x)
{
debugAssert(x);
Uint64 y = bitIsPowerOf2U64(x) ? x : (1ull << (bitLog2U64(x) - 1ull));
return y;
}
header_function
Uint64 bitNextPowerOf2U32 (Uint32 x)
{
debugAssert(x);
Uint64 y = bitIsPowerOf2U32(x) ? x : (1u << (bitLog2U32(x) + 1u));
return y;
}
header_function
Uint64 bitNextPowerOf2U64 (Uint64 x)
{
debugAssert(x);
Uint64 y = bitIsPowerOf2U64(x) ? x : (1ull << (bitLog2U64(x) + 1ull));
return y;
}
// Number of leading zeros (on the most significant end)
header_function
Uint8 bitMSZerosU32 (Uint32 x)
{
debugAssert(x);
Uint8 clz = 32U - bitMSBU32(x) - 1U;
return clz;
}
header_function
Uint8 bitMSZerosU64 (Uint64 x)
{
debugAssert(x);
Uint8 clz = 64U - bitMSBU64(x) - 1U;
return clz;
}
// square root functions from Mark Dickinson
// https://stackoverflow.com/a/70550680/12862673
header_function
Uint16 bitSqrtU32 (Uint32 x)
{
Sint lz = bitMSZerosU32(x | 1U) & 0x1E;
x <<= lz;
Uint32 y = 1u + (x >> 30);
y = (y << 1) + (x >> 27) / y;
y = (y << 3) + (x >> 21) / y;
y = (y << 7) + (x >> 9) / y;
y -= x < cast_val(Uint32, y) * y;
return cast_val(Uint16, (y >> (lz >> 1)));
}
header_function
Uint32 bitSqrtU64 (Uint64 x)
{
Uint8 lz = cast_val(Uint8, bitMSZerosU64(x | 1ull) & 0x3Eu);
x <<= lz;
Uint64 y = 2ull + (x >> 63);
y = (y << 1) + (x >> 59) / y;
y = (y << 3) + (x >> 53) / y;
y = (y << 7) + (x >> 41) / y;
y = (y << 15) + (x >> 17) / y;
y -= x < cast_val(Uint64, y) * y;
return cast_val(Uint32, (y >> (lz >> 1)));
}
#if defined(ENV_LANG_C) && ENV_LANG_C >= 2011
# define bitPow2(x) _Generic((x), Uint32: bitPow2U32, Uint64: bitPow2U64)(x)
# define bitLog2(x) _Generic((x), Uint32: bitLog2U32, Uint64: bitLog2U64)(x)
# define bitIsPowerOf2(x) _Generic((x), Uint32: bitIsPowerOf2U32, Uint64: bitIsPowerOf2U64)(x)
# define bitPrevPowerOf2(x) _Generic((x), Uint32: bitPrevPowerOf2U32, Uint64: bitPrevPowerOf2U64)(x)
# define bitNextPowerOf2(x) _Generic((x), Uint32: bitNextPowerOf2U32, Uint64: bitNextPowerOf2U64)(x)
# define bitSqrt(x) _Generic((x), Uint32: bitSqrtU32, Uint64: bitSqrtU64)(x)
#elif defined(ENV_LANG_CXX)
header_function Uint64 bitPow2 (Uint32 x) { return bitPow2U32(x); }
header_function Uint64 bitPow2 (Uint64 x) { return bitPow2U64(x); }
header_function Uint8 bitLog2 (Uint32 x) { return bitLog2U32(x); }
header_function Uint8 bitLog2 (Uint64 x) { return bitLog2U64(x); }
header_function Bool bitIsPowerOf2 (Uint32 x) { return bitIsPowerOf2U32(x); }
header_function Bool bitIsPowerOf2 (Uint64 x) { return bitIsPowerOf2U64(x); }
header_function Uint64 bitPrevPowerOf2 (Uint32 x) { return bitPrevPowerOf2U32(x); }
header_function Uint64 bitPrevPowerOf2 (Uint64 x) { return bitPrevPowerOf2U64(x); }
header_function Uint64 bitNextPowerOf2 (Uint32 x) { return bitNextPowerOf2U32(x); }
header_function Uint64 bitNextPowerOf2 (Uint64 x) { return bitNextPowerOf2U64(x); }
header_function Uint16 bitSqrt (Uint32 x) { return bitSqrtU32(x); }
header_function Uint32 bitSqrt (Uint64 x) { return bitSqrtU64(x); }
#endif
/*************************************************************************************
* Random Number Generation (xoshiro256**)
* ---------------------------------------
* Courtsey of David Blackman and Sebastiano Vigna
* License: Public Domain
* https://prng.di.unimi.it/xoshiro256starstar.c
*/
// splitmix64 RNG used to seed the xoshiro256** state
// https://prng.di.unimi.it/splitmix64.c
header_function
Uint64 rng_SeedCalculator (Uint64 *seed_ptr)
{
*seed_ptr += 0x9e3779b97f4a7c15;
Uint64 seed = *seed_ptr;
seed = (seed ^ (seed >> 30)) * 0xbf58476d1ce4e5b9;
seed = (seed ^ (seed >> 27)) * 0x94d049bb133111eb;
Uint64 result = seed ^ (seed >> 31);
return result;
}
header_function
Uint64 rng_RotateLeft(const Uint64 x, Sint k) {
return (x << k) | (x >> (64 - k));
}
typedef struct RNG_State {
Uint64 s[4];
} RNG_State;
header_function
RNG_State rngMake (Uint64 seed)
{
RNG_State state = {};
state.s[0] = rng_SeedCalculator(&seed);
state.s[1] = rng_SeedCalculator(&seed);
state.s[2] = rng_SeedCalculator(&seed);
state.s[3] = rng_SeedCalculator(&seed);
return state;
}
header_function
Uint64 rngGet (RNG_State *state)
{
const Uint64 result = rng_RotateLeft(state->s[1] * 5, 7) * 9;
const Uint64 t = state->s[1] << 17;
state->s[2] ^= state->s[0];
state->s[3] ^= state->s[1];
state->s[1] ^= state->s[2];
state->s[0] ^= state->s[3];
state->s[2] ^= t;
state->s[3] = rng_RotateLeft(state->s[3], 45);
return result;
}
// This is equivalent to 2^128 calls to rngGet(), it can be used to generate 2^128
// non-overlapping subsequences for parallel computations.
header_function
RNG_State rngJump128 (RNG_State prev_state)
{
persistent_immutable Uint64 JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c };
RNG_State new_state = {};
for (Size i = 0; i < elemin(JUMP); i++) {
for (Size b = 0; b < 64; b++) {
if (JUMP[i] & 1ull << b) {
new_state.s[0] ^= prev_state.s[0];
new_state.s[1] ^= prev_state.s[1];
new_state.s[2] ^= prev_state.s[2];
new_state.s[3] ^= prev_state.s[3];
}
rngGet(&prev_state);
}
}
return new_state;
}
// This is equivalent to 2^192 calls to rngGet(), it can be used to generate 2^64 starting points,
// from each of which rngJump128() will generate 2^64 non-overlapping subsequences for parallel
// **distributed** computations.
header_function
RNG_State rngJump192 (RNG_State prev_state)
{
persistent_immutable Uint64 LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 };
RNG_State new_state = {};
for (Size i = 0; i < elemin(LONG_JUMP); i++) {
for (Size b = 0; b < 64; b++) {
if (LONG_JUMP[i] & 1ull << b) {
new_state.s[0] ^= prev_state.s[0];
new_state.s[1] ^= prev_state.s[1];
new_state.s[2] ^= prev_state.s[2];
new_state.s[3] ^= prev_state.s[3];
}
rngGet(&prev_state);
}
}
return new_state;
}
/*************************************************************************************
* Serialization
*/
header_function
void dump8 (Uint8 src, Byte *dest)
{
dest[0] = src;
}
#define dump8BE dump8
#define dump8LE dump8
header_function
void dump16LE (Uint16 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
}
header_function
void dump16BE (Uint16 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
dest[1] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
}
header_function
void dump32LE (Uint32 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
dest[2] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020);
dest[3] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030);
}
header_function
void dump32BE (Uint32 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030);
dest[1] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020);
dest[2] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
dest[3] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
}
header_function
void dump64LE (Uint64 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
dest[1] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
dest[2] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020);
dest[3] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030);
dest[4] = cast_val(Uint8, (0x000000FF00000000 & (src)) >> 040);
dest[5] = cast_val(Uint8, (0x0000FF0000000000 & (src)) >> 050);
dest[6] = cast_val(Uint8, (0x00FF000000000000 & (src)) >> 060);
dest[7] = cast_val(Uint8, (0xFF00000000000000 & (src)) >> 070);
}
header_function
void dump64BE (Uint64 src, Byte *dest)
{
dest[0] = cast_val(Uint8, (0xFF00000000000000 & (src)) >> 070);
dest[1] = cast_val(Uint8, (0x00FF000000000000 & (src)) >> 060);
dest[2] = cast_val(Uint8, (0x0000FF0000000000 & (src)) >> 050);
dest[3] = cast_val(Uint8, (0x000000FF00000000 & (src)) >> 040);
dest[4] = cast_val(Uint8, (0x00000000FF000000 & (src)) >> 030);
dest[5] = cast_val(Uint8, (0x0000000000FF0000 & (src)) >> 020);
dest[6] = cast_val(Uint8, (0x000000000000FF00 & (src)) >> 010);
dest[7] = cast_val(Uint8, (0x00000000000000FF & (src)) >> 000);
}
header_function
Uint8 load8 (const Byte *src)
{
Uint8 result = src[0];
return result;
}
#define load8BE load8
#define load8LE load8
header_function
Uint16 load16LE (const Byte *src) {
Uint16 result = cast_val(Uint16, cast_val(Uint16, 255 & src[1]) << 8 | cast_val(Uint16, 255 & src[0]));
return result;
}
header_function
Uint16 load16BE (const Byte *src) {
Uint16 result = cast_val(Uint16, cast_val(Uint16, 255 & src[0]) << 8 | cast_val(Uint16, 255 & src[1]));
return result;
}
header_function
Uint32 load32LE (const Byte *src)
{
Uint32 result = (cast_val(Uint32, 255 & src[3]) << 030 | cast_val(Uint32, 255 & src[2]) << 020 |
cast_val(Uint32, 255 & src[1]) << 010 | cast_val(Uint32, 255 & src[0]) << 000);
return result;
}
header_function
Uint32 load32BE (const Byte *src)
{
Uint32 result = (cast_val(Uint32, 255 & src[0]) << 030 | cast_val(Uint32, 255 & src[1]) << 020 |
cast_val(Uint32, 255 & src[2]) << 010 | cast_val(Uint32, 255 & src[3]) << 000);
return result;
}
header_function
Uint64 load64LE (const Byte *src)
{
Uint64 result = (cast_val(Uint64, 255 & src[7]) << 070 | cast_val(Uint64, 255 & src[6]) << 060 |
cast_val(Uint64, 255 & src[5]) << 050 | cast_val(Uint64, 255 & src[4]) << 040 |
cast_val(Uint64, 255 & src[3]) << 030 | cast_val(Uint64, 255 & src[2]) << 020 |
cast_val(Uint64, 255 & src[1]) << 010 | cast_val(Uint64, 255 & src[0]) << 000);
return result;
}
header_function
Uint64 load64BE (const Byte *src)
{
Uint64 result = (cast_val(Uint64, 255 & src[0]) << 070 | cast_val(Uint64, 255 & src[1]) << 060 |
cast_val(Uint64, 255 & src[2]) << 050 | cast_val(Uint64, 255 & src[3]) << 040 |
cast_val(Uint64, 255 & src[4]) << 030 | cast_val(Uint64, 255 & src[5]) << 020 |
cast_val(Uint64, 255 & src[6]) << 010 | cast_val(Uint64, 255 & src[7]) << 000);
return result;
}
typedef union loadump_8 { Uint8 u8; Sint8 s8; } loadump_8;
typedef union loadump_16 { Uint16 u16; Sint16 s16; } loadump_16;
typedef union loadump_32 { Uint32 u32; Sint32 s32; Float32 f32; } loadump_32;
typedef union loadump_64 { Uint64 u64; Sint64 s64; Float64 f64; } loadump_64;
/*************************************************************************************
* Serialization
*/
// NOTE(naman): A basic implementation of this function for reading and writing from memory
// is given below after the implementation of segar.
// Search for `Srlz_Memory_Loader` and `Srlz_Memory_Dumper`.
#define SRLZ_FUNC(_name) Bool _name (void* data, Size size, void* userdata)
header_function
Bool srlzUint8 (Uint8 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
unused_variable(big_endian);
loadump_8 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.u8 = *datum;
dump8(ld.u8, buf);
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
ld.u8 = load8(buf);
*datum = ld.u8;
}
return true;
}
header_function
Bool srlzSint8 (Sint8 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
unused_variable(big_endian);
loadump_8 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.s8 = *datum;
dump8(ld.u8, buf);
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
ld.u8 = load8(buf);
*datum = ld.s8;
}
return true;
}
header_function
Bool srlzUint16 (Uint16 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_16 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.u16 = *datum;
if (big_endian) { dump16BE(ld.u16, buf); }
else { dump16LE(ld.u16, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u16 = load16BE(buf); }
else { ld.u16 = load16LE(buf); }
*datum = ld.u16;
}
return true;
}
header_function
Bool srlzSint16 (Sint16 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_16 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.s16 = *datum;
if (big_endian) { dump16BE(ld.u16, buf); }
else { dump16LE(ld.u16, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u16 = load16BE(buf); }
else { ld.u16 = load16LE(buf); }
*datum = ld.s16;
}
return true;
}
header_function
Bool srlzUint32 (Uint32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_32 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.u32 = *datum;
if (big_endian) { dump32BE(ld.u32, buf); }
else { dump32LE(ld.u32, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u32 = load32BE(buf); }
else { ld.u32 = load32LE(buf); }
*datum = ld.u32;
}
return true;
}
header_function
Bool srlzSint32 (Sint32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_32 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.s32 = *datum;
if (big_endian) { dump32BE(ld.u32, buf); }
else { dump32LE(ld.u32, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u32 = load32BE(buf); }
else { ld.u32 = load32LE(buf); }
*datum = ld.s32;
}
return true;
}
header_function
Bool srlzFloat32 (Float32 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_32 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.f32 = *datum;
if (big_endian) { dump32BE(ld.u32, buf); }
else { dump32LE(ld.u32, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u32 = load32BE(buf); }
else { ld.u32 = load32LE(buf); }
*datum = ld.f32;
}
return true;
}
header_function
Bool srlzUint64 (Uint64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_64 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.u64 = *datum;
if (big_endian) { dump64BE(ld.u64, buf); }
else { dump64LE(ld.u64, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u64 = load64BE(buf); }
else { ld.u64 = load64LE(buf); }
*datum = ld.u64;
}
return true;
}
header_function
Bool srlzSint64 (Sint64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_64 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.s64 = *datum;
if (big_endian) { dump64BE(ld.u64, buf); }
else { dump64LE(ld.u64, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u64 = load64BE(buf); }
else { ld.u64 = load64LE(buf); }
*datum = ld.s64;
}
return true;
}
header_function
Bool srlzFloat64 (Float64 *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
loadump_64 ld = {0};
Byte buf[sizeof(*datum)] = {0};
if (write) {
ld.f64 = *datum;
if (big_endian) { dump64BE(ld.u64, buf); }
else { dump64LE(ld.u64, buf); }
if (!func(buf, sizeof(*datum), funcdata)) return false;
} else {
if (!func(buf, sizeof(*datum), funcdata)) return false;
if (big_endian) { ld.u64 = load64BE(buf); }
else { ld.u64 = load64LE(buf); }
*datum = ld.f64;
}
return true;
}
/**************************************************************************************
* Printing
*/
#define PRINT_FUNC(name) Size name (void *userdata, const Char *fmt, ...)
#define PRINT_CALLBACK_FUNC(name) void name (void *userdata, const Char *filled_buffer, Size buffer_len, Size buffer_cap)
typedef enum Print_Flags {
Print_Flags_LEFT_JUSTIFIED = 1u << 0,
Print_Flags_ALTERNATE_FORM = 1u << 1,
Print_Flags_LEADING_PLUS = 1u << 2,
Print_Flags_LEADING_SPACE = 1u << 3,
Print_Flags_LEADING_ZERO = 1u << 4,
Print_Flags_INT8 = 1u << 5,
Print_Flags_INT16 = 1u << 6,
Print_Flags_INT64 = 1u << 7,
Print_Flags_NEGATIVE = 1u << 8,
Print_Flags_FLOAT_FIXED = 1u << 9,
Print_Flags_FLOAT_EXP = 1u << 10,
Print_Flags_FLOAT_HEX = 1u << 11,
} Print_Flags;
// TODO(naman): Add the following features:
// * Comma in numbers
// + Indian style (`)
// + Western style (')
// * Unit suffix
// + SI (1 K = 1000) (:)
// + IEC (1 K = 1024) (;)
header_function
with_clang_gcc(__attribute__(( format( __printf__, 3, 0 )))) /* 2nd arg is 0 since va_list can't be checked */
Size printStringVarArg (Char *buffer, Size buffer_size,
Char const *format, va_list va,
PRINT_CALLBACK_FUNC((*callback)), void *userdata)
{
debugAssert(buffer);
debugAssert(buffer_size);
debugAssert(format);
debugAssert(callback);
Char const *fmt = format;
Size buffer_index = 0;
Size total_size = 0;
#define PRINT_ADD_CHAR(_c) do { \
if (buffer_index == buffer_size) { \
callback(userdata, buffer, buffer_index, buffer_size); \
buffer_index = 0; \
} \
buffer[buffer_index++] = _c; \
total_size++; \
} while (0)
for(;;) {
// Copy everything up to the next % (or end of string)
while ((fmt[0] != '%') && (fmt[0] != '\0')) {
PRINT_ADD_CHAR(fmt[0]);
fmt++;
}
if (fmt[0] == '%') {
Char const *format_pointer = fmt;
fmt++;
// read the modifiers first
Uint32 flags = 0;
// flags
for(;;) {
switch (fmt[0]) {
case '-': { // if we have left justify
flags |= Print_Flags_LEFT_JUSTIFIED;
fmt++;
continue;
} break;
case '#': { // if we use alternate form
flags |= Print_Flags_ALTERNATE_FORM;
fmt++;
continue;
} break;
case '+': { // if we have leading plus
flags |= Print_Flags_LEADING_PLUS;
fmt++;
continue;
} break;
case ' ': { // if we have leading space
flags |= Print_Flags_LEADING_SPACE;
fmt++;
continue;
} break;
case '0': { // if we have leading zero
flags |= Print_Flags_LEADING_ZERO;
fmt++;
goto flags_done;
} break;
default: {
goto flags_done;
} break;
}
}
flags_done:
Sint minimum_width = 0; // called field width in documentation
// get the field width
if (fmt[0] == '*') {
minimum_width = va_arg(va, Sint);
fmt++;
} else {
while ((fmt[0] >= '0') && (fmt[0] <= '9')) {
minimum_width = (minimum_width * 10) + (fmt[0] - '0');
fmt++;
}
}
Sint precision = -1;
// get the precision
if (fmt[0] == '.') {
fmt++;
if (fmt[0] == '*') {
precision = va_arg(va, Sint);
fmt++;
} else {
precision = 0;
if (fmt[0] == '-') { // Negative precision is treated as if precision was omitted
fmt++;
precision = -1;
while ((fmt[0] >= '0') && (fmt[0] <= '9')) {
fmt++;
}
} else {
while ((fmt[0] >= '0') && (fmt[0] <='9')) {
precision = (precision * 10) + (fmt[0] - '0');
fmt++;
}
}
}
}
// handle integer size overrides
switch (fmt[0]) {
case 'h': { // are we 64-bit
flags |= Print_Flags_INT16;
fmt++;
if (fmt[0] == 'h') {
flags &= ~cast_val(Uint32, Print_Flags_INT16);
flags |= Print_Flags_INT8;
fmt++;
}
} break;
case 'l': { // are we 64-bit
flags |= Print_Flags_INT64;
fmt++;
if (fmt[0] == 'l') fmt++;
} break;
case 'z': { // are we 64-bit on size_t?
flags |= (sizeof(Size) == 8) ? Print_Flags_INT64 : 0;
fmt++;
} break;
case 't': { // are we 64-bit on ptrdiff_t?
flags |= (sizeof(Dptr) == 8) ? Print_Flags_INT64 : 0;
fmt++;
} break;
default: {
} break;
}
# define PRINT_STR_SIZE 2048ULL
Char num_str[PRINT_STR_SIZE];
Char *num_str_ptr = num_str;
Char *str = nullptr;
Char head_str[8] = {0};
Size head_index = 0;
Char tail_str[8] = {0};
Size tail_index = 0;
Size len = 0;
switch (fmt[0]) { // handle each replacement
case 's': { // string
// get the string
str = va_arg(va, Char*);
if (str == nullptr) {
str = cast_const(Char *, "null");
}
// By this point, str is most definitely not nullptr
len = strlen(str);
// clamp to precision
// Since precision inits at -1, if none was mentioned, this will not execute
if (len > cast_val(Size, precision)) {
len = cast_val(Size, precision);
}
precision = 0;
} break;
case 'c': { // char
// get the character
str = num_str_ptr + PRINT_STR_SIZE - 1;
*str = cast_val(Char, va_arg(va, Sint));
len = 1;
precision = 0;
} break;
case 'n': { // weird write-bytes specifier
if (flags & Print_Flags_INT64) {
Sint64 *p = va_arg(va, Sint64*);
*p = cast_val(Sint64, buffer_index);
} else if (flags & Print_Flags_INT16) {
Sint16 *p = va_arg(va, Sint16*);
*p = cast_val(Sint16, buffer_index);
} else if (flags & Print_Flags_INT8) {
Sint8 *p = va_arg(va, Sint8*);
*p = cast_val(Sint8, buffer_index);
} else {
Sint *p = va_arg(va, Sint*);
*p = cast_val(Sint, buffer_index);
}
} break;
case 'b': case 'B':
case 'o': case 'O' :
case 'x': case 'X': { // binary
enum { BIN, OCT, HEX } base;
Bool upper = false;
Char type = fmt[0];
switch (type) {
case 'b': base = BIN; break;
case 'B': base = BIN; upper = true; break;
case 'o': base = OCT; break;
case 'O': base = OCT; upper = true; break;
case 'x': base = HEX; break;
case 'X': base = HEX; upper = true; break;
default: {
base = BIN; // To silence unitialized variable warning
debugAssert(false && "Print: Integer base unitialized");
} break;
}
Uint64 num;
if (flags & Print_Flags_INT64) {
num = va_arg(va, Uint64);
} else {
num = va_arg(va, Uint32);
}
Uint64 num_dec = num;
if (flags & Print_Flags_INT8) {
num_dec = cast_val(Uint8, num_dec);
} else if (flags & Print_Flags_INT16) {
num_dec = cast_val(Uint16, num_dec);
}
str = num_str_ptr + PRINT_STR_SIZE;
if ((num == 0) && (precision == 0)) {
break;
}
for (;;) {
Uint64 and_mask = 0;
switch (base) {
case BIN: and_mask = 0x1ULL; break;
case OCT: and_mask = 0x7ULL; break;
case HEX: and_mask = 0xFULL; break;
default: debugBreak(); break;
}
Uint64 shift_magnitude = 0;
switch (base) {
case BIN: shift_magnitude = 1ULL; break;
case OCT: shift_magnitude = 3ULL; break;
case HEX: shift_magnitude = 4ULL; break;
default: debugBreak(); break;
}
Uint64 n = num_dec & and_mask;
num_dec = num_dec >> shift_magnitude;
str--;
switch (base) {
case BIN: {
*str = (n == 1) ? '1' : '0';
} break;
case OCT: {
switch (n) {
case 0: *str = '0'; break;
case 1: *str = '1'; break;
case 2: *str = '2'; break;
case 3: *str = '3'; break;
case 4: *str = '4'; break;
case 5: *str = '5'; break;
case 6: *str = '6'; break;
case 7: *str = '7'; break;
default: debugBreak(); break;
}
} break;
case HEX: {
switch (n) {
case 0x0: *str = '0'; break;
case 0x1: *str = '1'; break;
case 0x2: *str = '2'; break;
case 0x3: *str = '3'; break;
case 0x4: *str = '4'; break;
case 0x5: *str = '5'; break;
case 0x6: *str = '6'; break;
case 0x7: *str = '7'; break;
case 0x8: *str = '8'; break;
case 0x9: *str = '9'; break;
case 0xA: *str = upper ? 'A' : 'a'; break;
case 0xB: *str = upper ? 'B' : 'b'; break;
case 0xC: *str = upper ? 'C' : 'c'; break;
case 0xD: *str = upper ? 'D' : 'd'; break;
case 0xE: *str = upper ? 'E' : 'e'; break;
case 0xF: *str = upper ? 'F' : 'f'; break;
default: debugBreak(); break;
}
} break;
default: debugBreak(); break;
}
if ((num_dec != 0) || (((num_str_ptr + PRINT_STR_SIZE) - str) < precision)) {
continue;
} else {
break;
}
}
len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str);
Char head_char = 0;
switch (base) {
case BIN: head_char = upper ? 'B' : 'b'; break;
case OCT: head_char = upper ? 'O' : 'o'; break;
case HEX: head_char = upper ? 'X' : 'x'; break;
default: debugBreak(); break;
}
if (flags & Print_Flags_ALTERNATE_FORM) {
head_str[head_index++] = '0';
if (upper) {
head_str[head_index++] = head_char;
} else {
head_str[head_index++] = head_char;
}
}
if (precision < 0) {
precision = 0;
}
} break;
case 'u':
case 'd': { // integer
// get the integer and abs it
Uint64 num = 0;
if (flags & Print_Flags_INT64) {
Sint64 i64 = va_arg(va, Sint64);
if ((fmt[0] != 'u') && (i64 < 0)) {
num = 0ULL - cast_val(Uint64, i64);
flags |= Print_Flags_NEGATIVE;
} else {
num = cast_val(Uint64, i64);
}
} else {
Sint32 i = va_arg(va, Sint32);
if ((fmt[0] != 'u') && (i < 0)) {
flags |= Print_Flags_NEGATIVE;
num = cast_val(Uint64, 0U - cast_val(Uint32, i));
} else {
num = cast_val(Uint64, i);
}
}
// convert to string
Uint64 num_dec = num;
if (flags & Print_Flags_INT8) {
num_dec = cast_val(Uint8, num_dec);
} else if (flags & Print_Flags_INT16) {
num_dec = cast_val(Uint16, num_dec);
}
str = num_str_ptr + PRINT_STR_SIZE;
if ((num == 0) && (precision == 0)) {
break;
}
while (num_dec) {
str--;
*str = cast_val(Char, num_dec % 10) + '0';
num_dec /= 10;
}
// get the length that we copied
len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str);
if (len == 0) {
--str;
*str = '0';
len = 1;
}
if (flags & Print_Flags_NEGATIVE) {
head_str[head_index++] = '-';
}
if (flags & Print_Flags_LEADING_PLUS) {
head_str[head_index++] = '+';
}
if (flags & Print_Flags_LEADING_SPACE) {
if (!(flags & Print_Flags_NEGATIVE)) {
head_str[head_index++] = ' ';
}
}
if (flags & Print_Flags_LEADING_ZERO) {
head_str[head_index++] = '0';
}
if (precision < 0) {
precision = 0;
}
} break;
case 'p': { // pointer
const void *pv = va_arg(va, void*);
const Uptr pval = cast_ptr(Uptr, pv);
const Uint64 num = cast_val(Uint64, pval);
debugAssert(sizeof(pval) <= sizeof(num));
Uint64 num_dec = num;
str = num_str_ptr + PRINT_STR_SIZE;
for (;;) {
Uint64 n = num_dec & 0xf;
num_dec = num_dec >> 4;
str--;
switch (n) {
case 0x0: *str = '0'; break;
case 0x1: *str = '1'; break;
case 0x2: *str = '2'; break;
case 0x3: *str = '3'; break;
case 0x4: *str = '4'; break;
case 0x5: *str = '5'; break;
case 0x6: *str = '6'; break;
case 0x7: *str = '7'; break;
case 0x8: *str = '8'; break;
case 0x9: *str = '9'; break;
case 0xA: *str = 'A'; break;
case 0xB: *str = 'B'; break;
case 0xC: *str = 'C'; break;
case 0xD: *str = 'D'; break;
case 0xE: *str = 'E'; break;
case 0xF: *str = 'F'; break;
default: debugBreak(); break;
}
if ((num_dec != 0) || (((num_str_ptr + PRINT_STR_SIZE) - str) < precision)) {
continue;
} else {
break;
}
}
len = (cast_ptr(Uptr, num_str_ptr) + PRINT_STR_SIZE) - cast_ptr(Uptr, str);
} break;
case 'f': case 'F':
case 'e': case 'E':
case 'a': case 'A': {
switch (fmt[0]) {
case 'f': case 'F': { flags |= Print_Flags_FLOAT_FIXED; } break;
case 'e': case 'E': { flags |= Print_Flags_FLOAT_EXP; } break;
case 'a': case 'A': { flags |= Print_Flags_FLOAT_HEX; } break;
default: break;
}
Bool capital = false;
switch (fmt[0]) {
case 'F': case 'E': case 'A': { capital = true; } break;
default: break;
}
Bool precision_provided = precision >= 0;
if (flags & Print_Flags_FLOAT_HEX) {
// Default precision is set to -1, so this means that if precision is not mentioned,
// we print 13 digits which is enough for a lossless roundtrip.
if (precision < 0) {
precision = 13;
} else if (precision > 17) {
precision = 13;
}
} else {
// Since decimal (non-hex) is for human reading, 6 is the right precision
// (considering that it is also default in libc).
if (precision < 0) {
precision = 6;
} else if (precision > 17) {
precision = 17;
}
}
Float64 f64 = va_arg(va, Float64);
union loadump_64 ld64 = {.f64 = f64};
Uint64 bits = ld64.u64;
/* NOTE(naman): 64-bit IEEE-754 Floating Point structure
____________________________________________
| Sign bit | Exponent bits | Mantissa bits|
| 1 | 11 | 52 |
--------------------------------------------
*/
#define PRINT_F64_MANTISSA_BITS 52
#define PRINT_F64_EXPONENT_BITS 11
#define PRINT_F64_BIAS 1023
// Is the top bit set?
Bool negative = bits >> (PRINT_F64_MANTISSA_BITS + PRINT_F64_EXPONENT_BITS);
// Remove all mantissa bits ------------------ and then reset the sign bit
Uint32 exponent_biased = ((cast_val(Uint32, bits >> PRINT_F64_MANTISSA_BITS)) &
((1U << PRINT_F64_EXPONENT_BITS) - 1U));
Sint32 exponent = cast_val(Sint32, exponent_biased) - PRINT_F64_BIAS;
// Reset all bits except for the mantissa bits
Uint64 mantissa = bits & ((1ULL << PRINT_F64_MANTISSA_BITS) - 1ULL);
if (negative) {
flags |= Print_Flags_NEGATIVE;
}
{ // Leading String
if (flags & Print_Flags_NEGATIVE) {
head_str[head_index++] = '-';
} else if (flags & Print_Flags_LEADING_PLUS) {
head_str[head_index++] = '+';
} else if (flags & Print_Flags_LEADING_SPACE) {
if (!(flags & Print_Flags_NEGATIVE)) {
head_str[head_index++] = ' ';
}
}
if ((flags & Print_Flags_LEADING_ZERO) &&
!(flags & Print_Flags_FLOAT_HEX)) {
head_str[head_index++] = '0';
}
}
// Step 1: Print if it is NaN/inf or zero
if (exponent_biased == 0x7FF) { // Handle NaN and Inf
str = num_str;
if (capital) {
if (mantissa) {
*str++ = 'N';
*str++ = 'A';
*str++ = 'N';
} else {
*str++ = 'I';
*str++ = 'N';
*str++ = 'F';
}
} else {
if (mantissa) {
*str++ = 'n';
*str++ = 'a';
*str++ = 'n';
} else {
*str++ = 'i';
*str++ = 'n';
*str++ = 'f';
}
}
} else if (exponent_biased == 0 && mantissa == 0) {
if (flags & Print_Flags_FLOAT_HEX) {
head_str[head_index++] = '0';
head_str[head_index++] = capital ? 'X' : 'x';
}
str = num_str;
*str++ = '0';
if (precision > 0) {
*str++ = '.';
memset(str, '0', cast_val(Size, precision));
str += precision;
}
if (flags & Print_Flags_FLOAT_EXP) {
tail_str[tail_index++] = capital ? 'E' : 'e';
tail_str[tail_index++] = '+';
tail_str[tail_index++] = '0';
tail_str[tail_index++] = '0';
} else if (flags & Print_Flags_FLOAT_HEX) {
tail_str[tail_index++] = capital ? 'P' : 'p';
tail_str[tail_index++] = '+';
tail_str[tail_index++] = '0';
}
}
// Normalized means that the float can always be represented as
// mantissa_normalised * 2^exponent_normalised
Uint64 mantissa_normalised;
Sint32 exponent_normalised;
if ((exponent_biased == 0) && (mantissa != 0)) { // Subnormal/Denormals
// Value of float is:
// (mantissa / 2^52) * 2^(1-1023)
// => mantissa * 2^(1 - 1023 - 52)
// => mantissa * 2^(-1074)
// Meaning, mantissa: mantissa ; exponent: -1074
mantissa_normalised = mantissa;
exponent_normalised = -1074;
} else {
// If the float is normal, the value is:
// (1 + (mantissa / 2^52)) * 2^(exponent)
// => (2^52 + mantissa) * 2^(exponent - 52)
// Meaning, mantissa: 2^52 + mantissa ; exponent: exponent - 52
mantissa_normalised = mantissa | (1ULL << 52);
exponent_normalised = exponent - 52;
}
// Step 2: Print if it a hexadecimal float
if ((str == nullptr) && (flags & Print_Flags_FLOAT_HEX)) {
head_str[head_index++] = '0';
head_str[head_index++] = capital ? 'X' : 'x';
Uint64 man = mantissa_normalised;
Sint32 exp = exponent_normalised + PRINT_F64_MANTISSA_BITS;
if (mantissa || precision) {
/* This makes it so that the MSB of mantissa is on
* 60th bit, and the normal/denormal bit (that we set above)
* is on 61st. This is done so that we can later extract the
* bit a nibble at a time by taking the top 4 bits repeatedly like this:
* (man >> 60) & 0xF; man <<= 4;
* This prevents having to do more complex masking.
*
* To find out how much to shift, we need to know how many nibbles
* need to be printed, and then shift so that that many nibbles end up
* in the front (when going from MSB to LSB). That calculation is:
* bitsInFloat64 - (bitsInNibble * (MANTISSA_BITS/bitsInNibble + 1))
* => 64 - (4 * (52/4 + 1))
* => 64 - (4 * (13 + 1))
* => 64 - (4 * 14)
* => 64 - 56
* => 8
* (1 at the end comes to handle the 53rd implicit bit that we just added above)
*/
man <<= 8;
/* This will do the rounding if the precision is smaller than maximum.
* It does this by incrementing the MSB of mantissa that won't be printed.
* Doing that will send a carry forward if the nibble associated with
* that MSB was >= 8; and not if it was < 7. Similarly, the carry will
* propagate further, rounding each nibble to its nearest value,
* with ties to even (i.e., round-to-nearest-even) which is considered default.
*/
if (precision < 13) {
/* Now that we have moved all the nibbles to the top with `man <<= 8`,
* the bit index of the first nibble that will NOT be printed
* is (59 - 4*precision).
*/
const Uint64 one_at_unprinted_msb_if_precision_is[13] = {
1ULL << (59 - 4 * 0), // 576460752303423488ULL, 0x800000000000000
1ULL << (59 - 4 * 1), // 36028797018963968ULL, 0x80000000000000
1ULL << (59 - 4 * 2), // 2251799813685248ULL, 0x8000000000000
1ULL << (59 - 4 * 3), // 140737488355328ULL, 0x800000000000
1ULL << (59 - 4 * 4), // 8796093022208ULL, 0x80000000000
1ULL << (59 - 4 * 5), // 549755813888ULL, 0x8000000000
1ULL << (59 - 4 * 6), // 34359738368ULL, 0x800000000
1ULL << (59 - 4 * 7), // 2147483648ULL, 0x80000000
1ULL << (59 - 4 * 8), // 134217728ULL, 0x8000000
1ULL << (59 - 4 * 9), // 8388608ULL, 0x800000
1ULL << (59 - 4 * 10), // 524288ULL, 0x80000
1ULL << (59 - 4 * 11), // 32768ULL, 0x8000
1ULL << (59 - 4 * 12), // 2048ULL, 0x800
};
// Various masks used for rounding
Uint64 one_at_unprinted_msb = one_at_unprinted_msb_if_precision_is[precision];
Uint64 ones_after_unprinted_msb = one_at_unprinted_msb - 1ULL;
Uint64 zeroes_at_unprinted = ~((ones_after_unprinted_msb << 1) | 1);
Uint64 one_at_printed_lsb = one_at_unprinted_msb << 1;
// https://medium.com/angular-in-depth/how-to-round-binary-fractions-625c8fa3a1af
// Truncate the mantissa so zero all bits that won't be printed due to precision
Uint64 lower = man & zeroes_at_unprinted;
// Set most significant unprinted bit (not zero) to one
Uint64 middle = lower + one_at_unprinted_msb;
// Add one bit at least significant printed bit (and deal with any carry forwards)
Uint64 upper = lower + one_at_printed_lsb;
if (man < middle) {
man = lower;
} else if (man > middle) {
man = upper;
} else { // man == middle
// Resolve tie to nearest even
if ((lower & one_at_printed_lsb) == 0) {
man = lower;
} else {
man = upper;
}
}
}
Char const *hexs = capital ? "0123456789ABCDEF" : "0123456789abcdef";
str = num_str;
// This prints 0/1 depending on normal/denormal status
*str++ = hexs[(man >> 60) & 0xF];
man <<= 4;
if (precision) *str++ = '.';
Sint32 n = precision;
Uint32 count_of_end_zeroes = 0;
while (n--) {
if ((man >> 60) & 0xF) {
count_of_end_zeroes = 0;
} else {
count_of_end_zeroes++;
}
*str++ = hexs[(man >> 60) & 0xF];
man <<= 4;
}
if (precision_provided) {
count_of_end_zeroes = 0;
}
// str -= count_of_end_zeroes; // Apparently, ending zeroes ARE printed
unused_variable(count_of_end_zeroes);
} else {
len = 0;
}
{
tail_str[tail_index++] = capital ? 'P' : 'p';
tail_str[tail_index++] = exp >= 0 ? '+' : '-';
exp = exp > 0 ? exp : -exp;
Char es[4] = {0};
Char *ep = &es[elemin(es)];
Char el = 0;
if (exp == 0) {
*--ep = '0';
el++;
}
while (exp) {
*--ep = cast_val(Char, exp % 10) + '0';
el++;
exp /= 10;
}
while (el) {
el--;
tail_str[tail_index++] = *ep++;
}
}
}
Bool rounding_up_added_a_new_integer_digit_on_the_left = true;
// If this is true, str will be set to num_str at the end, else to num_str+1.
// This is needed to deal with the carry digit when rounding due to less precision.
// Step 3: Print if it is the usual float to decimal situation
// FIXME(naman): This uses bigint arithmetic, which makes it quite slow (especially
// for doubles with huge integral parts). See about replacing it with
// the Swift's dtoa that uses a improved Grisu2/Errol3 algorithm:
// https://github.com/swiftlang/swift/blob/main/stdlib/public/runtime/SwiftDtoa.cpp
if (str == nullptr) { // If still unprinted, print through the long method
rounding_up_added_a_new_integer_digit_on_the_left = false;
str = num_str_ptr + 1;
// This algorithm is basically a fixed-point exact-conversion
// implementation (inspired by Tei Andu: https://pastebin.com/z9hYEWF1).
// Since this implementation is extremely simple, we don't try to find
// the minimum digits that allow round tripping, especially since
// `printf` like interfaces don't allow asking for it either.
// If you want round-tripping, print as a hex-float, or
// print with precision set to 17.
// If M = mantissa_normalised and X = exponent_normalised,
// then M should be interpreted as a number of shape 1.pqrs or 0.pqrs
// Multiplying with 2^X turns this into a real floating point value:
// v = M.2^X
// But X can be both positive and negative, which means that it could move
// the binary point in M right or left. And even if it is positive, it may not
// move the binary point enough for M to become an integer, which is what
// we want for easy processing.
// So, we multiply the whole value `v` with a huge number 2^p, where `p`
// is chosen so that it can overpower even the most negative X so that
// the whole number `v` becomes a single integer.
// N = v.2^p = M.2^X.2^p
// => N = v.2^p = M.2^(X+p)
// => N = v.2^p = M.2^L
// What this means is that we shift the mantissa M left by X+p (=L) bits to turn
// the whole number into a giant integer. Later, we can extract the integral
// and fractions parts (`I` and `F`) of the original value `v` using:
// I = floor(N/2^p) = N >> p
// F = N mod 2^p = N & ((1 << p) - 1)
// Now, L has to be greater than or equal to zero, since shifting right is stupid,
// even if the mantissa is already integral. So,
// L >= 0
// => X+p >= 0
// => p >= -X
// If `p` is greater than `-X`, we need highest value of `-X` which means we need
// lowest value of `X`. So let's find Xmin:
// First, let's say that the number of exponent bits is k.
// Then, bias is equal to 2^(k-1) - 1
// And since the values of exponent_biased can not be 0 and 111...1
// since these values are reserved for NaN and Inf, exponent_biased's range is
// [1, 2^k - 2].
// To remove bias, we subtract it, so:
// exponent = exponent_biased - bias
// => exponent = [1, 2^k - 2] - (2^(k-1) - 1)
// => exponent = [1, 2^k - 2] - 2^(k-1) + 1
// => exponent = [1 - 2^(k-1) + 1, 2.2^(k-1) - 2^(k-1) + 1]
// => exponent = [2 - 2^(k-1), 2^(k-1) - 1]
// For Float32, this is [-126, 127] and for Float64, this is [-1022, 1023]
// Since X = exponent - mantissa_bits,
// X for Float32 is [-126-23, 127-23] => [-149, 104]
// X for Float64 is [-1022-52, 1023-52] => [-1074, 971]
// Therefore, coming to the inequality,
// p >= -X
// => p >= -(-1074)
// => p >= 1074
// Similarly to find out what is the maximum possible number of bits in `N`, we need
// to add the already existing bits in mantissa and how much we shifted them by. Thus,
// h = L + mantissa_bits
// => h = X + p + mantissa_bits
// => h = (exponent - mantissa_bits) + p + mantissa_bits
// => h = exponent + p
// Let's say that the big int that will store the shifted mantissa has B bits.
// Accounting for one carry bit, this means that,
// h <= B - 1
// => exponent + p <= B - 1
// => p <= B - 1 - exponent
// For inequality to hold forever, (B - 1 - exponent) has to be minimum, meaning
// that `exponent` has to be maximum.
// => p <= B - 1 - 1023
// => p <= B - 1024
// Combining the two inequalities about `p`, we get
// 1074 <= B - 1024
// => B >= 2098
// Since the big int B will be composed of Uint64s, we need to find out how many
// Uint64s it will take. 2098/(8*8) is 32.78, and the closest larger number that
// can be easily cleanly divided in two in 34. However, the Ryan Juckett algorithm used
// bigints with 35 Uint32s, so maybe its safer to use 36 Uint64s, meaning:
// B = 2304
Uint64 bigint[36] = {0};
// Once we set B, we can derive p by:
// p <= 2304 - 1024
// => p <= 1280
// and p >= 1074
// The ideal choice is p = 1152, since it is B/2 (easy to deal with in various ways).
Uint pivot = 1152; // p above, 160 in reference
Sint total_left_shift_signed = exponent_normalised + cast_val(Sint, pivot);
debugAssert(total_left_shift_signed >= 0);
Uint total_left_shift = cast_val(Uint, total_left_shift_signed);
// `L` above, `mlsft` in reference
// The casting is safe since pivot is chosen to be big enough that it makes
// even fully negative X become positive after addition.
// Because pivot is divided by 64 evenly (1152/64=18), the pivot bit will fall
// on an element boundary. This means that in the total_left_shift, the shift
// contributed by pivot always ends at a element boundary, and then the shift by
// exponent_normalised (whether positive or negative) moves the mantissa bits
// left or right, potentially out of alignment with that element boundary.
Uint pivot_elem = pivot/64;
// Since we are using Uint64s for bigint, we need to calculate which elements
// of the array do the bits of mantissa get stored in first, and which bits get
// stored where.
// N = M.2^L
// => N = M.2^(64*(L/64) + (L%64))
// If elems_shifted = L/64, and bits_shifted = L%64
// => N = M.2^(bits_shifted).2^(64*elems_shifted)
// This means we first shift by enough bits to move the mantissa into the right
// relative position between two elements. Then we shift across the as many word
// until the mantissa is in the right place.
// NOTE(naman): The LSB->MSB of our mantissa is stored from index 0->35
// So the best way to visualize the bigint array is:
// bigint = [ 35 | 34 | 33 | 32 | ... | 3 | 2 | 1 | 0 ]
// with the bits in each element going:
// element = [ 63 | 62 | 61 | ... | 3 | 2 | 1 | 0 ]
// In the rest of the comments below, I will refer to range within
// the array as [larger, smaller] indices, since that is what makes
// sense here.
Uint elems_shifted = total_left_shift >> 6; // L / 64, `a` in reference
Uint bits_shifted = total_left_shift & ((1u << 6) - 1u); // L % 64, `b` in reference
bigint[elems_shifted] = mantissa_normalised << bits_shifted; // Mantissa's LSB
if (bits_shifted) {
bigint[elems_shifted+1] = mantissa_normalised >> (64u - bits_shifted); // Mantissa's MSB
}
// The `I` (integer part) is in [35, pivot_elem] and the `F` (fractional part) is
// in [pivot_elem-1, 0].
Char *str_int_begin = str;
Char *str_int_end;
{ // Print integral part
// The basic algorithm is to divide by 10 continuously and collect the remainder.
// This remainder is the digit to be printed in the next place value. Of course,
// when we are dealing with bigint, we have to modify the algorithm like this:
// carry = 0
// for (&elem in integral_bigint from left to right) {
// current = carry.2^64 + *elem
// *elem = floor(current/10)
// carry = current % 10
// }
// As an example, lets say we have the number 5150, and we are dealing with
// two digits at a time. Diving by 10 will mean first doing 51/10=5 and getting
// carry of 1. Then, we multiply the carry 1 with 100, and add it to 50 to get 150,
// which is divided again as 150/10=15. Thus, the final quotient is 515.
// Now, doing the (carry.2^64 + *elem) will require more than 64-bits, which we don't
// have. Maybe we could do it in 128-bits, but there is an easier hack. What if we break
// this operation into two 32-bit ops? That way, we can get it done directly without
// needing any new helper functions or data types.
// Let start with:
// current = carry.2^64 + *elem
// Divide *elem in two 32-bit parts, `hi` and `lo`
// *elem = hi.2^32 + lo
// Let's say:
// cur_hi = carry.2^32 + hi
// and cur_hi = 10.q_hi + r_hi // Quotient and Remainder
// Similarly,
// cur_lo = r_hi.2^32 + lo
// and cur_lo = 10.q_lo + r_lo
// Combining:
// current = carry.2^64 + *elem
// => current = carry.2^64 + hi.2^32 + lo
// => current = 2^32.(carry.2^32 + hi) + lo
// => current = 2^32.cur_hi + lo
// => current = 2^32.(10.q_hi + r_hi) + lo
// => current = 2^32.10.q_hi + 2^32.r_hi + lo
// => current = (q_hi << 32).10 + cur_lo
// => current = (q_hi << 32).10 + 10.q_lo + r_lo
// => current = 10.((q_hi << 32) + q_lo) + r_lo
// And so, we can calculate:
// floor(current/10) = (q_hi << 32) +q_lo
// and current % 10 = r_lo
Bool non_zero_digits_left = false;
Sint non_zero_starts_at = cast_val(Sint, elems_shifted + 1);
str_int_begin = str;
do {
Uint32 carry = 0;
non_zero_digits_left = false;
for (Sint i = non_zero_starts_at; i >= cast_val(Sint, pivot_elem); i--) {
Uint64 elem = bigint[i];
Uint32 hi = cast_val(Uint32, elem >> 32);
Uint32 lo = cast_val(Uint32, elem);
Uint64 cur_hi = (cast_val(Uint64, carry) << 32) + hi;
Uint32 q_hi = cast_val(Uint32, cur_hi / 10u);
Uint32 r_hi = cast_val(Uint32, cur_hi % 10u);
Uint64 cur_lo = (cast_val(Uint64, r_hi) << 32) + lo;
Uint32 q_lo = cast_val(Uint32, cur_lo / 10u);
Uint32 r_lo = cast_val(Uint32, cur_lo % 10u);
bigint[i] = (cast_val(Uint64, q_hi) << 32) + q_lo;
carry = r_lo;
Bool is_zero = bigint[i] == 0;
if ((i == non_zero_starts_at) && is_zero) {
non_zero_starts_at--;
}
non_zero_digits_left |= !is_zero;
}
debugAssert(carry < 10);
*str++ = cast_val(Char, carry) + '0';
} while (non_zero_digits_left);
str_int_end = str - 1;
// Reverse the printed string
Char *begin = str_int_begin;
Char *end = str_int_end;
while (end > begin) {
Char swap = *end;
*end-- = *begin;
*begin++ = swap;
}
}
*str++ = '.';
Char *str_frc_begin = str;
Char *str_frc_end;
{ // Print Fractional part
// When I say that `F` is the fractional part, what I mean is that the
// `F` is an integer that will make the fractional part if the number was
// written in the format `I.F` (where the dot is the decimal point, not
// multiplication. But as it is, if we simply were to read the bits in
// [pivot_elem-1, 0], we just get an integer F that represents
// the fractional part. So, to actually get the digits out of it,
// we will have to figure out how to treat the integer F as a fraction.
// Also, remember that we have a total `pivot` number of bits in F,
// meaning that the decimal point (after multiplying 2^p originally when
// making the whole floating point an integer) is on the pivot point.
// So, the way to get the fractional part is to read only what comes after
// the pivot.
// To calculate the digit:
// d = floor((10 * F)/2^p)
// What this means is that we first multiply F by 10 (thus moving one digit to
// the left of the decimal point), and then right-shift by p-bits so that all
// the numbers right of the decimal point disappear, leaving only the one digit
// that was to the left of the point.
// Once this is done, before we can do the next digit, we change F with:
// F = (10 * F) % 2^p
// Basically, this gives us the bits right of the decimal point after multiplying
// with 10.
// Since we are dealing with bigint, the multiplication algorithm has to be:
// carry = 0
// while (&elem in fractional_bigint from right to left) {
// product = *elem * 10
// *elem = carry + (product % 2^64)
// carry = product / 2^64
// }
// As an example, lets say we have the number 0.5432, and we are dealing with
// two digits at a time. Multiplying with 10 will mean first doing 32*10=320
// and getting result of 20 and carry of 3. Then, we multiply 54 with 10
// to get 540 to which we add the carry 3, getting 543. Again, 43 remain and 5
// becomes the carry. This 5 is the digit extracted, since the number now is
// 5.432, with 5 stored in carry and 4320 being left for future digit extraction.
// Similar to the integral part, doing the (*elem * 10) and (carry + product)
// will require more than 64-bits, which we don't have. So, let's try to do this
// with 32 bit ops again.
// Let's start with:
// product = *elem * 10
// Divide *elem in two 32-bit parts, `hi` and `lo`
// *elem = hi.2^32 + lo
// Let's say:
// prod_lo = lo * 10
// prod_lo_mod = prod_lo % 2^32
// elem_lo = carry + prod_lo_mod
// carry_lo = prod_lo / 2^32
//
// prod_hi = hi * 10
// elem_hi = carry_lo + (prod_hi % 2^32)
// carry_hi = prod_hi / 2^32
//
// *elem = elem_hi.2^32 + elem_lo
// carry = carry_hi
Bool non_zero_digits_left = false;
Uint fraction_starts_at = elems_shifted;
Uint digit_count = 0;
// This loop will always print atleast one digit
do {
Uint32 carry = 0;
non_zero_digits_left = false;
for (Uint32 i = fraction_starts_at; i < pivot_elem; i++) {
Uint64 elem = bigint[i];
Uint32 hi = cast_val(Uint32, elem >> 32);
Uint32 lo = cast_val(Uint32, elem);
Uint64 prod_lo = cast_val(Uint64, lo) * 10u;
Uint32 prod_lo_mod = cast_val(Uint32, prod_lo);
Uint32 elem_lo = carry + prod_lo_mod;
Uint32 carry_lo = cast_val(Uint32, prod_lo >> 32);
if (elem_lo < prod_lo_mod) { // Overflow when adding carry
carry_lo++;
}
Uint64 prod_hi = cast_val(Uint64, hi) * 10u;
Uint32 prod_hi_mod = cast_val(Uint32, prod_hi);
Uint32 elem_hi = carry_lo + prod_hi_mod;
Uint32 carry_hi = cast_val(Uint32, prod_hi >> 32);
if (elem_hi < prod_hi_mod) { // Overflow when adding carry
carry_hi++;
}
bigint[i] = (cast_val(Uint64, elem_hi) << 32) | elem_lo;
carry = carry_hi;
Bool is_zero = bigint[i] == 0;
if ((i == fraction_starts_at) && is_zero) {
fraction_starts_at++;
}
non_zero_digits_left |= !is_zero;
}
if (digit_count < (cast_val(Uint, precision) + 1)) {
debugAssert(carry < 10);
*str++ = cast_val(Char, carry) + '0';
digit_count++;
} else {
// Even after printing (precision + 1) digits, the remaining fraction is
// not zero. So we mark that condition by setting digit_count to a value
// one greater than that.
digit_count = cast_val(Uint, precision) + 2;
}
} while (non_zero_digits_left && (digit_count <= (cast_val(Uint, precision) + 1)));
str_frc_end = str - 1;
unused_variable(str_frc_end); // Prevent "set but unused" warnings
// TODO(naman): Add support for printing floats in exponential notation (A.BCe+D)
// This may be useful: https://gist.github.com/forksnd/c9a91957e9e5c933635148d3b74f7d65
// Now, do rounding based on round-half-to-even (Banker's rounding)
// https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even
// The basic algorithm is this, given precision after decimal point is p,
// 1. Let rounding_digit be the digit at position (p+1)
// 2. Let tail be all digits that come after rounding_digit
// 3. If rounding_digit > 5, round up
// 4. If rounding_digit < 5, round down (do nothing)
// 5. If rounding_digit == 5,
// A. If tail has any non-zero digits: round up
// B. If tail is all zeroes,
// a) If digit at position p is odd, round up
// b) If digit at position p is even, do nothing
// The edge cases to keep in mind are:
// 1. precision == 0, meaning there is no fractional part
// 2. Round-up cascade reaching into integral part from fractional part
// 3. For negative numbers, rounding is done to magnitude and sign is added later
Bool round_up;
if (digit_count <= cast_val(Uint, precision)) { // We needed more fractional digits
// We didn't collect (precision + 1) digits, so non_zero_digits_left must have
// become false, meaning that there is a bunch of zeroes that should trail our
// number.
if (cast_val(Uint, precision) > digit_count) {
Uint zero_count = cast_val(Uint, precision) - digit_count;
// Trailing zeroes
memset(str, '0', zero_count);
str += zero_count;
str_frc_end = str - 1;
}
// Since the fraction ended prematurely, there is no rounding needed either.
round_up = false;
} else { // We got all the fractional digits we needed
const Char rounding_digit = str_frc_begin[precision];
// Bool tail_is_nonzero = ((digit_count > (cast_val(Uint, precision) + 1)) ||
// non_zero_digits_left);
Bool tail_is_nonzero;
if (digit_count > (cast_val(Uint, precision) + 1)) {
// We captured more than precision+1 digits => there is a non-zero tail
tail_is_nonzero = true;
} else if (digit_count < (cast_val(Uint, precision) + 1)) {
// We didn't even capture the rounding digit => fraction ended early => no tail
tail_is_nonzero = false;
} else {
// exactly precision+1 digits were captured — use the post-multiply flag
tail_is_nonzero = non_zero_digits_left;
}
if (rounding_digit > '5') {
round_up = true;
} else if (rounding_digit < '5') {
round_up = false;
} else { // rounding_digit == 5
if (tail_is_nonzero) {
round_up = true;
} else {
Char prev;
if (precision == 0) { // No fraction digits, rounding baed on integer
// We have still printed one fraction digit in the (precision + 1)
// slot, which is why the (rounding_digit == 5) condition is hit.
prev = *str_int_end;
} else { // Fraction digit present, rounding based on them
prev = str_frc_begin[precision - 1];
}
Bool prev_is_odd = (prev - '0') & 1;
round_up = prev_is_odd;
}
}
if (round_up) {
Sint k = precision - 1; // -1 if precision == 0
while (k >= 0) { // Only run if there are fractional digits to print
if (str_frc_begin[k] < '9') { // Can be rounded up in place
str_frc_begin[k]++; // Round up
break; // and we are done
} else { // str_frc_begin[k] == '9'
debugAssert(str_frc_begin[k] == '9');
str_frc_begin[k] = '0'; // 9+1 = 10, so 0 here and 1 carries over
k--; // We need to apply the carry to the digit on the left
}
}
if (k < 0) { // Either no fractional digits, or 1 carried from fractional rounding
Char *int_digit = str_int_end;
while (int_digit >= str_int_begin) {
if (*int_digit < '9') {
(*int_digit)++;
break;
} else {
*int_digit = '0';
int_digit--;
}
}
if (int_digit < str_int_begin) { // One more carry
debugAssert(int_digit == (str_int_begin - 1));
rounding_up_added_a_new_integer_digit_on_the_left = true;
*int_digit = '1';
str_int_begin--;
}
}
}
str = str_frc_begin + precision;
}
}
}
len = cast_val(Uptr, str - num_str_ptr);
if (rounding_up_added_a_new_integer_digit_on_the_left) {
str = num_str_ptr;
} else {
str = num_str_ptr + 1;
len -= 1;
}
precision = 0;
} break;
case '%': {
str = num_str_ptr;
str[0] = '%';
len = 1;
precision = 0;
} break;
case '\0': {
// If the format string ends prematurely, we print
// whatever half-formed conversion specification came through;
// to do that, we decrement the pointer since we need to make
// sure that we don't end up copying the terminating null byte,
// since that will be put in the final output at the end. We
// also want to decrement since after the next fmt++ (at the
// end of the function), we want fmt to point to the null byte
// so that the priting will properly end.
fmt--;
goto nlib_print_unimplemented_feature;
} break;
default: { // unknown, just copy conversion specification
nlib_print_unimplemented_feature:
str = num_str_ptr;
while (format_pointer <= fmt) {
str[0] = format_pointer[0];
format_pointer++;
str++;
}
len = cast_val(Size, str - num_str_ptr);
str = num_str_ptr;
precision = 0;
} break;
}
Size head_size = head_index;
head_index = 0;
Size tail_size = tail_index;
tail_index = 0;
// get minimum_width=leading/trailing space, precision=leading zeros
if (cast_val(Size, precision) < len) {
precision = cast_val(Sint, len);
}
Sint zeros_head_tail = (precision +
cast_val(Sint, head_size) +
cast_val(Sint, tail_size));
if (minimum_width < zeros_head_tail) {
minimum_width = zeros_head_tail;
}
minimum_width -= zeros_head_tail;
precision -= cast_val(Sint, len);
// handle right justify and leading zeros
if ((flags & Print_Flags_LEFT_JUSTIFIED) == 0) {
if (flags & Print_Flags_LEADING_ZERO) { // then everything is in precision
precision = (minimum_width > precision) ? minimum_width : precision;
minimum_width = 0;
}
}
// copy leading spaces
if ((minimum_width + precision) > 0) {
// copy leading spaces (or when doing %8.4d stuff)
if ((flags & Print_Flags_LEFT_JUSTIFIED) == 0) {
for (Sint i = 0; i < minimum_width; i++) {
PRINT_ADD_CHAR(' ');
}
}
for (Size i = 0; i < head_size; i++) { // copy the head
PRINT_ADD_CHAR(head_str[i]);
}
head_index += head_size;
for (Sint i = 0; i < precision; i++) { // copy leading zeros
PRINT_ADD_CHAR('0');
}
}
if (head_index < head_size) { // copy the head
Size repeat = head_size - head_index;
for (Size i = 0; i < repeat; i++) {
PRINT_ADD_CHAR(head_str[i]);
}
head_index += repeat;
}
for (Size i = 0; i < len; i++) { // copy the string
PRINT_ADD_CHAR(str[i]);
}
str += len;
for (Size i = 0; i < tail_size; i++) { // copy the tail
PRINT_ADD_CHAR(tail_str[i]);
}
// handle the left justify
if (flags & Print_Flags_LEFT_JUSTIFIED) {
for (Sint i = 0; i < minimum_width; i++) {
PRINT_ADD_CHAR(' ');
}
}
fmt++;
} else if (fmt[0] =='\0') {
break;
}
}
if (buffer_index) {
callback(userdata, buffer, buffer_index, buffer_size);
}
#undef PRINT_ADD_CHAR
return total_size;
}
/**************************************************************************************
* Memory
*/
header_function
void* memoryAlignUpPtrTo (void *p, Size align)
{
Size k = align - 1LLU;
Size not_k = ~k;
Size up = cast_bit(Uptr, p) + k;
Size result_uptr = up & cast_val(Uptr, not_k);
void *result = cast_bit(void *, result_uptr);
return result;
}
header_function
Size memoryAlignUpSizeTo (Size s, Size align)
{
Size k = align - 1LLU;
Size result = (s + k) & (~ k);
return result;
}
header_function
void* memoryAlignDownPtrTo (void *p, Size align)
{
Byte *pb = cast_val(Byte *, p);
Size k = align - 1LLU;
Byte *result = cast_val(Byte *, memoryAlignUpPtrTo(pb - k, align));
return result;
}
header_function
Size memoryAlignDownSizeTo (Size s, Size align)
{
Size k = align - 1LLU;
Size result = memoryAlignUpSizeTo(s - k, align);
return result;
}
#if defined(ENV_LANG_C) && ENV_LANG_C >= 2011
# define memoryAlignUpTo(x, a) _Generic((x), void*: memoryAlignUpPtrTo, Size:memoryAlignUpSizeTo)(x, a)
# define memoryAlignDownTo(x, a) _Generic((x), void*: memoryAlignDownPtrTo, Size:memoryAlignDownSizeTo)(x, a)
#elif defined(ENV_LANG_CXX)
header_function void* memoryAlignUpTo (void *p, Size align) { return memoryAlignUpPtrTo(p, align); }
header_function Size memoryAlignUpTo (Size s, Size align) { return memoryAlignUpSizeTo(s, align); }
header_function void* memoryAlignDownTo (void *p, Size align) { return memoryAlignDownPtrTo(p, align); }
header_function Size memoryAlignDownTo (Size s, Size align) { return memoryAlignDownSizeTo(s, align); }
#endif
# define memoryAlignUp(x) (memoryAlignUpTo(x, alignof(max_align_t)))
# define memoryAlignDown(x) (memoryAlignDownTo(x, alignof(max_align_t)))
#define aligned_sizeof(x) memoryAlignUp(sizeof(x))
#define MEMORY_ALLOT_FUNC(name) void* name (void *userdata, Size amount)
#define MEMORY_REMIT_FUNC(name) void name (void *userdata, void *ptr)
/*************************************************************************************
* Free Grid
*/
/*
* row mask |‾‾‾‾‾‾‾‾‾‾side‾‾‾‾‾‾‾‾‾|
* lsb
* 1 FF FF FF FF FF ‾|
* 1 FF FF FF FF FF |
* 1 FF FF 07 00 00 side
* 0 00 00 00 00 00 |
* 0 00 00 00 00 00 _|
* msb
* 1 1 1 1 1 <- col mask
* lsb msb
*/
// TODO(naman): Here, the "correct" thing to do would have been to
// calculate the `side` variable using `sqrt(elem_count)`.
#define FG_SIDE 64u
// NOTE(naman): 2^18 is 64*64*64 (= 262144), and that is the maximum count we support
#define FREE_GRID_MAXIMUM_ELEMENTS (1U << 18)
typedef struct Free_Grid {
Uint64 masks[FG_SIDE * FG_SIDE * sizeof(Uint64)];
Uint8 count_masks_in_row[FG_SIDE];
Uint8 count_masks_in_col[FG_SIDE];
Uint64 row_mask;
Uint64 col_mask;
Uint32 count;
Uint32 allocated;
} Free_Grid;
header_function
void fgCreate (Free_Grid *fg, Uint32 elem_count)
{
debugAssert(elem_count <= FREE_GRID_MAXIMUM_ELEMENTS);
memzero(fg);
fg->count = elem_count;
Uint32 fully_marked_mask_count = elem_count / 64;
Uint32 extra_marked_bits_count = elem_count % 64;
if (fully_marked_mask_count) memset(fg->masks, 0xFF, fully_marked_mask_count * sizeof(Uint64));
if (extra_marked_bits_count) {
fg->masks[fully_marked_mask_count] |= ((~cast_val(Uint64, 0x0ull)) >> (64 - extra_marked_bits_count));
}
Uint32 total_marked_masks = fully_marked_mask_count + (extra_marked_bits_count ? 1 : 0);
Uint32 fully_filled_rows = total_marked_masks / FG_SIDE;
Uint8 partially_filled_row_size = total_marked_masks % FG_SIDE;
Uint32 total_rows = fully_filled_rows + (partially_filled_row_size ? 1 : 0);
fg->row_mask = ((~cast_val(Uint64, 0x0ull)) >> (64 - total_rows));
Uint32 col_bits_count = fully_filled_rows ? FG_SIDE : partially_filled_row_size;
fg->col_mask = ((~cast_val(Uint64, 0x0ull)) >> (64 - minimum(FG_SIDE, col_bits_count)));
for (Size i = 0; i < fully_filled_rows; i++) {
fg->count_masks_in_row[i] = FG_SIDE;
}
if (partially_filled_row_size) {
pragma_msvc("warning ( push )");
pragma_msvc("warning ( disable: 6386 )"); // Analyze: WRITE_OVERRUN (Buffer overrun while writing to 'fg->count_masks_in_row')
fg->count_masks_in_row[fully_filled_rows] = partially_filled_row_size;
pragma_msvc("warning ( pop )");
}
for (Size i = 0; i < FG_SIDE; i++) {
fg->count_masks_in_col[i] = cast_val(Uint8, fully_filled_rows);
if (partially_filled_row_size && (i < partially_filled_row_size)) {
fg->count_masks_in_col[i]++;
}
}
return;
}
header_function
void fgDelete (Free_Grid *fg)
{
unused_variable(fg);
}
header_function
Bool fgAllocate (Free_Grid *fg, Uint32 *index)
{
Uint8 row_first_bit_index = bitLSBU64(fg->row_mask);
if (row_first_bit_index == cast_val(Uint8, -1)) {
return false;
}
debugAssert(row_first_bit_index < 64);
Uint8 col_first_bit_index = bitLSBU64(fg->col_mask);
debugAssert(col_first_bit_index != cast_val(Uint8, -1));
while (!fg->masks[(row_first_bit_index * FG_SIDE) + col_first_bit_index]) {
col_first_bit_index++;
}
debugAssert(col_first_bit_index < 64);
Uint32 mask_array_index = row_first_bit_index * FG_SIDE + col_first_bit_index;
debugAssert(mask_array_index < (64*64));
debugAssert(fg->masks[mask_array_index]);
Uint8 mask_first_bit_index = bitLSBU64(fg->masks[mask_array_index]);
debugAssert(mask_first_bit_index != cast_val(Uint8, -1));
*index = mask_array_index * 64 + cast_val(Uint32, mask_first_bit_index);
debugAssert(*index < fg->count);
fg->masks[mask_array_index] &= ~(1ULL << mask_first_bit_index);
if (fg->masks[mask_array_index] == 0) {
fg->count_masks_in_row[row_first_bit_index]--;
fg->count_masks_in_col[col_first_bit_index]--;
if (fg->count_masks_in_row[row_first_bit_index] == 0) {
fg->row_mask &= ~(1ULL << row_first_bit_index);
}
if (fg->count_masks_in_col[col_first_bit_index] == 0) {
fg->col_mask &= ~(1ULL << col_first_bit_index);
}
}
fg->allocated++;
return true;
}
header_function
void fgFree (Free_Grid *fg, Uint32 index)
{
debugAssert(index < fg->count);
Uint32 fully_traversed_mask_index = (index + 1) / 64;
Uint32 extra_untreaded_bits_count = (index + 1) % 64;
Uint32 total_relevant_mask_count = fully_traversed_mask_index + (extra_untreaded_bits_count ? 1 : 0);
Uint32 final_relevant_mask_index = total_relevant_mask_count - 1;
Uint32 row_bit_index = final_relevant_mask_index / FG_SIDE;
Uint32 col_bit_index = final_relevant_mask_index - (row_bit_index * FG_SIDE);
Uint32 relevant_bit_index;
if (extra_untreaded_bits_count) {
relevant_bit_index = extra_untreaded_bits_count - 1;
} else {
relevant_bit_index = 63;
}
debugAssert((fg->masks[final_relevant_mask_index] & (1ULL << relevant_bit_index)) == 0);
Uint64 old_mask = fg->masks[final_relevant_mask_index];
fg->masks[final_relevant_mask_index] |= (1ULL << relevant_bit_index);
if (old_mask == 0) {
fg->count_masks_in_row[row_bit_index]++;
fg->count_masks_in_col[col_bit_index]++;
fg->row_mask |= (1ULL << row_bit_index);
fg->col_mask |= (1ULL << col_bit_index);
}
}
#undef FG_SIZE
/************************************************************************************************
* TLSF Allocator
* --------------
*
* The following is a sample table with buffer_size = GiB(4), min_alloc = MiB(1) and increment_step = 8
* Each row is primary level, where the buffer is divided into units of size being consecutive powers of two
* (geometric progression). Each column them breaks each unit into a linear sub-unit of size being a constant
* increment (arithmetic progression).
*
* ________________________________________________________________________________________________________________________
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
* |----|-------|---------------|--------------|--------------|--------------|--------------|--------------|--------------|
* | 31 | 2G | 2G+256M | 2G+512M | 2G+768M | 3G | 3G+256M | 3G+512M | 3G+768M |
* | 30 | 1G | 1G+128M | 1G+256M | 1G+384M | 1G+512M | 1G+640M | 1G+768M | 1G+896M |
* | 29 | 512M | 576M | 640M | 704M | 768M | 832M | 896M | 960M |
* | 28 | 256M | 288M | 320M | 352M | 384M | 416M | 448M | 480M |
* | 27 | 128M | 144M | 160M | 176M | 192M | 208M | 224M | 240M |
* | 26 | 64M | 72M | 80M | 88M | 96M | 104M | 112M | 120M |
* | 25 | 32M | 36M | 40M | 44M | 48M | 52M | 56M | 60M |
* | 24 | 16M | 18M | 20M | 22M | 24M | 26M | 28M | 30M |
* | 23 | 8M | 9M | 10M | 11M | 12M | 13M | 14M | 15M |
* | 22 | 4M | 4M+512K | 5M | 5M+512K | 6M | 6M+512K | 7M | 7M+512K |
* | 21 | 2M | 2M+256K | 2M+512K | 2M+768K | 3M | 3M+256K | 3M+512K | 3M+768K |
* | 20 | 1M | 1M+128K | 1M+256K | 1M+384K | 1M+512K | 1M+640K | 1M+768K | 1M+896K |
* |------------|---------------|--------------|--------------|--------------|--------------|--------------|--------------|
*
* Reference:
* [1] : http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf
* [2] : http://www.gii.upv.es/tlsf/files/papers/ecrts04_tlsf.pdf
* [3] : http://www.gii.upv.es/tlsf/files/papers/jrts2008.pdf
*/
// TODO(naman): Right now, this is 64-bytes large. Once I am sure that the allocator is bug-free (i.e., once it has been
// tested enough), rename `next_slot_available_for_dispensing` and `next_free` to `next` and `prev_free` to `prev`, since a slot can either
// be empty or house a free node but not both. Also, store the `allocated` bool in the top bit of `size`. That will drop the
// size of the struct to 48-bytes, reducing the metadata size by close to 25%.
typedef struct TLSF_Node {
Uint64 offset;
Uint64 size;
// Contains the pointer to the next slot from which a node can be dispensed in case of out-of-band allocation
struct TLSF_Node *next_slot_available_for_dispensing;
// For free blocks. The next member of the node contains the index of the next node in chain corresponding to another
// free block in the same size class. The index of chain's head is stored in the free_table.
struct TLSF_Node *next_free, *prev_free;
// For allocated and free blocks. The following members contain the index of the nodes associated with the blocks right
// after and before them in the address space.
struct TLSF_Node *next_mem, *prev_mem;
Bool allocated; // false = free (make sure it's not left true for empty slots)
Byte _pad[7];
} TLSF_Node;
static_assert(sizeof(TLSF_Node) % alignof(max_align_t) == 0, \
"TLSF Nodes need to be of the same size as alignment, " \
"so that they can be packed in the node_dispenser, etc.");
#define TLSF_INCREMENT_COUNT_PER_ROW 16
typedef struct TLSF {
MEMORY_ALLOT_FUNC((*m_allot));
MEMORY_REMIT_FUNC((*m_remit));
void *m_datum;
Uint64 buffer_size;
Uint64 min_alloc;
Uint32 rows, columns;
// One bit for each row, to show if any bit in the corresponding row_bitmap is set.
// Bitmap's LSB->MSB is bottom->top row
Uint64 column_bitap;
// One bitmap per row, with each bit standing for one cell.
// Index 0->N maps to bottom->top row, bitmap's LSB->MSB is left->right cell
Uint64 row_bitmaps[64];
TLSF_Node* free_table[64][TLSF_INCREMENT_COUNT_PER_ROW];
union { // Both point to TLSF->data
Byte *memory; // In-place memory allocator
TLSF_Node *node_dispenser; // Out-of-band 64-bit address space allocator
};
TLSF_Node *first_slot_available_for_dispensing; // If this is NULL, we are dealing with in-place memory allocator
Byte data[];
} TLSF;
static_assert(sizeof(TLSF) % alignof(max_align_t) == 0,
"TLSF's size is not divisible by alignof(max_align_t), thus data[] member will be out of alignment");
#define TLSF_HEAD_OF_FREE_LIST_(t, r, c) ((t)->free_table[(r)][(c)])
// sl = log2(size)
// ml = log2(min_alloc)
// il = log2(increment)
//
// r = sl - ml
// c = (size - 2^sl) / ((2*2^sl - 2^sl)/2^il)
// = (size - 2^sl) / (2^sl*(2 - 1) / 2^il)
// = (size - 2^sl) / (2^sl / 2^il)
// = (size - 2^sl) / (2^(sl-il))
// = (size/2^(sl-il)) - (2^sl-(2^(sl-il)))
// = (size/2^(sl-il)) - 2^il
// = (size >> (sl-il)) - 2^il
// = (size >> (sl-il)) - (1 << il)
#define TLSF_ROW_(t, s) cast_val(Uint8, (bitLog2U64(s) - bitLog2U64((t)->min_alloc)))
#define TLSF_COL_(t, s) cast_val(Uint8, (((s) >> (bitLog2U64(s) - bitLog2U64(cast_val(Uint64, ((t)->columns))))) - \
(1ULL << bitLog2U64(cast_val(Uint64, ((t)->columns))))))
// NOTE(naman): All three parameters should be powers of two with value >= 2.
// buffer_size should be greater than min_alloc
// NOTE(naman): With (buffer_size=512 MiB, min_alloc=32 KiB), it will need slightly more than 1 MiB of metadata.
// WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to min_alloc
// before calling this function.
#define tlsfCreate(_miface, _bufsz, _minal) tlsfCreateEx(_miface, _bufsz, _minal, true)
header_function
TLSF* tlsfCreateEx (MEMORY_ALLOT_FUNC((*m_allot)), MEMORY_REMIT_FUNC((*m_remit)), void *m_datum,
Uint64 buffer_size, Uint64 min_alloc,
Bool out_of_band_metadata)
{
Uint8 increment_steps = 16;
Uint8 buffer_size_bits = bitLog2U64(buffer_size);
Uint8 min_alloc_bits = bitLog2U64(min_alloc);
Uint8 row_count = cast_val(Uint8, buffer_size_bits - min_alloc_bits);
Size maximum_allocation_count = buffer_size / min_alloc;
Size total_size = aligned_sizeof(TLSF);
if (out_of_band_metadata) {
Size list_size = maximum_allocation_count * sizeof(TLSF_Node);
total_size += memoryAlignUp(list_size);
} else {
total_size += memoryAlignUp(buffer_size);
}
TLSF *tlsf = cast_val(TLSF *, m_allot(m_datum, total_size));
memzero(tlsf);
tlsf->m_allot = m_allot;
tlsf->m_remit = m_remit;
tlsf->m_datum = m_datum;
tlsf->buffer_size = buffer_size;
tlsf->min_alloc = min_alloc;
tlsf->rows = row_count;
tlsf->columns = increment_steps;
if (out_of_band_metadata) {
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wcast-align\"");
tlsf->node_dispenser = cast_ptr(TLSF_Node *, tlsf->data);
pragma_clang("clang diagnostic pop");
tlsf->first_slot_available_for_dispensing = &tlsf->node_dispenser[0];
// Put all available nodes in a list so that we can find the next one in O(1) for real-time
for (Size i = 0; i < maximum_allocation_count-1; i++) {
memzero(&tlsf->node_dispenser[i]);
tlsf->node_dispenser[i].next_slot_available_for_dispensing = &tlsf->node_dispenser[i] + 1;
}
} else {
asan_block(tlsf->data, buffer_size);
tlsf->memory = tlsf->data;
tlsf->first_slot_available_for_dispensing = nullptr;
}
// This macro is defined here since it only becomes valid from here
// (one first_slot_available_for_dispensing has been set)
# define TLSF_OOB_(t) ((t)->first_slot_available_for_dispensing)
{ // Adding the full buffer in free_table
TLSF_Node node;
memzero(&node);
node.offset = 0;
node.size = buffer_size - 1;
Uint8 row = TLSF_ROW_(tlsf, node.size);
Uint8 col = TLSF_COL_(tlsf, node.size);
TLSF_Node *slot_for_full_buffer = nullptr;
if (out_of_band_metadata) {
slot_for_full_buffer = tlsf->first_slot_available_for_dispensing;
tlsf->first_slot_available_for_dispensing = slot_for_full_buffer->next_slot_available_for_dispensing;
} else {
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wcast-align\"");
slot_for_full_buffer = cast_ptr(TLSF_Node *, tlsf->memory);
debugAssert(cast_ptr(Uptr, slot_for_full_buffer) % alignof(max_align_t) == 0);
pragma_clang("clang diagnostic pop");
}
if (!TLSF_OOB_(tlsf)) asan_allow(slot_for_full_buffer, sizeof(*slot_for_full_buffer));
*slot_for_full_buffer = node;
if (!TLSF_OOB_(tlsf)) asan_block(slot_for_full_buffer, sizeof(*slot_for_full_buffer));
TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = slot_for_full_buffer;
tlsf->column_bitap |= (1ULL << row);
tlsf->row_bitmaps[row] |= (1ULL << col);
}
return tlsf;
}
header_function
void tlsfDelete (TLSF *tlsf)
{
MEMORY_REMIT_FUNC((*m_remit)) = tlsf->m_remit;
void *m_datum = tlsf->m_datum;
m_remit(m_datum, tlsf);
}
header_function
void tlsf_AddNodeToFreeList (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
Size row = TLSF_ROW_(tlsf, node->size); // fl in [1]
Size col = TLSF_COL_(tlsf, node->size); // sl in [1]
// Add node to free list
node->prev_free = nullptr;
node->next_free = TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col);
if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col)) {
TLSF_Node *head = TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col);
if (!TLSF_OOB_(tlsf)) asan_allow(head, sizeof(*head));
head->prev_free = node;
if (!TLSF_OOB_(tlsf)) asan_block(head, sizeof(*head));
}
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
// Add node to table
TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = node;
// Update bitmap
tlsf->row_bitmaps[row] |= (1ULL << col);
tlsf->column_bitap |= (1ULL << row);
}
header_function
void tlsf_RemoveNodeFromFreeList (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
Size row = TLSF_ROW_(tlsf, node->size); // fl in [1]
Size col = TLSF_COL_(tlsf, node->size); // sl in [1]
// Remove node from the free list
if (node->next_free) {
if (!TLSF_OOB_(tlsf)) asan_allow(node->next_free, sizeof(*node->next_free));
node->next_free->prev_free = node->prev_free;
if (!TLSF_OOB_(tlsf)) asan_block(node->next_free, sizeof(*node->next_free));
}
if (node->prev_free) {
if (!TLSF_OOB_(tlsf)) asan_allow(node->prev_free, sizeof(*node->prev_free));
node->prev_free->next_free = node->next_free;
if (!TLSF_OOB_(tlsf)) asan_block(node->prev_free, sizeof(*node->prev_free));
}
// Remove node from table
if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) == node) {
TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) = node->next_free;
}
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
// Update bitmap
if (TLSF_HEAD_OF_FREE_LIST_(tlsf, row, col) == nullptr) { // No free blocks left in that size range
tlsf->row_bitmaps[row] &= ~(1ULL << col);
if (tlsf->row_bitmaps[row] == 0) {
tlsf->column_bitap &= ~(1ULL << row);
}
}
}
header_function
TLSF_Node* tlsf_AcquireNode (TLSF *tlsf, Size offset)
{
TLSF_Node *result;
// Find a empty slot in list and put the splitted node in it
if (TLSF_OOB_(tlsf)) {
result = tlsf->first_slot_available_for_dispensing;
tlsf->first_slot_available_for_dispensing = result->next_slot_available_for_dispensing;
} else {
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wcast-align\"");
result = cast_ptr(TLSF_Node *, &tlsf->data[offset]);
debugAssert(cast_ptr(Uptr, result) % alignof(max_align_t) == 0);
pragma_clang("clang diagnostic pop");
}
if (!TLSF_OOB_(tlsf)) asan_allow(result, sizeof(*result));
memzero(result);
if (!TLSF_OOB_(tlsf)) asan_block(result, sizeof(*result));
return result;
}
header_function
void tlsf_ReleaseNode (TLSF *tlsf, TLSF_Node *node)
{
if (TLSF_OOB_(tlsf)) { // Add node into the slot dispenser
node->next_slot_available_for_dispensing = tlsf->first_slot_available_for_dispensing;
tlsf->first_slot_available_for_dispensing = node;
}
}
// WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested
// before calling this function.
header_function
TLSF_Node* tlsfAllocate (TLSF *tlsf, Size size_requested)
{
Size size = memoryAlignUp(maximum(size_requested, tlsf->min_alloc));
if (size > tlsf->buffer_size) return nullptr;
// The next slot for size (so that we do a loop-less good fit for real-time instead of a looped best fit)
// sl = log2(size)
// il = log2(increment)
//
// gap = size_of_each_column_in_that_row = ((2*2^sl - 2^sl)/2^il) = 2^(sl-il)
// size_next = size + 2^(sl-il)
// However, in case of size = 2^sl, we don't want to go to the next block. Thus, we should subtract 1 from size_next,
// so that we end up back in the same table slot in that particular case.
// => size_next = size + 2^(sl-il) - 1
Size size_class = size + (1ULL << (bitLog2U64(size) - bitLog2U64(cast_val(Uint64, tlsf->columns)))) - 1;
size_class = minimum(size_class, tlsf->buffer_size-1);
Size row = TLSF_ROW_(tlsf, size_class); // fl in [1]
Size col = TLSF_COL_(tlsf, size_class); // sl in [1]
Uint64 row_bitmap = tlsf->row_bitmaps[row] & (0xFFFFFFFFFFFFFFFFULL << col);
Size row_available, col_available;
if (row_bitmap) {
col_available = bitLSBU64(row_bitmap);
row_available = row;
} else {
Uint64 col_bitmap = tlsf->column_bitap & (0xFFFFFFFFFFFFFFFFULL << (row + 1));
if (col_bitmap) {
row_available = bitLSBU64(col_bitmap);
col_available = bitLSBU64(tlsf->row_bitmaps[row_available]);
} else {
return nullptr;
}
}
// Get the free node of the free list and mark it used
TLSF_Node *free_node = TLSF_HEAD_OF_FREE_LIST_(tlsf, row_available, col_available); // This will definitely succeed, so free_node != nullptr
// WARN(naman): Don't zero out free_node. Some of it's contents are used below and some are carried forward.
tlsf_RemoveNodeFromFreeList(tlsf, free_node);
TLSF_Node *split_node_slot = nullptr;
if (!TLSF_OOB_(tlsf)) asan_allow(free_node, sizeof(*free_node));
if (free_node->size >= (size + tlsf->min_alloc)) { // If the acquired block is too big
Size split_node_size = free_node->size - size;
Size split_offset = free_node->offset + size;
split_node_slot = tlsf_AcquireNode(tlsf, split_offset);
if (!TLSF_OOB_(tlsf)) asan_allow(split_node_slot, sizeof(*split_node_slot));
split_node_slot->offset = split_offset;
split_node_slot->size = split_node_size;
split_node_slot->prev_mem = free_node;
split_node_slot->next_mem = free_node->next_mem;
if (!TLSF_OOB_(tlsf)) asan_block(split_node_slot, sizeof(*split_node_slot));
tlsf_AddNodeToFreeList(tlsf, split_node_slot);
}
{ // Create a node for the allocated block
// free_node->offset = offset; // Already set
free_node->size = size;
free_node->next_slot_available_for_dispensing = nullptr;
free_node->next_free = nullptr;
free_node->prev_free = nullptr;
free_node->next_mem = split_node_slot;
// free_node->prev_mem = prev_mem; // Already set
free_node->allocated = true;
}
if (!TLSF_OOB_(tlsf)) asan_block(free_node, sizeof(*free_node));
return free_node;
}
header_function
TLSF_Node* tlsf_MergeLeft (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
TLSF_Node *left = node->prev_mem;
if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left));
if (left && !left->allocated) {
Size node_size_old = node->size;
Size left_size_old = left->size;
Size left_size_new = node_size_old + left_size_old;
tlsf_RemoveNodeFromFreeList(tlsf, left);
if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left));
{ // Remove node from the memory list
left->next_mem = node->next_mem;
if (left->next_mem) {
if (!TLSF_OOB_(tlsf)) asan_allow(left->next_mem, sizeof(*left->next_mem));
left->next_mem->prev_mem = left;
if (!TLSF_OOB_(tlsf)) asan_block(left->next_mem, sizeof(*left->next_mem));
}
}
tlsf_ReleaseNode(tlsf, node);
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
if (!node->allocated) { // If the node to be expanded is free (e.g., during deallocation)
tlsf_RemoveNodeFromFreeList(tlsf, node);
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
left->size = left_size_new; // Updated here, so that tlsf_AddNodeToFreeList below works
tlsf_AddNodeToFreeList(tlsf, left);
if (!TLSF_OOB_(tlsf)) asan_allow(left, sizeof(*left));
} else {
left->allocated = true;
node->allocated = false;
left->size = left_size_new; // Still has to be updated if node->allocated == true
}
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
if (!TLSF_OOB_(tlsf)) asan_block(left, sizeof(*left));
return left;
} else {
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
if (!TLSF_OOB_(tlsf)) asan_block(left, sizeof(*left));
return node;
}
}
header_function
TLSF_Node* tlsf_MergeRight (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
TLSF_Node *right = node->next_mem;
if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right));
if (right && !right->allocated) {
Size node_size_old = node->size;
Size right_size_old = right->size;
Size node_size_new = node_size_old + right_size_old;
tlsf_RemoveNodeFromFreeList(tlsf, right);
if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right));
{ // Remove right from the memory list
node->next_mem = right->next_mem;
if (node->next_mem) {
if (!TLSF_OOB_(tlsf)) asan_allow(node->next_mem, sizeof(*node->next_mem));
node->next_mem->prev_mem = node;
if (!TLSF_OOB_(tlsf)) asan_block(node->next_mem, sizeof(*node->next_mem));
}
}
tlsf_ReleaseNode(tlsf, right);
if (!TLSF_OOB_(tlsf)) asan_allow(right, sizeof(*right));
if (!node->allocated) { // If the node to be expanded is free (e.g., during deallocation)
tlsf_RemoveNodeFromFreeList(tlsf, node);
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
node->size = node_size_new; // Updated here, so that tlsf_AddNodeToFreeList below works
tlsf_AddNodeToFreeList(tlsf, node);
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
} else {
node->size = node_size_new; // Still has to be updated if node->allocated == true
}
}
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
if (!TLSF_OOB_(tlsf)) asan_block(right, sizeof(*right));
return node;
}
// WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested
// before calling this function.
header_function
Bool tlsfAdjustLeft (TLSF *tlsf, TLSF_Node **node, Size new_size)
{
if (!(*node)->allocated) return false;
*node = tlsf_MergeLeft(tlsf, *node);
new_size = memoryAlignUp(maximum(new_size, tlsf->min_alloc));
if ((*node)->size < new_size) return false;
Size remnant_offset = (*node)->offset;
Size remnant_size = (*node)->size - new_size;
if (remnant_size == 0) return true;
if (remnant_size < tlsf->min_alloc) return true;
{ // Add a remnnant node
TLSF_Node *remnant = tlsf_AcquireNode(tlsf, remnant_offset);
// Size it properly
remnant->offset = remnant_offset;
remnant->size = remnant_size;
(*node)->offset += remnant->size;
(*node)->size = new_size;
// Add to memory list
remnant->next_mem = *node;
remnant->prev_mem = (*node)->prev_mem;
if ((*node)->prev_mem) (*node)->prev_mem->next_mem = remnant;
(*node)->prev_mem = remnant;
tlsf_AddNodeToFreeList(tlsf, remnant);
}
return true;
}
// WARN(naman): When using with in-band memory, make sure to add the sizeof(TLSF_Node) to size_requested
// before calling this function.
header_function
Bool tlsfAdjustRight (TLSF *tlsf, TLSF_Node **node, Size new_size)
{
if (!(*node)->allocated) return false;
*node = tlsf_MergeRight(tlsf, *node);
new_size = memoryAlignUp(maximum(new_size, tlsf->min_alloc));
if ((*node)->size < new_size) return false;
Size remnant_offset = (*node)->offset + new_size;
Size remnant_size = (*node)->size - new_size;
if (remnant_size == 0) return true;
if (remnant_size < tlsf->min_alloc) return true;
{ // Add a remnnant node
TLSF_Node *remnant = tlsf_AcquireNode(tlsf, remnant_offset);
// Size it properly
remnant->offset = remnant_offset;
remnant->size = remnant_size;
// node->offset doesn't change
(*node)->size = new_size;
// Add to memory list
remnant->prev_mem = *node;
remnant->next_mem = (*node)->next_mem;
if ((*node)->next_mem) (*node)->next_mem->prev_mem = remnant;
(*node)->next_mem = remnant;
tlsf_AddNodeToFreeList(tlsf, remnant);
}
return true;
}
header_function
Bool tlsfFree (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
if (!node->allocated) return false;
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
node = tlsf_MergeLeft(tlsf, node);
node = tlsf_MergeRight(tlsf, node);
tlsf_AddNodeToFreeList(tlsf, node);
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
node->allocated = false;
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
return true;
}
header_function
Size tlsfGetSize (TLSF *tlsf, TLSF_Node *node)
{
if (!TLSF_OOB_(tlsf)) asan_allow(node, sizeof(*node));
if (!node->allocated) return 0;
Size size = node->size;
if (!TLSF_OOB_(tlsf)) asan_block(node, sizeof(*node));
return size;
}
/************************************************************************************************
* TLSF-based General Purpose Memory Allocator
*/
// TODO(naman): Add support for multiple allocation buffers, so that when one buffer is filled up,
// we allocate a new one to parcel new allocations out of and keep going. This will probably require
// having multiple TLSF pointers
typedef struct Memory {
TLSF *tlsf;
Size max_mem;
} Memory;
// NOTE(naman): With (512 MiB, 32 KiB, 16), it will need slightly more than 1 MiB of metadata.
header_function
Memory memCreate (MEMORY_ALLOT_FUNC((*m_allot)), MEMORY_REMIT_FUNC((*m_remit)), void *m_datum,
Size memory_size)
{
Size max_mem = memory_size;
Size min_mem = bitNextPowerOf2((2 * sizeof(TLSF_Node)) - 1); // To get atleast 50% load
debugAssert(max_mem > min_mem);
debugAssert(bitIsPowerOf2(max_mem));
debugAssert(bitIsPowerOf2(min_mem));
TLSF *tlsf = tlsfCreateEx(m_allot, m_remit, m_datum, max_mem, min_mem, false);
Memory memory = {
.tlsf = tlsf,
.max_mem = max_mem,
};
return memory;
}
header_function
void memDelete (Memory *mt)
{
tlsfDelete(mt->tlsf);
}
header_function
void* memAllot (Memory *mt, Size size)
{
debugAssert(size);
Size real_size = size + sizeof(TLSF_Node);
TLSF_Node *node = tlsfAllocate(mt->tlsf, real_size);
Byte *result = cast_ptr(Byte *, node + 1);
asan_allow(result, size);
memset(result, 0, size);
return result;
}
// Return nullptr if extention failed, so that the application knows that more memory is not available
header_function
void* memExtend (Memory *mt, void *ptr, Size size)
{
TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1;
Size real_size = size + sizeof(TLSF_Node);
Bool adjusted = tlsfAdjustLeft(mt->tlsf, &node, real_size);
Byte *result = cast_ptr(Byte *, node + 1);
return adjusted ? result : nullptr;
}
header_function
void memRemit (Memory *mt, void *ptr)
{
debugAssert(ptr);
TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1;
Size size = tlsfGetSize(mt->tlsf, node);
asan_block(ptr, size);
tlsfFree(mt->tlsf, node);
}
header_function
Size memGetSize (Memory *mt, void *ptr)
{
TLSF_Node *node = cast_ptr(TLSF_Node *, ptr) - 1;
asan_allow(node, sizeof(*node));
Size size = tlsfGetSize(mt->tlsf, node);
asan_block(node, sizeof(*node));
return size;
}
/*****************************************************************************************
* Memory Push Allocator
*/
struct Memory_Push_Block_ {
struct Memory_Push_Block_ *next;
Size cap;
Size len;
Byte _pad[8];
Byte data[];
};
static_assert(offsetof(struct Memory_Push_Block_, data) % alignof(max_align_t) == 0, "The data member has to be properly aligned");
typedef struct Memory_Push {
Memory *parent_memory;
struct Memory_Push_Block_ *first, *last;
Size size;
} Memory_Push;
header_function
Size memPush_PaddingForAlignment (void *ptr, Size offset, Size alignment)
{
Uptr old_location = cast_bit(Uptr, ptr) + offset;
if (old_location % alignment) {
Uptr next_location = old_location + alignment;
Uptr align_location = (next_location/alignment)*alignment;
Uptr diff = align_location - old_location;
return diff;
}
return 0;
}
header_function
struct Memory_Push_Block_* memPush_AcquireMemory (Memory_Push *mp, Size size)
{
Size size_total = sizeof(struct Memory_Push_Block_) + size;
struct Memory_Push_Block_ *block = cast_ptr(struct Memory_Push_Block_ *,
memAllot(mp->parent_memory, size_total));
memzero(block);
block->cap = size;
block->len = 0;
asan_block(block->data, block->cap);
return block;
}
header_function
void memPushCreate (Memory_Push *mp, Memory *parent_memory, Size size)
{
memzero(mp);
mp->parent_memory = parent_memory;
mp->size = size;
mp->first = memPush_AcquireMemory(mp, size);
mp->last = mp->first;
memzero(mp->first);
mp->first->cap = size;
}
header_function
void memPushDelete (Memory_Push *push)
{
for (struct Memory_Push_Block_ *mpb = push->first, *mpbn; mpb != nullptr; mpb = mpbn) {
mpbn = mpb->next;
asan_block(mpb->data, mpb->cap);
memRemit(push->parent_memory, mpb);
}
memzero(push);
}
header_function
void* memPushAllotA (Memory_Push *push, Size size, Size alignment)
{
debugAssert(size);
Size pad = memPush_PaddingForAlignment(push->last->data, push->last->len, alignment);
if ((push->last->len + pad + size) > push->last->cap) {
if (push->last->next == nullptr) {
Size s = maximum(push->size, size) + alignment;
push->last->next = memPush_AcquireMemory(push, s);
push->last->next->cap = s;
}
push->last = push->last->next;
}
pad = memPush_PaddingForAlignment(push->last->data, push->last->len, alignment);
push->last->len += pad;
void *m = push->last->data + push->last->len;
push->last->len += size;
asan_allow(m, size);
memset(m, 0, size);
return m;
}
header_function
void* memPushAllot (Memory_Push *push, Size size)
{
return memPushAllotA(push, size, alignof(max_align_t));
}
header_function
void memPushRemitAll (Memory_Push *push)
{
for (struct Memory_Push_Block_ *mpb = push->first, *mpbn; mpb != nullptr; mpb = mpbn) {
mpbn = mpb->next;
asan_block(mpb->data, mpb->cap);
mpb->len = 0;
}
push->last = push->first;
}
/************************************************************************************************
* Memory Slab Allocator
*/
typedef struct Memory_Slab {
Free_Grid *fg;
Memory *parent_memory;
void *buffer;
Size memory_size;
Size elem_size;
Size elem_count;
Size elem_allocated;
} Memory_Slab;
header_function
void memSlabCreate (Memory_Slab *s, Memory *parent_memory, Uint32 elem_count, Size elem_size)
{
memzero(s);
s->parent_memory = parent_memory;
s->elem_count = elem_count;
s->elem_size = elem_size;
s->memory_size = elem_size * elem_count;
s->fg = cast_val(Free_Grid*, memAllot(s->parent_memory, sizeof(*s->fg)));
fgCreate(s->fg, elem_count);
s->buffer = memAllot(s->parent_memory, s->memory_size);
asan_block(s->buffer, s->memory_size);
}
header_function
void memSlabDelete (Memory_Slab *s)
{
asan_allow(s->buffer, s->memory_size);
memRemit(s->parent_memory, s->buffer);
fgDelete(s->fg);
}
header_function
Size memSlabGetIndexOfPtr (Memory_Slab *s, void *m)
{
Uptr offset = cast_bit(Uptr, m) - cast_bit(Uptr, s->buffer);
Uptr index = offset / s->elem_size;
return index;
}
header_function
void* memSlabGetPtrOfIndex (Memory_Slab *s, Size index)
{
void *result = cast_val(Byte *, s->buffer) + (index * s->elem_size);
return result;
}
header_function
void* memSlabAllot (Memory_Slab *s)
{
debugAssert(s->elem_allocated < s->elem_count);
Uint32 index = 0;
debugAssert(fgAllocate(s->fg, &index));
void *result = memSlabGetPtrOfIndex(s, index);
asan_allow(result, s->elem_size);
memset(result, 0, s->elem_size);
s->elem_allocated++;
return result;
}
header_function
void memSlabRemit (Memory_Slab *s, void *m)
{
debugAssert(m);
debugAssert(s->elem_allocated);
asan_block(m, s->elem_size);
Uint32 index = cast_val(Uint32, (cast_val(Dptr, (cast_val(Byte *, m) - cast_val(Byte *, s->buffer)))/cast_val(Dptr, s->elem_size)));
fgFree(s->fg, index);
s->elem_allocated--;
}
/************************************************************************************************
* Hashing
* *******
*/
// https://en.wikipedia.org/wiki/Pearson_hashing
header_function
Uint8 hash8 (const void *data, Size len, Uint8 seed)
{
const Byte *d = cast_val(const Byte*, data);
Uint8 H = seed;
for (Size i = 0; i < len; i++) {
const Uint8 A = cast_val(Uint8, H ^ d[i]);
// Permutation function generated with https://programming.sirrida.de/calcperm.php using input "4 0 3 7 5 2 1 6"
const Uint8 B = cast_val(Uint8, (((A & 0x41) << 1) |
((A & 0x04) << 3) |
((A & 0x02) << 5) |
((A & 0x90) >> 4) |
((A & 0x28) >> 1)));
H = B;
}
return H;
}
// Reference: Fast CRCs by Gam D. Nguyen (https://arxiv.org/pdf/1009.5949) [Fig. 8, Page 14]
header_function
Uint16 hash16 (const void *data, Size len, Uint16 seed)
{
const Byte *d = cast_val(const Byte*, data);
const Uint16 f = 0x7;
const Uint16 k = 0x8000;
Uint16 CRC = seed;
Size i;
// Run until zero or one bytes are left
for (i = 0; (i + 2) <= len; i = i + 2) {
const Uint16 hword = load16BE(d + i);
Uint16 a = cast_val(Uint16, CRC ^ hword);
Uint16 c;
if (a & k) {
c = cast_val(Uint16, (a << 1) ^ f);
} else {
c = cast_val(Uint16, a << 1);
}
if (c & k) {
CRC = cast_val(Uint16, (c << 1) ^ f);
} else {
CRC = cast_val(Uint16, c << 1);
}
CRC = cast_val(Uint16, CRC ^ c ^ a);
}
// Finish up the last byte
if ((i + 1) == len) {
const Uint16 hword = cast_val(Uint16, cast_val(Uint16, load8(d + i)) << 8);
Uint16 a = cast_val(Uint16, CRC ^ hword);
Uint16 c;
if (a & k) {
c = cast_val(Uint16, (a << 1) ^ f);
} else {
c = cast_val(Uint16, a << 1);
}
if (c & k) {
CRC = cast_val(Uint16, (c << 1) ^ f);
} else {
CRC = cast_val(Uint16, c << 1);
}
CRC = cast_val(Uint16, CRC ^ c ^ a);
}
return CRC;
}
// https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash3.cpp#L94-L146
header_function
Uint32 hash32 (const void *data, Size len, Uint32 seed)
{
const Byte *d = cast_val(const Byte*, data);
Uint32 H = seed;
const Uint32 c1 = 0xcc9e2d51;
const Uint32 c2 = 0x1b873593;
#if defined(ENV_COMPILER_MSVC)
# define ROTL32(x,y) _rotl(x,y)
#elif defined(ENV_COMPILER_GCC) || defined(ENV_COMPILER_CLANG)
# define ROTL32(x,y) ((x << y) | (x >> (32 - y)))
#endif
Size i;
// Run until zero or one bytes are left
for (i = 0; (i + 4) <= len; i = i + 4) {
Uint32 word = load32BE(d + i);
word *= c1;
word = ROTL32(word, 15);
word *= c2;
H ^= word;
H = ROTL32(H, 13);
H = H * 5 + 0xe6546b64;
}
{ // Finish up the remaining bytes
Size remaining = len - i;
Uint32 k = 0;
switch (remaining) {
case 3: k ^= cast_val(Uint32, d[i+2]) << 16; switch_fallthrough;
case 2: k ^= cast_val(Uint32, d[i+1]) << 8; switch_fallthrough;
case 1: k ^= cast_val(Uint32, d[i]);
k *= c1;
k = ROTL32(k, 15);
k *= c2;
H ^= k;
switch_fallthrough;
default: break;
}
}
#undef ROTL32
H ^= len;
H ^= H >> 16;
H *= 0x85ebca6b;
H ^= H >> 13;
H *= 0xc2b2ae35;
H ^= H >> 16;
return H;
}
// https://github.com/aappleby/smhasher/blob/0ff96f7835817a27d0487325b6c16033e2992eb5/src/MurmurHash2.cpp#L88-L137
header_function
Uint64 hash64 (const void *data, Size len, Uint64 seed)
{
const Byte *d = cast_val(const Byte*, data);
const Uint64 m = 0xc6a4a7935bd1e995ULL;
const Sint r = 47;
Uint64 H = seed ^ (len * m);
Size i;
// Run until less than eight bytes are left
for (i = 0; (i + 8) <= len; i = i + 8) {
Uint64 dword = load64BE(d + i);
dword *= m;
dword ^= dword >> r;
dword *= m;
H ^= dword;
H *= m;
}
{ // Finish up the remaining bytes
Size remaining = len - i;
switch (remaining) {
case 7: H ^= cast_val(Uint64, d[i+6]) << 48; switch_fallthrough;
case 6: H ^= cast_val(Uint64, d[i+5]) << 40; switch_fallthrough;
case 5: H ^= cast_val(Uint64, d[i+4]) << 32; switch_fallthrough;
case 4: H ^= cast_val(Uint64, d[i+3]) << 24; switch_fallthrough;
case 3: H ^= cast_val(Uint64, d[i+2]) << 16; switch_fallthrough;
case 2: H ^= cast_val(Uint64, d[i+1]) << 8; switch_fallthrough;
case 1: H ^= cast_val(Uint64, d[i]);
H *= m;
switch_fallthrough;
default: break;
}
}
H ^= H >> r;
H *= m;
H ^= H >> r;
return H;
}
/************************************************************************************************
* Cyclic Redundancy Check (CRC)
*/
/* CRC-32
*
* Copied from: https://web.archive.org/web/20080303102530/http://c.snippets.org/snip_lister.php?fname=crc_32.c
* Copyright (C) 1986 Gary S. Brown. You may use this program, or
* code or tables extracted from it, as desired without restriction.
*
* Seems to be the same algoritm that is used in the HashCheck shell extensions (https://code.kliu.org/hashcheck/)
*/
header_function
Uint32 crc32 (Byte *buf, Size len)
{
persistent_immutable Uint32 lookup_table[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
Uint32 accumulator = 0xFFFFFFFFu;
for (Size i = 0; i < len; i++) {
Uint32 crc = accumulator;
Uint8 byte = buf[i];
Uint32 mixed = crc ^ byte;
Uint8 index = cast_val(Uint8, mixed & 0xFFu);
Uint32 lookup = lookup_table[index];
Uint32 upper = crc >> 8;
accumulator = lookup ^ upper;
}
Uint32 result = ~accumulator;
return result;
}
/************************************************************************************************
* Symbol and Label (Small immutable stack-allocated strings)
* ***********************************************
*/
// We put the string at the end since most reads will be about comparing the hash and length;
// and even in cases when one has to read the string, the likelihood is that the string will be less
// than 248 characters long, meaning that later cache lines will never actually load.
typedef struct Label {
union { struct { Uint32 h32; Uint16 h16; Uint8 h8; Uint8 len; }; Uint64 _id; };
Char str[247]; Char _zero; // = 0
} Label;
static_assert(sizeof(Label) == 256, "Label incorrectly sized");
#define LABEL_STR_CAPACITY (elemin(valzero(Label).str))
typedef struct Symbol {
union { struct { Uint32 h32; Uint16 h16; Uint8 h8; Uint8 len; }; Uint64 _id; };
Char str[55]; Char _zero; // = 0
} Symbol;
static_assert(sizeof(Symbol) == 64, "Symbol incorrectly sized");
#define SYMBOL_STR_CAPACITY (elemin(valzero(Symbol).str))
header_function
Label labelMakeL (const Char *str, Uint8 len)
{
debugAssert(len <= LABEL_STR_CAPACITY);
Label l = {
.len = len,
};
if (l.len) {
memcpy(l.str, str, l.len);
l.h32 = hash32(l.str, l.len, 0);
l.h16 = hash16(l.str, l.len, 0);
l.h8 = hash8(l.str, l.len, 0);
}
return l;
}
header_function
Symbol symbolMakeL (const Char *str, Uint8 len)
{
debugAssert(len <= SYMBOL_STR_CAPACITY);
Symbol l = {
.len = len,
};
if (l.len) {
memcpy(l.str, str, l.len);
l.h32 = hash32(l.str, l.len, 0);
l.h16 = hash16(l.str, l.len, 0);
l.h8 = hash8(l.str, l.len, 0);
}
return l;
}
header_function Label labelMake (const Char *str) { return labelMakeL(str, cast_val(Uint8, strlen(str))); }
header_function Symbol symbolMake (const Char *str) { return symbolMakeL(str, cast_val(Uint8, strlen(str))); }
header_function Uint8 labelLen (Label l) { return l.len; }
header_function Uint8 symbolLen (Symbol l) { return l.len; }
header_function Bool labelIsEqual (Label a, Label b) { return (a._id == b._id) && (memcmp(a.str, b.str, a.len) == 0); }
header_function Bool symbolIsEqual (Symbol a, Symbol b) { return (a._id == b._id) && (memcmp(a.str, b.str, a.len) == 0); }
header_function Bool labelIsEqualStrL (Label a, const Char *b, Size blen)
{
return (a.len == blen) && (memcmp(a.str, b, blen) == 0);
}
header_function Bool symbolIsEqualStrL (Symbol a, const Char *b, Size blen)
{
return (a.len == blen) && (memcmp(a.str, b, blen) == 0);
}
header_function
Bool labelIsEqualStr (Label a, const Char *b)
{
Bool result = labelIsEqualStrL(a, b, strlen(b));
return result;
}
header_function
Bool symbolIsEqualStr (Symbol a, const Char *b)
{
Bool result = symbolIsEqualStrL(a, b, strlen(b));
return result;
}
header_function
Bool srlzLabel (Label *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
if (!srlzUint8(&datum->len, func, funcdata, write, big_endian)) return false;
if (datum->len) {
if (!func(datum->str, datum->len, funcdata)) return false;
if (!write) *datum = labelMakeL(datum->str, datum->len);
} else {
if (!write) memzero(datum);
}
return true;
}
header_function
Bool srlzSymbol (Symbol *datum, SRLZ_FUNC((*func)), void *funcdata, Bool write, Bool big_endian) {
if (!srlzUint8(&datum->len, func, funcdata, write, big_endian)) return false;
if (datum->len) {
if (!func(datum->str, datum->len, funcdata)) return false;
if (!write) *datum = symbolMakeL(datum->str, datum->len);
} else {
if (!write) memzero(datum);
}
return true;
}
/************************************************************************************************
* Txt (String Library)
* ********************
*/
typedef struct Txt { Char *str; } Txt;
typedef struct Txt_Header_ {
Uint32 len;
Char str[];
} Txt_Header_;
header_function
Txt txt_Allocate (Memory_Push *mp, Size len)
{
Size size = len + 1 + sizeof(Txt_Header_);
Txt_Header_ *hdr = cast_val(Txt_Header_*, memPushAllotA(mp, size, alignof(Txt_Header_)));
debugAssert(len < UINT32_MAX);
hdr->len = cast_val(Uint32, len);
hdr->str[len] = '\0';
Char *s = hdr->str;
Txt t = {.str = s};
return t;
}
header_function
Txt txtNewL (Memory_Push *mp, const Char *s, Size len)
{
Txt t = txt_Allocate(mp, len);
memcpy(t.str, s, len);
return t;
}
header_function
Txt txtNew (Memory_Push *mp, const Char *s)
{
Size length = strlen(s);
Txt result = txtNewL(mp, s, length);
return result;
}
header_function
Char* txtStr (Txt t)
{
Char *s = t.str;
return s;
}
header_function
Size txtLen (Txt t)
{
Txt_Header_ *hdr = containerof(t.str, Txt_Header_, str);
return hdr->len;
}
header_function
Bool txt_EqN (Txt t1, Txt t2, Size len)
{
Bool result = memcmp(t1.str, t2.str, len) == 0;
return result;
}
header_function
Bool txtEq_TT (Txt t1, Txt t2)
{
Size t1l = txtLen(t1);
Size t2l = txtLen(t2);
if (t1l != t2l) return false;
Bool result = txt_EqN(t1, t2, t1l);
return result;
}
header_function
Bool txtEq_TC (Txt t1, const Char *s2)
{
Size t1l = txtLen(t1);
Size s2l = strlen(s2);
if (t1l != s2l) return false;
Bool result = memcmp(txtStr(t1), s2, t1l) == 0;
return result;
}
header_function
Bool txtEq_TL (Txt t1, Label l2)
{
Size t1l = txtLen(t1);
Size l2l = labelLen(l2);
if (t1l != l2l) return false;
Bool result = memcmp(txtStr(t1), l2.str, t1l) == 0;
return result;
}
header_function
Bool txtEq_TS (Txt t1, Symbol s2)
{
Size t1l = txtLen(t1);
Size s2l = symbolLen(s2);
if (t1l != s2l) return false;
Bool result = memcmp(txtStr(t1), s2.str, t1l) == 0;
return result;
}
#if defined(ENV_LANG_C)
# define txtEq(_a, _b) \
_Generic((_b) \
, Txt : txtEq_TT \
, Char* : txtEq_TC \
, const Char* : txtEq_TC \
, Label : txtEq_TL \
, Symbol : txtEq_TS \
)(_a, _b)
#elif defined(ENV_LANG_CXX)
header_function Bool txtEq (Txt a, Txt b) { return txtEq_TT(a, b); }
header_function Bool txtEq (Txt a, Char* b) { return txtEq_TC(a, b); }
header_function Bool txtEq (Txt a, const Char* b) { return txtEq_TC(a, b); }
header_function Bool txtEq (Txt a, Label b) { return txtEq_TL(a, b); }
header_function Bool txtEq (Txt a, Symbol b) { return txtEq_TS(a, b); }
#endif
header_function
Char* txtChr (Txt t, Char c)
{
Size len = txtLen(t);
Char *str = txtStr(t);
for (Size i = 0; i < len; i++) {
if (str[i] == c) return &str[i];
}
return nullptr;
}
header_function
Char* txtChrR (Txt t, Char c)
{
Size len = txtLen(t);
Char *str = txtStr(t);
for (Size i = len - 1; i < len; i++) {
if (str[i] == c) return &str[i];
}
return nullptr;
}
typedef struct Txt_Span {
Txt t;
Uint32 begin, end; // begin inclusive, end exclusive { [begin, end) }
Uint64 hash;
} Txt_Span;
header_function
Txt_Span txtGetSpan (Txt t, Sint64 begin, Sint64 end)
{
if (begin < 0) begin = cast_val(Uint32, cast_val(Sint32, txtLen(t)) - (-begin));
if (end < 0) end = cast_val(Uint32, cast_val(Sint32, txtLen(t)) - (-end));
if (end <= begin) {
end = begin + 1; // So that we don't end up with unnecessary errors
}
if (end > cast_val(Sint, txtLen(t))) {
end = cast_val(Sint, txtLen(t));
}
Txt_Span ts = {
.t = t,
.begin = cast_val(Uint32, begin),
.end = cast_val(Uint32, end),
.hash = hash64(txtStr(t) + begin, cast_val(Size, end - begin), 0),
};
return ts;
}
header_function
Char* txtspanPtr(Txt_Span ts)
{
Char *str = txtStr(ts.t) + ts.begin;
return str;
}
header_function
Size txtspanLen (Txt_Span ts)
{
Size len = ts.end - ts.begin;
return len;
}
header_function
Char txtspanIndex (Txt_Span ts, Uint index)
{
Uint ioffset = ts.begin + index;
if (ioffset >= ts.end) {
return '\0';
} else {
Char c = ts.t.str[ioffset];
return c;
}
}
header_function
Txt txtspanExtract (Memory_Push *mp, Txt_Span ts)
{
return txtNewL(mp, txtspanPtr(ts), txtspanLen(ts));
}
header_function
Bool txtspanIsEqualStr (Txt_Span ts, const Char *str)
{
Size strs = strlen(str);
Size tss = ts.end - ts.begin;
return (strs == tss) && (memcmp(str, ts.t.str + ts.begin, tss) == 0);
}
header_function
Bool txtspanIsEqualLabel (Txt_Span ts, Label l)
{
return labelIsEqualStrL(l, ts.t.str + ts.begin, ts.end-ts.begin);
}
header_function
Label labelMakeTS (Txt_Span ts)
{
return labelMakeL(ts.t.str + ts.begin, cast_val(Uint8, ts.end - ts.begin));
}
header_function
Symbol symbolMakeTS (Txt_Span ts)
{
return symbolMakeL(ts.t.str + ts.begin, cast_val(Uint8, ts.end - ts.begin));
}
typedef struct Txt_Fmt_Chunk Txt_Fmt_Chunk;
typedef struct Txt_Fmt_Pool {
Memory_Slab chunk_allocator;
Size chunk_count;
} Txt_Fmt_Pool;
typedef struct Txt_Fmt {
Txt_Fmt_Pool *pool;
Size length;
Txt_Fmt_Chunk *first_chunk, *last_chunk;
} Txt_Fmt;
struct Txt_Fmt_Chunk {
Txt_Fmt_Chunk *next_chunk;
Char data[55];
Uint8 filled;
};
static_assert(sizeof(Txt_Fmt_Chunk) == 64, "Txt fmt chunk won't fit in a single cache line");
#define TXT_FMT_CHARS_PER_CHUNK_ elemin(valzero(Txt_Fmt_Chunk).data)
static_assert(TXT_FMT_CHARS_PER_CHUNK_ == 55, "Number of bytes in each Txt fmt chunk is wrong");
header_function
void txtfmtPoolCreate (Txt_Fmt_Pool *tp, Memory *parent_memory, Uint32 chunk_count)
{
memzero(tp);
// 512 chunks in 32 KiB (same size as the free-grid metadata)
chunk_count = maximum(chunk_count, 512u);
tp->chunk_count = chunk_count;
memSlabCreate(&tp->chunk_allocator, parent_memory, chunk_count, sizeof(Txt_Fmt_Chunk));
}
header_function
void txtfmtPoolDelete (Txt_Fmt_Pool *tp)
{
memSlabDelete(&tp->chunk_allocator);
}
header_function
Txt_Fmt txtfmtCreate (Txt_Fmt_Pool *tp)
{
Txt_Fmt txt = {
.pool = tp,
};
txt.first_chunk = cast_val(Txt_Fmt_Chunk*, memSlabAllot(&tp->chunk_allocator));
memzero(txt.first_chunk);
txt.last_chunk = txt.first_chunk;
return txt;
}
header_function
void txtfmtDelete (Txt_Fmt *tf)
{
for (Txt_Fmt_Chunk *tc = tf->first_chunk, *tcc; tc != nullptr; tc = tcc) {
tcc = tc->next_chunk;
memSlabRemit(&tf->pool->chunk_allocator, tc);
}
}
header_function
Txt_Fmt_Chunk* txtfmt_GetLastChunk (Txt_Fmt *tf)
{
if (tf->last_chunk->filled == TXT_FMT_CHARS_PER_CHUNK_) {
tf->last_chunk->next_chunk = cast_val(Txt_Fmt_Chunk*, memSlabAllot(&tf->pool->chunk_allocator));
memzero(tf->last_chunk->next_chunk);
tf->last_chunk = tf->last_chunk->next_chunk;
}
return tf->last_chunk;
}
header_function
Size txtfmtAffix (Txt_Fmt *tf, Char c)
{
Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf);
s->data[s->filled] = c;
s->filled++;
tf->length++;
return 1;
}
header_function
PRINT_CALLBACK_FUNC(txtfmt_PrintCallback)
{
unused_variable(buffer_cap);
Txt_Fmt *tf = cast_val(Txt_Fmt*, userdata);
Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf);
debugAssert(s);
Size length = cast_val(Size, buffer_len);
Size empty = TXT_FMT_CHARS_PER_CHUNK_ - s->filled;
if (length > empty) {
memcpy(s->data + s->filled, filled_buffer, empty);
s->filled += cast_val(Uint8, empty);
if (length - empty) {
s = txtfmt_GetLastChunk(tf);
memcpy(s->data, filled_buffer + empty, length - empty);
s->filled += cast_val(Uint8, length - empty);
}
} else if (length != 0) {
memcpy(s->data + s->filled, filled_buffer, length);
s->filled += cast_val(Uint8, length);
}
tf->length += length;
}
with_clang_gcc(__attribute__(( format( __printf__, 2, 0 )))) /* 2nd arg is 0 since va_list can't be checked */
header_function
Size txtfmtAppendFV (Txt_Fmt *tf, const Char *fmt, va_list va)
{
Char buf[TXT_FMT_CHARS_PER_CHUNK_] = {0};
Size size = printStringVarArg(buf, elemin(buf), fmt, va, txtfmt_PrintCallback, tf);
return size;
}
with_clang_gcc(__attribute__ (( format( __printf__, 2, 3 ))))
header_function
Size txtfmtAppendF (Txt_Fmt *tf, const Char *fmt, ...)
{
va_list args;
va_start(args, fmt);
Size result = txtfmtAppendFV(tf, fmt, args);
va_end(args);
return result;
}
header_function
Size txtfmtAppendCL (Txt_Fmt *tf, const Char *tc, Size length)
{
Size copied = 0;
Size len = length;
while (len) {
Txt_Fmt_Chunk *s = txtfmt_GetLastChunk(tf);
Size l = minimum(len, TXT_FMT_CHARS_PER_CHUNK_);
l = minimum(l, TXT_FMT_CHARS_PER_CHUNK_ - s->filled);
memcpy(s->data + s->filled, tc + copied, l);
copied += l;
len -= l;
s->filled += cast_val(Uint8, l);
}
tf->length += length;
return copied;
}
header_function
Size txtfmtAppendC (Txt_Fmt *tf, const Char *tc)
{
Size len = strlen(tc);
Size result = txtfmtAppendCL(tf, tc, len);
return result;
}
header_function
Size txtfmtAppendT (Txt_Fmt *tf, Txt txt)
{
Size result = txtfmtAppendCL(tf, txtStr(txt), txtLen(txt));
return result;
}
header_function
Size txtfmtAppendTs (Txt_Fmt *tf, Txt_Span ts)
{
Size result = txtfmtAppendCL(tf, txtStr(ts.t) + ts.begin, cast_val(Size, ts.end - ts.begin));
return result;
}
header_function
Size txtfmtAppendLbl (Txt_Fmt *tf, Label lbl)
{
Size result = txtfmtAppendCL(tf, lbl.str, lbl.len);
return result;
}
header_function
Txt txtfmtFinish (Txt_Fmt *tf, Memory_Push *mp)
{
Txt t = txt_Allocate(mp, tf->length);
Size filled = 0;
for (Txt_Fmt_Chunk *s = tf->first_chunk; s != nullptr; s = s->next_chunk) {
debugAssert((filled + s->filled) < UINT32_MAX);
memcpy(txtStr(t) + filled, s->data, s->filled);
filled += s->filled;
}
(txtStr(t))[filled] = '\0';
txtfmtDelete(tf);
return t;
}
with_clang_gcc(__attribute__ (( format( __printf__, 3, 4 ))))
header_function
Txt txtFormat (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Char *fmt, ...)
{
Txt_Fmt tf = txtfmtCreate(tfp);
va_list args;
va_start(args, fmt);
txtfmtAppendFV(&tf, fmt, args);
va_end(args);
Txt result = txtfmtFinish(&tf, mempush);
return result;
}
#undef TXT_FMT_BYTES_PER_CHUNK_
/************************************************************************************************
* Tokenizer
*
* A C-syntax tokenizer
*/
typedef enum Token_Kind {
Token_Kind_EOF = 0,
// NOTE(naman): Single letter non-alphanumeric tokens (like '+', '{', etc.) with ASCII value <=127 are stord as their own kind.
Token_Kind_EQUALITY = 128,
Token_Kind_NOTEQUAL,
Token_Kind_LESSEQ,
Token_Kind_GREATEQ,
Token_Kind_LOGICAL_OR,
Token_Kind_LOGICAL_AND,
Token_Kind_SHIFT_LEFT,
Token_Kind_SHIFT_RIGHT,
Token_Kind_ADD_ASSIGN,
Token_Kind_SUB_ASSIGN,
Token_Kind_MULTIPLY_ASSIGN,
Token_Kind_DIVIDE_ASSIGN,
Token_Kind_MODULUS_ASSIGN,
Token_Kind_XOR_ASSIGN,
Token_Kind_OR_ASSIGN,
Token_Kind_AND_ASSIGN,
Token_Kind_SHIFT_LEFT_ASSIGN,
Token_Kind_SHIFT_RIGHT_ASSIGN,
Token_Kind_INCREMENT,
Token_Kind_DECREMENT,
Token_Kind_PTR_DEREF,
Token_Kind_ELLIPSIS,
Token_Kind_ATTRIBUTE_NAMESPACE,
Token_Kind_ATTRIBUTE_BEGIN,
Token_Kind_ATTRIBUTE_END,
Token_Kind_CONCATENATE,
Token_Kind_IDENTIFIER = 175,
Token_Kind_CONSTANT_INTEGER,
Token_Kind_CONSTANT_FLOATING,
Token_Kind_CONSTANT_CHARACTER,
Token_Kind_CONSTANT_STRING,
Token_Kind_TOTAL,
} Token_Kind;
static_assert(Token_Kind_TOTAL <= 255);
typedef struct Token {
Txt_Span span;
Token_Kind kind;
Uint line, column;
Byte _pad[4];
union {
Uint64 integer;
Float64 floating;
} value;
} Token;
#define TOKENIZER_ERROR_FUNC(_name) Token _name (struct Tokenizer *toker, const Char *msg, void *userdata)
typedef struct Tokenizer {
Token read_token;
Txt src;
Sint64 cursor;
Uint line;
Uint column;
TOKENIZER_ERROR_FUNC((*err_func));
void *err_data;
} Tokenizer;
header_function Token tokenizerGetToken (Tokenizer *toker);
header_function
Tokenizer tokenizerMake (Txt src, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data)
{
Tokenizer toker = {
.src = src,
.line = 1,
.column = 1,
.err_func = err_func,
.err_data = err_data,
};
tokenizerGetToken(&toker);
return toker;
}
header_function
void tokenizer_CharAdvance (Tokenizer *toker, Uint count)
{
for (size_t i = 0; i < count; i++) {
if (toker->src.str[toker->cursor] == '\n') {
toker->line++;
toker->column = 1;
} else {
toker->column++;
}
toker->cursor++;
}
}
header_function
Token tokenizerPeekToken (Tokenizer *toker)
{
Token tok = toker->read_token;
return tok;
}
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wassign-enum\""); // Assigning random values to enums (ASCII characters)
// FIXME(naman): Implement my own strtod and remove this usage of libc atof
#if defined(ENV_LANG_CXX)
extern "C"
#endif
#if defined(ENV_COMPILER_MSVC)
_Check_return_ _ACRTIMP
#endif
Float64 atof(
#if defined(ENV_COMPILER_MSVC)
_In_z_
#endif
const Char* str);
header_function
Token tokenizerGetToken (Tokenizer *toker)
{
Token last_token = toker->read_token;
Uint64 tokenized_integer = 0;
Float64 tokenized_floating = 0.0;
{ // Eat Whitespace
Bool ate_something_this_loop = true;
while (ate_something_this_loop) {
ate_something_this_loop = false;
while ((toker->src.str[toker->cursor] == ' ') ||
(toker->src.str[toker->cursor] == '\t') ||
(toker->src.str[toker->cursor] == '\r') ||
(toker->src.str[toker->cursor] == '\n')) {
ate_something_this_loop = true;
tokenizer_CharAdvance(toker, 1);
}
if ((toker->src.str[toker->cursor] == '/') && (toker->src.str[toker->cursor+1] == '/')) {
ate_something_this_loop = true;
for (;;) {
if (toker->src.str[toker->cursor] == '\n') {
if ((toker->cursor) > 0 && toker->src.str[toker->cursor-1] == '\\') {
// do nothing
} else {
break;
}
}
tokenizer_CharAdvance(toker, 1);
}
tokenizer_CharAdvance(toker, 1);
}
if ((toker->src.str[toker->cursor] == '/') && (toker->src.str[toker->cursor+1] == '*')) {
ate_something_this_loop = true;
tokenizer_CharAdvance(toker, 2);
while (!((toker->src.str[toker->cursor] == '*') && (toker->src.str[toker->cursor+1] == '/'))) tokenizer_CharAdvance(toker, 1);
tokenizer_CharAdvance(toker, 2);
}
}
}
Sint64 begin = toker->cursor;
Token_Kind kind;
switch (toker->src.str[toker->cursor]) {
case '\0': {
toker->read_token = valzero(Token);
return last_token;
} break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': {
// First, make sure if the token is even a number or a standalone dot / ellipsis
if ((toker->src.str[toker->cursor] == '.') &&
!((toker->src.str[toker->cursor+1] >= '0') && (toker->src.str[toker->cursor+1] <= '9'))) {
tokenizer_CharAdvance(toker, 1);
if ((toker->src.str[toker->cursor + 0] == '.') && (toker->src.str[toker->cursor + 1] == '.')) {
tokenizer_CharAdvance(toker, 2);
kind = Token_Kind_ELLIPSIS;
} else {
kind = cast_val(Token_Kind, '.');
}
} else { // If it is a number
Bool floating = false;
{ // First, detect if this is a float (which only supports hex and decimal)
Sint64 cursor = toker->cursor + 1;
// Quotes are used in place of commas
if (toker->src.str[cursor] == 'x') { // Hexadecimal
cursor += 2;
while (((toker->src.str[cursor] <= '9') && (toker->src.str[cursor] >= '0')) ||
((toker->src.str[cursor] <= 'F') && (toker->src.str[cursor] >= 'A')) ||
((toker->src.str[cursor] <= 'f') && (toker->src.str[cursor] >= 'a')) ||
(toker->src.str[cursor] == '\'')) {
cursor++;
}
if ((toker->src.str[cursor] == '.') ||
(toker->src.str[cursor] == 'p') ||
(toker->src.str[cursor] == 'P')) {
floating = true;
}
} else { // Decimal
while (((toker->src.str[cursor] <= '9') && (toker->src.str[cursor] >= '0')) ||
(toker->src.str[cursor] == '\'')) {
cursor++;
}
if ((toker->src.str[cursor] == '.') ||
(toker->src.str[cursor] == 'e') ||
(toker->src.str[cursor] == 'E')) {
floating = true;
}
}
}
if (floating) { // If it is float
kind = Token_Kind_CONSTANT_FLOATING;
tokenizer_CharAdvance(toker, 1);
// Hexadecimal float
if ((toker->src.str[toker->cursor] == 'x') || (toker->src.str[toker->cursor] == 'X')) {
tokenizer_CharAdvance(toker, 1);
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
if (toker->src.str[toker->cursor] == '.') {
tokenizer_CharAdvance(toker, 1);
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
if ((toker->src.str[toker->cursor] == 'p') || (toker->src.str[toker->cursor] == 'P')) {
tokenizer_CharAdvance(toker, 1);
if ((toker->src.str[toker->cursor] == '-') || (toker->src.str[toker->cursor] == '+')) {
tokenizer_CharAdvance(toker, 1);
}
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
} else { // Decimal float
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
if (toker->src.str[toker->cursor] == '.') {
tokenizer_CharAdvance(toker, 1);
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
if ((toker->src.str[toker->cursor] == 'e') || (toker->src.str[toker->cursor] == 'E')) {
tokenizer_CharAdvance(toker, 1);
if ((toker->src.str[toker->cursor] == '-') || (toker->src.str[toker->cursor] == '+')) {
tokenizer_CharAdvance(toker, 1);
}
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
}
/* */ if (toker->src.str[toker->cursor] == 'f') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'l') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'F') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
} else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'f')) {
tokenizer_CharAdvance(toker, 2);
} else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'd')) {
tokenizer_CharAdvance(toker, 2);
} else if ((toker->src.str[toker->cursor] == 'd') && (toker->src.str[toker->cursor+1] == 'l')) {
tokenizer_CharAdvance(toker, 2);
} else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'F')) {
tokenizer_CharAdvance(toker, 2);
} else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'D')) {
tokenizer_CharAdvance(toker, 2);
} else if ((toker->src.str[toker->cursor] == 'D') && (toker->src.str[toker->cursor+1] == 'L')) {
tokenizer_CharAdvance(toker, 2);
}
Txt_Span span = txtGetSpan(toker->src, begin, toker->cursor);
tokenized_floating = atof(span.t.str + span.begin);
} else { // Else, if it is an integer
kind = Token_Kind_CONSTANT_INTEGER;
Uint base;
Uint digit_begin_index;
if (toker->src.str[toker->cursor] == '0') { // If another base
tokenizer_CharAdvance(toker, 1);
if ((toker->src.str[toker->cursor] == 'x') || (toker->src.str[toker->cursor] == 'X')) {
// Hexadecimal
tokenizer_CharAdvance(toker, 1);
base = 16;
digit_begin_index = 2;
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
} else if ((toker->src.str[toker->cursor] == 'b') || (toker->src.str[toker->cursor] == 'B')) {
// Binary
tokenizer_CharAdvance(toker, 1);
base = 2;
digit_begin_index = 2;
while ((toker->src.str[toker->cursor] == '0') || (toker->src.str[toker->cursor] == '1') || (toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
} else {
// Octal
base = 8;
digit_begin_index = 1;
while (((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
} else { // Decimal
tokenizer_CharAdvance(toker, 1);
base = 10;
digit_begin_index = 0;
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
(toker->src.str[toker->cursor] == '\'')) {
tokenizer_CharAdvance(toker, 1);
}
}
Txt_Span digit_span = txtGetSpan(toker->src, begin + digit_begin_index, toker->cursor);
// Integer suffix
if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'l') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'l') {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'w') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'b') {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'W') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'B') {
tokenizer_CharAdvance(toker, 1);
}
}
} else if (toker->src.str[toker->cursor] == 'l') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'l') {
tokenizer_CharAdvance(toker, 1);
}
if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
}
if ((toker->src.str[toker->cursor] == 'u') || (toker->src.str[toker->cursor] == 'U')) {
tokenizer_CharAdvance(toker, 1);
}
}
Uint64 accumulator = 0;
Uint8 char_to_digit[128] = {};
char_to_digit['0'] = 0;
char_to_digit['1'] = 1;
char_to_digit['2'] = 2;
char_to_digit['3'] = 3;
char_to_digit['4'] = 4;
char_to_digit['5'] = 5;
char_to_digit['6'] = 6;
char_to_digit['7'] = 7;
char_to_digit['8'] = 8;
char_to_digit['9'] = 9;
char_to_digit['A'] = 10; char_to_digit['a'] = 10;
char_to_digit['B'] = 11; char_to_digit['b'] = 11;
char_to_digit['C'] = 12; char_to_digit['c'] = 12;
char_to_digit['D'] = 13; char_to_digit['d'] = 13;
char_to_digit['E'] = 14; char_to_digit['e'] = 14;
char_to_digit['F'] = 15; char_to_digit['f'] = 15;
for (Uint i = 0; txtspanIndex(digit_span, i); i++) {
Char c = txtspanIndex(digit_span, i);
if (c == '\'') {
continue;
}
Uint8 digit = char_to_digit[cast_val(Uint, c)];
if (accumulator > (UINT64_MAX - digit)/base) {
toker->read_token = (toker->err_func)(toker, "Integer too large to fit into Uint64", toker->err_data);
return last_token;
}
accumulator = accumulator * base + digit;
}
tokenized_integer = accumulator;
}
}
} break;
case 'A': case 'a': case 'B': case 'b': case 'C': case 'c':
case 'D': case 'd': case 'E': case 'e': case 'F': case 'f':
case 'G': case 'g': case 'H': case 'h': case 'I': case 'i':
case 'J': case 'j': case 'K': case 'k': case 'L': case 'l':
case 'M': case 'm': case 'N': case 'n': case 'O': case 'o':
case 'P': case 'p': case 'Q': case 'q': case 'R': case 'r':
case 'S': case 's': case 'T': case 't': case 'U': case 'u':
case 'V': case 'v': case 'W': case 'w': case 'X': case 'x':
case 'Y': case 'y': case 'Z': case 'z': case '_':
case '\'': case '"': { // Charater, string or identifier
// If it is character
if ((toker->src.str[toker->cursor] == '\'') ||
((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8') && (toker->src.str[toker->cursor+2] == '\'')) ||
((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '\'')) ||
((toker->src.str[toker->cursor] == 'U') && (toker->src.str[toker->cursor+1] == '\'')) ||
((toker->src.str[toker->cursor] == 'L') && (toker->src.str[toker->cursor+1] == '\''))) {
kind = Token_Kind_CONSTANT_CHARACTER;
if ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8')) {
tokenizer_CharAdvance(toker, 2);
} else if (toker->src.str[toker->cursor] == 'u') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'U') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
}
if (toker->src.str[toker->cursor] != '\'') {
toker->read_token = (toker->err_func)(toker, "Open quote missing in character literal", toker->err_data);
return last_token;
} else {
tokenizer_CharAdvance(toker, 1); // '
}
if (toker->src.str[toker->cursor] == '\'') {
toker->read_token = (toker->err_func)(toker, "character literal can not be empty", toker->err_data);
return last_token;
}
if (toker->src.str[toker->cursor] == '\n') {
toker->read_token = (toker->err_func)(toker, "character literal can not be a newline", toker->err_data);
return last_token;
}
if (toker->src.str[toker->cursor] == '\\') {
tokenizer_CharAdvance(toker, 1);
/* */ if (toker->src.str[toker->cursor] == '\'') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '"') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '?') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '\\') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'a') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'b') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'f') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'n') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'r') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 't') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'v') {
tokenizer_CharAdvance(toker, 1);
} else if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) {
for (size_t i = 0; i < 3; i++) {
if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) {
tokenizer_CharAdvance(toker, 1);
}
}
} else if (toker->src.str[toker->cursor] == 'x') {
tokenizer_CharAdvance(toker, 1);
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'u') {
tokenizer_CharAdvance(toker, 1);
for (size_t i = 0; i < 4; i++) {
if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
} else {
toker->read_token = (toker->err_func)(toker, "Universal character incomplete, needs four hex digits", toker->err_data);
return last_token;
}
}
} else if (toker->src.str[toker->cursor] == 'U') {
tokenizer_CharAdvance(toker, 1);
for (size_t i = 0; i < 8; i++) {
if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
} else {
toker->read_token = (toker->err_func)(toker, "Universal character incomplete, needs eight hex digits", toker->err_data);
return last_token;
}
}
} else {
toker->read_token = (toker->err_func)(toker, "Unknown escape sequence in character", toker->err_data);
return last_token;
}
} else {
tokenizer_CharAdvance(toker, 1);
}
if (toker->src.str[toker->cursor] != '\'') {
toker->read_token = (toker->err_func)(toker, "Closing quote missing in character literal", toker->err_data);
return last_token;
} else {
tokenizer_CharAdvance(toker, 1); // '
}
// If it is string
} else if ((toker->src.str[toker->cursor] == '\"') ||
((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8') && (toker->src.str[toker->cursor+2] == '"')) ||
((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '"')) ||
((toker->src.str[toker->cursor] == 'U') && (toker->src.str[toker->cursor+1] == '"')) ||
((toker->src.str[toker->cursor] == 'L') && (toker->src.str[toker->cursor+1] == '"'))) {
kind = Token_Kind_CONSTANT_STRING;
if ((toker->src.str[toker->cursor] == 'u') && (toker->src.str[toker->cursor+1] == '8')) {
tokenizer_CharAdvance(toker, 2);
} else if (toker->src.str[toker->cursor] == 'u') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'U') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'L') {
tokenizer_CharAdvance(toker, 1);
}
if (toker->src.str[toker->cursor] != '"') {
toker->read_token = (toker->err_func)(toker, "Open quote missing in string literal", toker->err_data);
return last_token;
} else {
tokenizer_CharAdvance(toker, 1); // "
}
if (toker->src.str[toker->cursor] == '"') {
tokenizer_CharAdvance(toker, 1); // "
} else {
while (toker->src.str[toker->cursor] != '"') {
if (toker->src.str[toker->cursor] == '\n') {
toker->read_token = (toker->err_func)(toker, "Newline not allowed in a string literal", toker->err_data);
return last_token;
}
if (toker->src.str[toker->cursor] == '\\') {
tokenizer_CharAdvance(toker, 1);
/* */ if (toker->src.str[toker->cursor] == '\'') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '"') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '?') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '\\') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'a') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'b') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'f') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'n') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'r') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 't') {
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == 'v') {
tokenizer_CharAdvance(toker, 1);
} else if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) {
for (size_t i = 0; i < 3; i++) {
if ((toker->src.str[toker->cursor] <= '7') && (toker->src.str[toker->cursor] >= '0')) {
tokenizer_CharAdvance(toker, 1);
}
}
} else if (toker->src.str[toker->cursor] == 'x') {
tokenizer_CharAdvance(toker, 1);
while (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
}
} else if (toker->src.str[toker->cursor] == 'u') {
tokenizer_CharAdvance(toker, 1);
for (size_t i = 0; i < 4; i++) {
if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
} else {
toker->read_token = (toker->err_func)(toker, "Universal character in string incomplete, needs four hex digits", toker->err_data);
return last_token;
}
}
} else if (toker->src.str[toker->cursor] == 'U') {
tokenizer_CharAdvance(toker, 1);
for (size_t i = 0; i < 8; i++) {
if (((toker->src.str[toker->cursor] <= '9') && (toker->src.str[toker->cursor] >= '0')) ||
((toker->src.str[toker->cursor] <= 'F') && (toker->src.str[toker->cursor] >= 'A')) ||
((toker->src.str[toker->cursor] <= 'f') && (toker->src.str[toker->cursor] >= 'a'))) {
tokenizer_CharAdvance(toker, 1);
} else {
toker->read_token = (toker->err_func)(toker, "Universal character in string incomplete, needs eight hex digits", toker->err_data);
return last_token;
}
}
} else {
toker->read_token = (toker->err_func)(toker, "Unknown escape sequence in string", toker->err_data);
return last_token;
}
} else {
tokenizer_CharAdvance(toker, 1);
}
}
if (toker->src.str[toker->cursor] != '"') {
toker->read_token = (toker->err_func)(toker, "Closing quote missing in string literal", toker->err_data);
return last_token;
} else {
tokenizer_CharAdvance(toker, 1); // "
}
}
} else {
tokenizer_CharAdvance(toker, 1);
char *ident = toker->src.str + toker->cursor;
size_t count = 0;
for (;;) {
char c = ident[count+0];
if ((c >= 'a') && (c <= 'z')) { count++; continue; }
if ((c >= 'A') && (c <= 'Z')) { count++; continue; }
if ((c >= '0') && (c <= '9')) { count++; continue; }
if (c == '_') { count++; continue; }
break;
}
for (size_t i = 0; i < count; i++) {
tokenizer_CharAdvance(toker, 1);
}
kind = Token_Kind_IDENTIFIER;
}
} break;
case '(': case ')':
case '{': case '}': case '~': case '?':
case ';': case ',': {
kind = cast_val(Token_Kind, toker->src.str[toker->cursor]);
tokenizer_CharAdvance(toker, 1);
} break;
#define TOKEN_CASE_1_2(_ch0, _ch1, _kind1) \
_ch0: { \
tokenizer_CharAdvance(toker, 1); \
if (toker->src.str[toker->cursor] == _ch1) { \
kind = _kind1; \
tokenizer_CharAdvance(toker, 1); \
} else { \
kind = cast_val(Token_Kind, _ch0); \
} \
} break
case TOKEN_CASE_1_2('=', '=', Token_Kind_EQUALITY);
case TOKEN_CASE_1_2('!', '=', Token_Kind_NOTEQUAL);
case TOKEN_CASE_1_2('#', '#', Token_Kind_CONCATENATE);
case TOKEN_CASE_1_2(':', ':', Token_Kind_ATTRIBUTE_NAMESPACE);
case TOKEN_CASE_1_2('*', '=', Token_Kind_MULTIPLY_ASSIGN);
case TOKEN_CASE_1_2('/', '=', Token_Kind_DIVIDE_ASSIGN);
case TOKEN_CASE_1_2('%', '=', Token_Kind_MODULUS_ASSIGN);
case TOKEN_CASE_1_2('^', '=', Token_Kind_XOR_ASSIGN);
case TOKEN_CASE_1_2('[', '[', Token_Kind_ATTRIBUTE_BEGIN);
case TOKEN_CASE_1_2(']', ']', Token_Kind_ATTRIBUTE_END);
#undef TOKEN_CASE_1_2
#define TOKEN_CASE_1_2_2(_ch0, _ch1, _kind1, _ch2, _kind2) \
_ch0: { \
tokenizer_CharAdvance(toker, 1); \
if (toker->src.str[toker->cursor] == _ch1) { \
kind = _kind1; \
tokenizer_CharAdvance(toker, 1); \
} else if (toker->src.str[toker->cursor] == _ch2) { \
kind = _kind2; \
tokenizer_CharAdvance(toker, 1); \
} else { \
kind = cast_val(Token_Kind, _ch0); \
} \
} break
case TOKEN_CASE_1_2_2('|', '|', Token_Kind_LOGICAL_OR, '=', Token_Kind_OR_ASSIGN);
case TOKEN_CASE_1_2_2('&', '&', Token_Kind_LOGICAL_AND, '=', Token_Kind_AND_ASSIGN);
case TOKEN_CASE_1_2_2('+', '+', Token_Kind_INCREMENT, '=', Token_Kind_ADD_ASSIGN);
#undef TOKEN_CASE_1_2_2
case '-': {
tokenizer_CharAdvance(toker, 1);
if (toker->src.str[toker->cursor] == '>') {
kind = Token_Kind_PTR_DEREF;
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '-') {
kind = Token_Kind_DECREMENT;
tokenizer_CharAdvance(toker, 1);
} else if (toker->src.str[toker->cursor] == '=') {
kind = Token_Kind_SUB_ASSIGN;
tokenizer_CharAdvance(toker, 1);
} else {
kind = cast_val(Token_Kind, '-');
}
} break;
#define TOKEN_CASE_1_2_2_3(_ch0, _ch1, _kind1, _ch2, _kind2, _ch3, _kind3) \
_ch0: { \
tokenizer_CharAdvance(toker, 1); \
if (toker->src.str[toker->cursor] == _ch1) { \
tokenizer_CharAdvance(toker, 1); \
kind = _kind1; \
} else if (toker->src.str[toker->cursor] == _ch2) { \
tokenizer_CharAdvance(toker, 1); \
if (toker->src.str[toker->cursor] == _ch3) { \
tokenizer_CharAdvance(toker, 1); \
kind = _kind3; \
} else { \
kind = _kind2; \
} \
} else { \
kind = cast_val(Token_Kind, _ch0); \
} \
} break
case TOKEN_CASE_1_2_2_3('<', '=', Token_Kind_LESSEQ, '<', Token_Kind_SHIFT_LEFT, '=', Token_Kind_SHIFT_LEFT_ASSIGN);
case TOKEN_CASE_1_2_2_3('>', '=', Token_Kind_GREATEQ, '<', Token_Kind_SHIFT_RIGHT, '=', Token_Kind_SHIFT_RIGHT_ASSIGN);
#undef TOKEN_CASE_1_2_2_3
default: {
tokenizer_CharAdvance(toker, 1);
toker->read_token = (toker->err_func)(toker, "Invalid token", toker->err_data);
return last_token;
} break;
}
toker->read_token = compound_init(Token, {
.span = txtGetSpan(toker->src, begin, toker->cursor),
.kind = kind,
.line = toker->line,
.column = toker->column,
});
if (toker->read_token.kind == Token_Kind_CONSTANT_INTEGER) {
toker->read_token.value.integer = tokenized_integer;
} else if (toker->read_token.kind == Token_Kind_CONSTANT_FLOATING) {
toker->read_token.value.floating = tokenized_floating;
}
return last_token;
}
pragma_clang("clang diagnostic pop");
/************************************************************************************************
* B-Tree
*/
// B-Tree implementation
// Reference: Chapter 18 of Introduction to Algorithms - Fourth Edition by Cormen, et al.
// Some changes have been made to make the implementation more amenable to future optimizations
// (SIMD or otherwise).
// First, the idea of "minimum degree" has been discarded in favour of having a maximum count.
// This means that we store an even number of keys instead of an odd number.
// Second, there is no leaf boolean; the leaf property is known by comparing the first child to nullptr.
// WARN(naman): This BTree doesn't rebalances itself when keys are dropped. This means that the size
// of the tree only ever goes up. Thus, don't use this in places where lots of deletions will
// be taking place, since a unnecessarily large tree will slow down the lookups.
// The value of M is chosen so that the all keys can be compared using 5 256-wide SSE instructions.
// In addition, this also makes the BTree_Node align with cache line sizes of 64, 128, 256 and 512 bytes.
#define M 20
typedef struct BTree_Node {
// In the keys and data array, [M/2 - 1, M] elements have to be present
// NOTE(naman): More often used members are on top together for cache friendliness.
Uint64 keys[M]; // Called k in Cormen
Size count; // Called n in Cormen
struct BTree_Node *children[M + 1]; // Called c in Cormen
void *data[M];
struct BTree_Node *next, *prev; // Unused for now
} BTree_Node;
static_assert(sizeof(BTree_Node) == 512, "BTree_Node's size is not expected");
typedef struct BTree {
Memory_Push *parent_memory_push;
BTree_Node *root;
} BTree;
header_function
void btreeInit (BTree *b, Memory_Push *parent_memory_push)
{
memzero(b);
b->parent_memory_push = parent_memory_push;
b->root = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*b->root), sizeof(*b->root)));
}
// NOTE(naman): There is no btreeDelete function since the only allocations are done on the
// push allocator.
header_function
void* btree_FindReplace (BTree *b, Uint64 key, Bool replace_value, void *replacement)
{
BTree_Node *n = b->root;
while (n != nullptr) {
Size i;
Bool found_equal = false;
for (i = 0; i < n->count; i++) {
if (n->keys[i] == key) {
found_equal = true;
break;
} else if (n->keys[i] > key) {
break;
}
}
if (found_equal) {
void *old = n->data[i];
if (replace_value) n->data[i] = replacement;
return old;
} else if (n->children[0] == nullptr) { // Leaf Node
return nullptr;
} else {
// If greater found, i is pointing to the key just greater, meaning the i-th child is
// between the just smaller and just greater keys. If greater was not found, then i is
// equal to n->count, which also points to the last child.
n = n->children[i];
}
}
return nullptr;
}
header_function
void* btreeSearch (BTree *b, Uint64 key) {
void *result = btree_FindReplace(b, key, false, nullptr);
return result;
}
header_function
void* btreeReplace (BTree *b, Uint64 key, void *new_value) {
void *result = btree_FindReplace(b, key, true, new_value);
return result;
}
header_function
void* btreeRemove (BTree *b, Uint64 key) {
void *result = btree_FindReplace(b, key, true, nullptr);
return result;
}
// NOTE(naman): Function returns the previous value, if any.
header_function
void btree_SplitChild (BTree *b, BTree_Node *parent, Size i)
{
// All indices in this function's comments (I, T, etc.) are zero indexed. Play close attention.
// To mark them against regular numbers, they are written in capital. `i` and `I` are both the same,
// `i` is used when talking about numbers (count, etc.), while `I` is used when talking about indices.
// This function gets called if the I-th child of the node has become full (i.e., has m keys)
// and need to be split.
BTree_Node *full = parent->children[i];
BTree_Node *newnode = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*newnode), sizeof(*newnode)));
// We will move the last m/2-1 keys from `full` into `newnode`. Since `full` had m keys, this means that
// `full` will be left with m/2+1 keys from [0...M/2] and m/2-1 keys from [M/2+1...M-1] wil become `newnode`'s keys
// from [0..M/2-1].
newnode->count = M/2 - 1;
memcpy(&newnode->keys[0], &full->keys[M/2 + 1], newnode->count * sizeof(newnode->keys[0]));
memcpy(&newnode->data[0], &full->data[M/2 + 1], newnode->count * sizeof(newnode->data[0]));
// If the `full` node is not a leaf, it has a bunch of children. Since the trailing keys got moved,
// the children whose points fall between those keys also have to be moved. Because we moved m/2-1 keys,
// there would be m/2 children associated with them that also need to be moved. This means that the
// M/2-th key in `full` will have no right-child anymore. But that's okay, since the M/2-th
// key will get get moved to `parent` anyways. So, we will move m/2 children from `full`'s [M/2+1...M]
// to `newnode`'s [0...M/2-1].
if (full->children[0] != nullptr) { // Not a leaf
memcpy(&newnode->children[0], &full->children[M/2+1], (newnode->count + 1) * sizeof(newnode->children[0]));
}
// Finally, we set the `full`'s count to m/2. Remember that `full` actually still has a total of m/2+1 elements.
// However, the element on index M/2 will soon get moved to the `parent`.
full->count = M/2;
// But before we can move `full`'s M/2 element and attach `newnode` as a new child, we have to make space
// for it in `parent`. First, let's move the children. Let parent->count be n. If the total number
// of keys that the parent has is n from 0 to N-1, the children would be n+1 from 0 to N. Since the
// `full` was attached at index I, we nee to move all children from [I+1...N] to [I+2...N+1], so that
// we can add `newnode` as a child at index I+1.
for (Size j = (parent->count + 1); j >= (i + 2); j--) {
parent->children[j] = parent->children[j-1];
}
parent->children[i+1] = newnode;
// Now, the turn for keys. Since `full`'s M/2 element has to be move to `parent`'s I element (right between
// the I-th and (I+1)-th child), we need to make space for it by moving elements from [I...N-1] to [I+1...N].
for (Size j = parent->count; j >= (i + 1); j--) {
parent->keys[j] = parent->keys[j-1];
parent->data[j] = parent->data[j-1];
}
// Now that there is a space in `parent`'s key array, let's put `full`'s M/2 element there.
parent->keys[i] = full->keys[M/2];
parent->data[i] = full->data[M/2];
parent->count++;
}
pragma_msvc("warning ( push )");
pragma_msvc("warning ( disable: 6385 )"); // Analyze: READ_OVERRUN (Reading invalid data from 'n->keys' and 'n->children')
header_function
void* btreeInsert (BTree *b, Uint64 key, void *datum)
{
if (b->root->count == M) {
// This is a clever hack. Cormen wanted to use the existing splitting function (btree_SplitChild) to spit the root too.
// But splitting requires transferring one item to our parent, and the root has no parent. So, they temporarily create
// an invalid tree by creating a new node with zero elements and setting the root as its child. But the splitting process
// adds one element and one more child (the new splitted node) to the new root, thus making the tree valid again.
BTree_Node *s = cast_val(BTree_Node *, memPushAllotA(b->parent_memory_push, sizeof(*s), sizeof(*s)));
s->children[0] = b->root;
b->root = s;
btree_SplitChild(b, b->root, 0);
}
BTree_Node *n = b->root;
while (n != nullptr) {
Size i = n->count;
if (n->children[0] == nullptr) { // Leaf
if ((key >= n->keys[0]) && (key <= n->keys[i-1])) { // If the key is in the space of this node
for (Size j = 0; j < i; j++) { // Check if the key has already been inserted
if (key == n->keys[j]) {
void *result = n->data[j];
n->data[j] = datum;
return result;
}
}
}
while ((i >= 1) && (key < n->keys[i-1])) { // Else move the data
n->keys[i] = n->keys[i-1];
n->data[i] = n->data[i-1];
i--;
}
n->keys[i] = key; // And copy the key
n->data[i] = datum;
n->count++;
return nullptr;
} else {
while ((i >= 1) && (key < n->keys[i-1])) {
i--;
}
if (n->children[i]->count == M) {
btree_SplitChild(b, n, i);
if (key > n->keys[i]) {
i++;
}
}
n = n->children[i];
}
}
return nullptr;
}
pragma_msvc("warning ( pop )");
header_function
void* btreeSwitch (BTree *b, Uint64 key, void *oldval, void *newval)
{
return oldval ? btreeReplace(b, key, newval) : btreeInsert(b, key, newval);
}
// Return false to prematuerely stop iteration
#define BTREE_ITERATOR_FUNC(name) Bool name (void *funcdata, Uint64 key, void* value)
header_function
Bool btree_IterateNode (BTree_Node *bn, BTREE_ITERATOR_FUNC((*func)), void *funcdata)
{
if (bn == nullptr) return true;
Size i;
for (i = 0; i < bn->count; i++) {
if (btree_IterateNode(bn->children[i], func, funcdata) == false) return false;
if (func(funcdata, bn->keys[i], bn->data[i]) == false) return false;
}
if (btree_IterateNode(bn->children[i], func, funcdata) == false) return false;
return true;
}
header_function
Bool btreeIterate (BTree *b, BTREE_ITERATOR_FUNC((*func)), void *funcdata)
{
Bool result = btree_IterateNode(b->root, func, funcdata);
return result;
}
#undef M
/************************************************************************************************
* Hashmap
*/
// WARN(naman): Same performance characteristics as the BTree. See it's documentation for more details
typedef struct Hashmap_Entry {
struct Hashmap_Entry *next;
Uint64 hash;
void *key;
void *value;
} Hashmap_Entry;
typedef struct Hashmap {
Memory *parent_memory;
BTree btree;
Memory_Push btree_allocator;
COMPARE_FUNC((*compare_func));
Memory_Slab entry_allocator;
} Hashmap;
header_function
void hashmapCreate (Hashmap *hm, Uint entry_count, COMPARE_FUNC((*compare_func)), Memory *parent_memory)
{
memzero(hm);
hm->compare_func = compare_func;
hm->parent_memory = parent_memory;
memPushCreate(&hm->btree_allocator, hm->parent_memory, HUNDRED * sizeof(*(hm->btree.root)));
btreeInit(&hm->btree, &hm->btree_allocator);
memSlabCreate(&hm->entry_allocator, hm->parent_memory, entry_count, sizeof(Hashmap_Entry));
}
// Return false to prematuerely stop iteration
#define HASHMAP_ITERATOR_FUNC(name) Bool name (void *funcdata, Hashmap_Entry *entry)
struct Hashmap_Iterator_Data_ {
HASHMAP_ITERATOR_FUNC((*func));
void *funcdata;
};
header_function
BTREE_ITERATOR_FUNC(hashmap_IterateNode)
{
unused_variable(key);
struct Hashmap_Iterator_Data_ *data = cast_val(struct Hashmap_Iterator_Data_ *, funcdata);
Hashmap_Entry *list = cast_val(Hashmap_Entry *, value);
for (Hashmap_Entry *e = list, *en; e != nullptr; e = en) {
en = e->next;
if ((data->func)(data->funcdata, e) == false) return false;
}
return true;
}
header_function
Bool hashmapIterate (Hashmap *hm, HASHMAP_ITERATOR_FUNC((*func)), void *funcdata)
{
struct Hashmap_Iterator_Data_ data = {
.func = func,
.funcdata = funcdata,
};
Bool result = btree_IterateNode(hm->btree.root, hashmap_IterateNode, &data);
return result;
}
header_function
HASHMAP_ITERATOR_FUNC(hashmap_DeleteNodeList)
{
Hashmap *hm = cast_val(Hashmap *, funcdata);
memSlabRemit(&hm->entry_allocator, entry);
return true;
}
header_function
void hashmapDelete (Hashmap *hm)
{
hashmapIterate(hm, hashmap_DeleteNodeList, hm);
memSlabDelete(&hm->entry_allocator);
memPushDelete(&hm->btree_allocator);
memzero(hm);
}
// Returns the existing data from BTree and saves out matched entry into matched pointer
header_function
Hashmap_Entry* hashmap_FindEntry (Hashmap *hm, Uint64 hash, void *key, Hashmap_Entry **matched)
{
Hashmap_Entry *list = cast_val(Hashmap_Entry *, btreeSearch(&hm->btree, hash));
for (Hashmap_Entry *ei = list; ei != nullptr; ei = ei->next) {
if (hm->compare_func(ei->key, key) == 0) {
if (matched) *matched = ei;
return list;
}
}
return list;
}
header_function
void hashmapInsert (Hashmap *hm, Uint64 hash, void *key, void *value)
{
Hashmap_Entry *matched = nullptr;
Hashmap_Entry *list = hashmap_FindEntry(hm, hash, key, &matched);
if (matched) {
matched->value = value;
return;
}
Hashmap_Entry *e = cast_ptr(Hashmap_Entry *, memSlabAllot(&hm->entry_allocator));
*e = compound_init(Hashmap_Entry, {
.next = list,
.hash = hash,
.key = key,
.value = value,
});
btreeSwitch(&hm->btree, hash, list, e);
}
header_function
void* hashmapSearch (Hashmap *hm, Uint64 hash, void *key)
{
Hashmap_Entry *matched = nullptr;
hashmap_FindEntry(hm, hash, key, &matched);
return matched ? matched->value : nullptr;
}
header_function
void hashmapRemove (Hashmap *hm, Uint64 hash, void *key)
{
Hashmap_Entry *matched = nullptr;
Hashmap_Entry *list = hashmap_FindEntry(hm, hash, key, &matched);
if (matched) {
if (list == matched) {
btreeSwitch(&hm->btree, hash, list, list->next);
} else {
for (Hashmap_Entry *e = list; e != nullptr; e = e->next) {
if (e->next == matched) {
Hashmap_Entry *old = e->next;
e->next = e->next->next;
memSlabRemit(&hm->entry_allocator, old);
return;
}
}
}
}
}
/******************************************************************************************
* Segmented Array (segar)
*
* Usage:
* typedef segarOf(int) Segar_Int;
* Segar_Int sgi = segarMake(&mempush);
* segarAdd(&sgi, 42);
* Uint32 sgi_len = segarLen(&sgi); // sgi_len == 1
* int val = segarGet(&sgi, 0); // val == 42
* int *ptr = segarGetPtr(&sgi, 0); // *ptr == 42
* int *data = segarAcquireContiguousBuffer(&sgi, &memory);
* segarReleaseContiguousBuffer(data);
*
* Misc Functions (useful when doing low-level operations):
* segarSegmentsCount(&sgi)
* segarSegment(&sgi, segment_index)
* segarSegmentLen(&sgi, segment_index)
* segarTypeSize(&sgi)
*
* Formulae Derivation:
* _________________________________________________________________________________________________
* | No. | Segment Index | Segment Size | Total Elements | Indices |
* |--------|-------------------|------------------|--------------------|--------------------------|
* | #1 | 0 | 1 | 1 | 0 |
* | #2 | 1 | 2 | 3 | 1 2 |
* | #3 | 2 | 4 | 7 | 3 4 5 6 |
* | #4 | 3 | 8 | 15 | 7 8 9 10 11 12 13 14 |
* | #5 | 4 | 16 | 31 | 15 16 17 18 19 20 21 22 |
* | | | | | 23 24 25 26 27 28 29 30 |
* |--------|-------------------|------------------|--------------------|--------------------------|
* | | si | 2^si | 2^(si+1) - 1 | [2^si - 1, 2^(si+1) - 2] |
* |--------|-------------------|------------------|--------------------|--------------------------|
*
* so, given an index I:
* seg = log2(I + 1); // which segment the index `I` falls into
* lo = (1u << seg) - 1; // The lowest index in that segment
* rel_idx = I - lo; // The relative index of element at `I` locally within the `seg` segment
*/
#define SEGAR_SEGS_ 31 /* Setting to 32 creates overflow in some bitwise formulae */
// Usage: typedef segarOf(int) Segar_Int;
#if defined(ENV_LANG_C)
# define segarOf(_type) struct { Memory_Push *mempush; Uint32 length; Byte _pad[4]; _type **segments; }
#elif defined(ENV_LANG_CXX)
template<typename T>
struct Segar_Template { Memory_Push *mempush; Uint32 length; Byte _pad[4]; T **segments; };
#define segarOf(_type) Segar_Template<_type>
#endif
pragma_msvc("warning ( push )");
pragma_msvc("warning ( disable: 4116 )"); // unnamed type definition in parentheses
static_assert(sizeof(segarOf(int)) == 24, "Invalid Segar size");
pragma_msvc("warning ( pop )");
// Usage: Segar_Int sgi = segarMake(&mempush);
// or: data.sgi = (typeof(data.sgi))segarMake(&mempush);
#define segarMake(_mempush_ptr) { .mempush = (_mempush_ptr) }
// Usage: Uint32 sgi_len = segarLen(&sgi);
#if defined(ENV_LANG_C)
# define segarLen(_segar_ptr) ((_segar_ptr)->length)
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarLen (const Segar_Template<T> *sp) { return sp->length; }
#endif
header_function Uint32 segar_GetSegmentIndexFromAbsoluteIndex (Uint32 abs_index);
header_function Uint32 segar_GetCountOfElementsFromSegmentIndex (Uint32 segment_index, Uint32 length);
header_function void* segar_GetPointerAtAbsoluteIndex (Memory_Push *mempush,
Uint32 length, Uint32 absolute_index, Uint32 type_size,
void ***segments);
header_function void* segar_CopyDataIntoBuffer (void *buffer, Uint32 length, Uint32 type_size, void **segments);
header_function void* segar_LinearizeDataIntoBuffer (Uint32 length, Uint32 type_size, void **segments,
Memory *memory);
header_function void segar_FreeLinearizedBuffer (void *buffer);
// Usage: int *ip = segarGetPtr(&sgi, 0);
#if defined(ENV_LANG_C)
# define segarGetPtr(_segar_ptr, _index) \
(cast_ptr(typeof((_segar_ptr)->segments[0]), \
(segar_GetPointerAtAbsoluteIndex((_segar_ptr)->mempush, \
segarLen(_segar_ptr), \
(_index), \
sizeof(*((_segar_ptr)->segments[0])), \
(void***)&((_segar_ptr)->segments)))))
#elif defined(ENV_LANG_CXX)
template<typename T>
T* segarGetPtr (const Segar_Template<T> *sp, Uint32 index)
{
pragma_clang("clang diagnostic push");
pragma_clang("clang diagnostic ignored \"-Wdeclaration-after-statement\"");
T *result = cast_ptr(T*, segar_GetPointerAtAbsoluteIndex(sp->mempush, segarLen(sp), index, sizeof(T),
cast_const(void***, cast_ptr(void**const*, &sp->segments))));
pragma_clang("clang diagnostic pop");
return result;
}
#endif
// Usage: int i = segarGet(&sgi, 0);
#if defined(ENV_LANG_C)
# define segarGet(_segar_ptr, _index) (*(segarGetPtr(_segar_ptr, _index)))
#elif defined(ENV_LANG_CXX)
template<typename T>
T segarGet (const Segar_Template<T> *sp, Uint32 index)
{
T *ptr = segarGetPtr(sp, index);
T result = *ptr;
return result;
}
#endif
// Usage: segarAdd(&sgi, 1);
#if defined(ENV_LANG_C)
# define segarAdd(_segar_ptr, _value) \
(*(segarGetPtr(_segar_ptr, segarLen(_segar_ptr))) = (_value), \
((_segar_ptr)->length++)) /* Using segar->length++ instead of segarLen(segar)++ to ensure the variable itself
is incremented and not some temporary value which might be returned by segarLen()
if the implementation of segarLen() changes */
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarAdd (Segar_Template<T> *sp, T value)
{
T *ptr = segarGetPtr(sp, segarLen(sp));
*ptr = value;
Uint32 result = sp->length++; // This weird return value is to make it match what C version above returns
return result;
}
#endif
// Usage: segarSpliceAt(&sgi, 0, -1);
#if defined(ENV_LANG_C)
# define segarSpliceAt(_segar_ptr, _index, _value) \
((*cast_ptr(typeof((_segar_ptr)->segments[0]), \
(segar_MoveRightFromIndex((_segar_ptr)->mempush, \
segarLen(_segar_ptr), \
(_index), \
sizeof(*((_segar_ptr)->segments[0])), \
(void***)&((_segar_ptr)->segments)))) = (_value)), \
((_segar_ptr)->length++)) /* Using segar->length++ instead of segarLen(segar)++ to ensure the variable itself
is incremented and not some temporary value which might be returned by segarLen()
if the implementation of segarLen() changes */
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarSpliceAt (Segar_Template<T> *sp, Uint32 index, T value)
{
T *ptr = cast_ptr(T*, segar_MoveRightFromIndex(sp->mempush, segarLen(sp), index, sizeof(T), cast_ptr(void***, &sp->segments)));
*ptr = value;
Uint32 result = sp->length++; // This weird return value is to make it match what C version above returns
return result;
}
#endif
// Usage: segarSpliceAt(&sgi, 0, -1);
#if defined(ENV_LANG_C)
# define segarDeleteAt(_segar_ptr, _index) \
((segar_MoveLeftOnIndex((_segar_ptr)->mempush, \
segarLen(_segar_ptr), \
(_index), \
sizeof(*((_segar_ptr)->segments[0])), \
(void***)&((_segar_ptr)->segments))), \
((_segar_ptr)->length--))
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarDeleteAt (Segar_Template<T> *sp, Uint32 index)
{
segar_MoveLeftOnIndex(sp->mempush, segarLen(sp), index, sizeof(T), cast_ptr(void***, &sp->segments));
Uint32 result = sp->length--; // This weird return value is to make it match what C version above returns
return result;
}
#endif
// int *data = segarAcquireContiguousBuffer(&sgi, &memory);
#if defined(ENV_LANG_C)
# define segarAcquireContiguousBuffer(_segar_ptr, _memory) \
segar_LinearizeDataIntoBuffer(segarLen(_segar_ptr), \
sizeof(*((_segar_ptr)->segments[0])), \
&((_segar_ptr)->segments), \
(_memory))
#elif defined(ENV_LANG_CXX)
template<typename T>
T* segarAcquireContiguousBuffer (const Segar_Template<T> *sp, Memory *memory)
{
T *result = cast_ptr(T*, segar_LinearizeDataIntoBuffer(segarLen(sp), sizeof(T), cast_ptr(void**, sp->segments), memory));
return result;
}
#endif
// segarReleaseContiguousBuffer(data);
#if defined(ENV_LANG_C)
# define segarReleaseContiguousBuffer(_buffer) segar_FreeLinearizedBuffer(_buffer)
#elif defined(ENV_LANG_CXX)
template<typename T>
void segarReleaseContiguousBuffer (T *buffer) { segar_FreeLinearizedBuffer(cast_ptr(void*, buffer)); }
#endif
#if defined(ENV_LANG_C)
# define segarSegmentsCount(_segar_ptr) (segar_GetSegmentIndexFromAbsoluteIndex(segarLen(_segar_ptr) - 1) + 1)
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarSegmentsCount (const Segar_Template<T> *sp)
{
Uint32 len = segarLen(sp);
Uint32 last_segment = segar_GetSegmentIndexFromAbsoluteIndex(len - 1) + 1;
return last_segment;
}
#endif
// int *data = segarAcquireContiguousPushBuffer(&sgi, &mempush);
#if defined(ENV_LANG_C)
# define segarAcquireContiguousPushBuffer(_segar_ptr, _mempush) \
segar_CopyDataIntoBuffer(memPushAllot((_mempush), \
(sizeof(*((_segar_ptr)->segments[0])) * \
segarLen(_segar_ptr))), \
segarLen(_segar_ptr), \
sizeof(*((_segar_ptr)->segments[0])), \
&((_segar_ptr)->segments))
#elif defined(ENV_LANG_CXX)
template<typename T>
T* segarAcquireContiguousPushBuffer (const Segar_Template<T> *sp, Memory_Push *mempush)
{
T *buffer = cast_ptr(typeof(buffer), memPushAllot(mempush, segarLen(sp) * sizeof(T)));
segar_CopyDataIntoBuffer(buffer, segarLen(sp), sizeof(T), cast_ptr(void**, sp->segments));
return buffer;
}
#endif
#if defined(ENV_LANG_C)
# define segarSegment(_segar_ptr, _seg_idx) ((_segar_ptr)->segments[(_seg_idx)])
#elif defined(ENV_LANG_CXX)
template<typename T>
T* segarSegment (const Segar_Template<T> *sp, Uint32 seg_idx)
{
T *result = sp->segments[seg_idx];
return result;
}
#endif
#if defined(ENV_LANG_C)
# define segarSegmentLen(_segar_ptr, _seg_idx) (segar_GetCountOfElementsFromSegmentIndex((_seg_idx), segarLen(_segar_ptr)))
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarSegmentLen (const Segar_Template<T> *sp, Uint32 seg_idx)
{
Uint32 len = segarLen(sp);
Uint32 seglen = segar_GetCountOfElementsFromSegmentIndex(seg_idx, len);
return seglen;
}
#endif
#if defined(ENV_LANG_C)
# define segarTypeSize(_segar_ptr) (sizeof((_segar_ptr)->segments[0][0]))
#elif defined(ENV_LANG_CXX)
template<typename T>
Uint32 segarTypeSize (const Segar_Template<T> *sp)
{
Uint32 size = sizeof(sp->segments[0][0]);
return size;
}
#endif
header_function Uint32 segar_GetSegmentIndexFromAbsoluteIndex (Uint32 abs_index) { Uint32 r = bitLog2(abs_index + 1); return r; }
header_function Uint32 segar_GetSegmentSizeFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << seg_index); return r; }
header_function Uint32 segar_GetTotalElementsFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << (seg_index + 1)) - 1; return r; }
header_function Uint32 segar_GetSegmentLowBoundFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << seg_index) - 1; return r; }
header_function Uint32 segar_GetSegmentHighBoundFromSegmentIndex (Uint32 seg_index) { Uint32 r = (1u << (seg_index + 1)) - 2; return r; }
header_function Uint32 segar_GetRelativeIndexFromAbsoluteIndex (Uint32 abs_index)
{
Uint32 seg_index = segar_GetSegmentIndexFromAbsoluteIndex(abs_index);
Uint32 seg_low = segar_GetSegmentLowBoundFromSegmentIndex(seg_index);
Uint32 rel_index = abs_index - seg_low;
return rel_index;
}
header_function
Uint32 segar_GetCountOfElementsFromSegmentIndex (Uint32 segment_index, Uint32 length)
{
Uint32 last_index = length - 1;
Uint32 lo = segar_GetSegmentLowBoundFromSegmentIndex(segment_index); // Inclusive
Uint32 rel_idx = last_index - lo; /* Relative index assuming that last_index was in segment,
might be larger than the segment's maximum length */
Uint32 hi = segar_GetSegmentHighBoundFromSegmentIndex(segment_index); // Inclusive
Uint32 length_segment = hi - lo; // If the entire segment was filled
Uint32 length_maximum = rel_idx; // If the segment was only partially filled, might be larger than the segment's maximum length
Uint32 length_actual = minimum(length_segment, length_maximum) + 1; /* Actual number of elements present,
+1 is due to the inclusivity */
return length_actual;
}
header_function
Uint32 segar_CheckAndGetSegmentIndex (Memory_Push *mempush, Uint32 length, Uint32 absolute_index, Uint32 type_size, void ***segments)
{
debugAssert(absolute_index <= length); // Equal for the segarAdd case
Uint32 xxx = segar_GetSegmentLowBoundFromSegmentIndex(SEGAR_SEGS_);
debugAssert(absolute_index <= xxx);
Uint32 segment_index = segar_GetSegmentIndexFromAbsoluteIndex(absolute_index);
debugAssert(segment_index < SEGAR_SEGS_);
if (*segments == nullptr) {
*segments = cast_ptr(void**, memPushAllot(mempush, SEGAR_SEGS_ * sizeof(**segments)));
}
if ((*segments)[segment_index] == nullptr) {
Uint32 segment_size = segar_GetSegmentSizeFromSegmentIndex(segment_index);
(*segments)[segment_index] = memPushAllot(mempush, type_size * segment_size);
}
return segment_index;
}
header_function
Uint32 segar_CheckAndGetRelativeIndex (Uint32 length, Uint32 absolute_index)
{
debugAssert(absolute_index <= length); // Equal for the segarAdd case
debugAssert(absolute_index <= segar_GetSegmentLowBoundFromSegmentIndex(SEGAR_SEGS_));
Uint32 segment_index = segar_GetSegmentIndexFromAbsoluteIndex(absolute_index);
debugAssert(segment_index < SEGAR_SEGS_);
Uint32 segment_size = segar_GetSegmentSizeFromSegmentIndex(segment_index);
Uint32 relative_index = segar_GetRelativeIndexFromAbsoluteIndex(absolute_index);
debugAssert(relative_index < segment_size);
return relative_index;
}
header_function
void* segar_GetPointerAtAbsoluteIndex (Memory_Push *mempush, Uint32 length, Uint32 absolute_index, Uint32 type_size, void ***segments)
{
Uint32 segment_index = segar_CheckAndGetSegmentIndex(mempush, length, absolute_index, type_size, segments);
Uint32 relative_index = segar_CheckAndGetRelativeIndex(length, absolute_index);
void *segment_ptr = (*segments)[segment_index];
Byte *segment_first_element_ptr = cast_ptr(Byte*, segment_ptr);
Byte *ptr = segment_first_element_ptr + (type_size * relative_index);
return cast_ptr(void*, ptr);
}
header_function
void* segar_MoveRightFromIndex (Memory_Push *mempush, Uint32 length, Uint32 pivot_index, Uint32 type_size, void ***segments)
{
debugAssert(pivot_index < length);
for (Uint32 i = length; i > pivot_index; i--) {
void *right_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i, type_size, segments);
void *left_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i - 1, type_size, segments);
memcpy(right_elem_ptr, left_elem_ptr, type_size);
}
void *result = segar_GetPointerAtAbsoluteIndex(mempush, length, pivot_index, type_size, segments);
memset(result, 0, type_size);
return result;
}
header_function
void segar_MoveLeftOnIndex (Memory_Push *mempush, Uint32 length, Uint32 pivot_index, Uint32 type_size, void ***segments)
{
debugAssert(pivot_index < length);
for (Uint32 i = pivot_index; i < length; i++) {
void *left_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i, type_size, segments);
void *right_elem_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, i + 1, type_size, segments);
memcpy(left_elem_ptr, right_elem_ptr, type_size);
}
void *rightmost_ptr = segar_GetPointerAtAbsoluteIndex(mempush, length, length - 1, type_size, segments);
memset(rightmost_ptr, 0, type_size);
}
header_function
void* segar_CopyDataIntoBuffer (void *buffer, Uint32 length, Uint32 type_size, void **segments)
{
Uint32 last_index = length - 1;
Uint32 total_segments = segar_GetSegmentIndexFromAbsoluteIndex(last_index);
for (Uint segidx = 0; segidx <= total_segments; segidx++) {
Uint32 length_to_copy = segar_GetCountOfElementsFromSegmentIndex(segidx, length);
Uint32 begin_offset = segar_GetSegmentLowBoundFromSegmentIndex(segidx) * type_size;
Uint32 total_size = length_to_copy * type_size; // +1 because of both `hi` and `rel_idx` being inclusive
memcpy(cast_ptr(Byte*, buffer) + begin_offset, segments[segidx], total_size);
}
return buffer; // To make the segarAcquireContiguousPushBuffer C macro work
}
header_function
void* segar_LinearizeDataIntoBuffer (Uint32 length, Uint32 type_size, void **segments, Memory *memory)
{
void *buffer = nullptr; {
void *allocation = memAllot(memory, (type_size * length) + sizeof(memory));
*cast_ptr(Memory**, allocation) = memory;
buffer = cast_ptr(Byte*, allocation) + sizeof(memory);
}
segar_CopyDataIntoBuffer(buffer, length, type_size, segments);
return buffer;
}
header_function
void segar_FreeLinearizedBuffer (void *buffer)
{
void *allocation = cast_ptr(Memory**, buffer) - 1;
Memory *memory = *cast_ptr(Memory**, allocation);
memRemit(memory, allocation);
}
/******************************************************************************************
* Serialization Helper Function
*/
typedef struct Srlz_Memory_Loader {
Byte *memory;
Size offset;
} Srlz_Memory_Loader;
header_function
SRLZ_FUNC(srlzMemoryLoad)
{
Srlz_Memory_Loader *loader = cast_ptr(typeof(loader), userdata);
Byte *buf = cast_ptr(typeof(buf), data);
memcpy(buf, loader->memory + loader->offset, size);
loader->offset += size;
return true;
}
typedef struct Srlz_Memory_Dumper {
segarOf(Byte) bytes;
} Srlz_Memory_Dumper;
header_function
SRLZ_FUNC(srlzMemoryDumper)
{
Srlz_Memory_Dumper *dumper = cast_ptr(typeof(dumper), userdata);
Byte *bytes = cast_ptr(Byte*, data);
for (Size i = 0; i < size; i++) {
segarAdd(&dumper->bytes, bytes[i]);
}
return true;
}
/******************************************************************************************
* Intrusive Circular Doubly Linked List
* Inspired from github.com/torvalds/linux/blob/master/include/linux/list.h
*/
typedef struct List_Node {
struct List_Node *next, *prev;
} List_Node;
/* To get the node (container struct) holding the linked list node */
#define listThisNode(nodeptr, type, member) containerof((nodeptr), type, member)
#define listNextNode(nodeptr, type, member) containerof((nodeptr)->next, type, member)
#define listPrevNode(nodeptr, type, member) containerof((nodeptr)->prev, type, member)
/*
* Initialize the list ike this:
* *****************************
* typedef struct N {
* List_Node l;
* } N;
*
* N n;
* n.l = listInit(n.l);
*/
#define listInit(name) compound_init(List_Node, {&(name), &(name)})
#define listReset(ptr) do{(ptr)->next = (ptr); (ptr)->prev = (ptr);}while(0)
header_function
void list_Add (List_Node *newnode, List_Node *prev, List_Node *next)
{
next->prev = newnode;
newnode->next = next;
newnode->prev = prev;
prev->next = newnode;
}
header_function
void listAddAfter (List_Node *newnode, List_Node *after_this)
{
list_Add(newnode, after_this, after_this->next);
}
header_function
void listAddBefore (List_Node *newnode, List_Node *before_this)
{
list_Add(newnode, before_this->prev, before_this);
}
header_function
void list_RemoveNodeBetween (List_Node * prev, List_Node * next)
{
next->prev = prev;
prev->next = next;
}
header_function
void listRemove (List_Node *entry)
{
list_RemoveNodeBetween(entry->prev, entry->next);
entry->next = nullptr;
entry->prev = nullptr;
}
header_function
void listRemoveAndInit (List_Node *entry)
{
list_RemoveNodeBetween(entry->prev, entry->next);
listReset(entry);
}
header_function
void listReplace(List_Node *old, List_Node *newnode)
{
newnode->next = old->next;
newnode->next->prev = newnode;
newnode->prev = old->prev;
newnode->prev->next = newnode;
}
header_function
void listReplaceAndInit(List_Node *old, List_Node *newnode)
{
listReplace(old, newnode);
listReset(old);
}
header_function
void listSwap(List_Node *entry1, List_Node *entry2)
{
List_Node *pos = entry2->prev;
listRemove(entry2);
listReplace(entry1, entry2);
if (pos == entry1) pos = entry2;
listAddAfter(entry1, pos);
}
header_function
void listMoveAfter (List_Node *list, List_Node *after_this)
{
list_RemoveNodeBetween(list->prev, list->next);
listAddAfter(list, after_this);
}
header_function
void listMoveBefore (List_Node *list, List_Node *before_this)
{
list_RemoveNodeBetween(list->prev, list->next);
listAddBefore(list, before_this);
}
header_function
Bool listIsEmpty (List_Node *node)
{
Bool result = (node->next == node);
return result;
}
// Splice in a List `list`, between the Nodes `node` and `node->next`
header_function
void list_Splice (List_Node *list, List_Node *node)
{
List_Node *first = list->next;
List_Node *last = list->prev;
List_Node *at = node->next;
first->prev = node;
node->next = first;
last->next = at;
at->prev = last;
}
// Splice in a List `list`, between the Nodes `node` and `node->next`
header_function
void listSplice (List_Node *list, List_Node *node)
{
if (!listIsEmpty(list)) list_Splice(list, node);
}
header_function
void listSpliceInit (List_Node *list, List_Node *node)
{
if (!listIsEmpty(list)) {
list_Splice(list, node);
listReset(list);
}
}
// NOTE(naman): The list head should be a placeholder terminal node with no associated metadata. Not doing this makes
// safe iteration very difficult and/or tedious. Trust me, I've wasted too much time trying various schemes out.
# define LIST_FOR_EACH(_idx, _head) \
for (List_Node \
* _idx = (_head)->next, \
* _idx##_2_ = (_head)->next->next; \
_idx != (_head); \
_idx = _idx##_2_, _idx##_2_ = _idx##_2_ -> next)
# define LIST_FOR_EACH_REVERSE(_idx, _head) \
for (List_Node \
* _idx = (_head)->prev, \
* _idx##_2_ = (_head)->prev->prev; \
_idx != (_head); \
_idx = _idx##_2_, _idx##_2_ = _idx##_2_ -> prev)
/******************************************************************************************
* Vinyas (.viny)
*
* Text Serialization Format
* **** ************* ******
*
* Syntax
* ======
* (inspired by SDLang/KDL)
*
* Viny := Entry*
* Entry := Key Tuple
* | Key '[' Size ']' Array
* | Key Value
* Tuple := '{' Viny '}'
* Array := '{' (Tuple | Value)* '}'
* Value := '"' Char* '"'
* | Uint64
* | Sint64
* | Float64
* Key := Identifier
* Size := Uint64
*
* Dump API (Write)
* ================
* (inspired by Tsoding's jim.h)
*
* Viny_Dump vdCreate (Txt_Fmt_Pool *tp, Memory_Push *mempush)
* Txt vdFinish (Viny_Dump *v)
*
* Viny_Dump_Tuple* vdRootTuple (Viny_Dump *v)
*
* Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Tuple *vt, const Char *name)
* Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Array *va)
* void vdTupleEnd (Viny_Dump_Tuple **vtp)
*
* Viny_Dump_Array* vdArrayBegin (Viny_Dump_Tuple *vt, const Char *name) // If size is unknown, pass zero
* void vdArrayEnd (Viny_Dump_Array **vtp)
*
* void vdSint (Viny_Dump_Tuple *vt, const Char *key, Sint64 val)
* void vdSint (Viny_Dump_Array *va, Sint64 val)
*
* void vdUint (Viny_Dump_Tuple *vt, const Char *key, Uint64 val)
* void vdUint (Viny_Dump_Array *va, Uint64 val)
*
* void vdFloat (Viny_Dump_Tuple *vt, const Char *key, Float64 val)
* void vdFloat (Viny_Dump_Array *va, Float64 val)
*
* void vdString (Viny_Dump_Tuple *vt, const Char *key, const Char *val)
* void vdString (Viny_Dump_Array *va, const Char *val)
*
* Load API (Read)
* ===============
* (inspired by Tsoding's jimp.h)
*
* Viny_Load vlMake (Txt str, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data)
*
* Viny_Load vlIsLeft (Viny_Load *v)
*
* Viny_Load_Tuple* vlRootTuple (Viny_Load *v)
*
* Viny_Load_Tuple* vlTupleBegin (Viny_Load_Tuple *vt, const Char *name)
* Viny_Load_Tuple* vlTupleBegin (Viny_Load_Array *va)
* Bool vlTupleEnd (Viny_Load_Tuple *vt)
*
* Viny_Load_Array* vlArrayBegin (Viny_Load_Tuple *vt, const Char *name)
* Bool vlArrayEnd (Viny_Load_Array *va)
*
* void vlFailed (Viny_Load *v) // Increment by one token (or by an entire open-close brace group)
*
* void vlSint (Viny_Load_Tuple *vt, const Char *key, Sint64 *val)
* void vlSint (Viny_Load_Array *va, Sint64 *val)
*
* void vlUint (Viny_Load_Tuple *vt, const Char *key, Uint64 *val)
* void vlUint (Viny_Load_Array *va, Uint64 *val)
*
* void vlFloat (Viny_Load_Tuple *vt, const Char *key, Float64 *val)
* void vlFloat (Viny_Load_Array *va, Float64 *val)
*
* void vlString (Viny_Load_Tuple *vt, const Char *key, Txt_Span *val)
* void vlString (Viny_Load_Array *va, Txt_Span *val)
*
* Usage
* -----
*
Viny_Load v = vlMake(txtNewL(&mempush, str, len), func, data);
while (vlIsLeft(&v)) {
Viny_Load_Tuple *vt_root = vlRootTuple(&v);
Viny_Load_Tuple *vt_tuple = nullptr;
Viny_Load_Array *va_array = nullptr;
if ((vt_tuple = vlTupleBegin(vt_root, "tuple"))) {
// pre-initialize stuff here...
while (!vlTupleEnd(&vt_tuple)) {
Txt_Span string = {0};
Uint64 integer = {0};
if (vlString(vt_tuple, "string", &string)) {
// process string here...
} else if (vlUint(vt_tuple, "number", integer)) {
// process integer here ...
} else vlFailed(&v);
}
// post-process after parsing the tuple here...
} else if ((va_array = vlArrayBegin(vt_root, "array"))) {
// same as above ...
} else vlFailed(&v);
// post-process after parsing the root here...
}
*
*/
# define VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(_1,_2,_NAME,...) _NAME
typedef struct Viny_Dump {
Txt_Fmt fmt;
Memory_Push *mempush;
Sint indent;
Byte _pad[4];
} Viny_Dump;
typedef struct Viny_Dump_Tuple Viny_Dump_Tuple;
typedef struct Viny_Dump_Array Viny_Dump_Array;
header_function
Viny_Dump vdCreate (Txt_Fmt_Pool *tp, Memory_Push *mempush)
{
Viny_Dump viny = {
.fmt = txtfmtCreate(tp),
.mempush = mempush,
};
return viny;
}
header_function
Txt vdFinish (Viny_Dump *v)
{
Txt result = txtfmtFinish(&v->fmt, v->mempush);
memzero(v);
return result;
}
// NOTE(naman): The first argument taken by these functions is always Viny_Dump,
// but cast into various pointer types to enforce type safety, and
// Vinyasa syntax through that.
header_function
Viny_Dump_Tuple* vdRootTuple (Viny_Dump *v)
{
return cast_ptr(Viny_Dump_Tuple*, v);
}
header_function
Viny_Dump_Tuple* vdTupleBegin_InTuple (Viny_Dump_Tuple *vt, const Char *name)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
txtfmtAppendF(&v->fmt, "%*s%s {\n", 4 * v->indent, "", name);
v->indent++;
return vt;
}
header_function
Viny_Dump_Tuple* vdTupleBegin_InArray (Viny_Dump_Array *va)
{
Viny_Dump *v = cast_ptr(typeof(v), va);
txtfmtAppendF(&v->fmt, "%*s{\n", 4 * v->indent, "");
v->indent++;
return cast_ptr(Viny_Dump_Tuple*, va);
}
#if defined(ENV_LANG_C)
# define vdTupleBegin_1(_vv) \
_Generic((_vv) \
, Viny_Dump_Tuple* : vdTupleBegin_InTuple \
, Viny_Dump_Array* : vdTupleBegin_InArray \
)(_vv)
# define vdTupleBegin_2(_vv, _name) \
_Generic((_vv) \
, Viny_Dump_Tuple* : vdTupleBegin_InTuple \
, Viny_Dump_Array* : vdTupleBegin_InArray \
)(_vv, _name)
#define vdTupleBegin(...) VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(__VA_ARGS__, vdTupleBegin_2, vdTupleBegin_1)(__VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Tuple *vt, const Char *name) { return vdTupleBegin_InTuple(vt, name); }
header_function Viny_Dump_Tuple* vdTupleBegin (Viny_Dump_Array *va) { return vdTupleBegin_InArray(va); }
#endif
header_function
void vdTupleEnd (Viny_Dump_Tuple **vtp)
{
Viny_Dump *v = cast_ptr(typeof(v), *vtp);
v->indent--;
txtfmtAppendF(&v->fmt, "%*s}\n", 4 * v->indent, "");
*vtp = nullptr;
return;
}
header_function
Viny_Dump_Array* vdArrayBegin (Viny_Dump_Tuple *vt, const Char *name)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
txtfmtAppendF(&v->fmt, "%*s%s[] {\n", 4 * v->indent, "", name);
v->indent++;
return cast_ptr(Viny_Dump_Array*, vt);
}
header_function
void vdArrayEnd (Viny_Dump_Array **vap)
{
Viny_Dump *v = cast_ptr(typeof(v), *vap);
v->indent--;
txtfmtAppendF(&v->fmt, "%*s}\n", 4 * v->indent, "");
*vap = nullptr;
return;
}
header_function
void vdSint_Tuple (Viny_Dump_Tuple *vt, const Char *key, Sint64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
debugAssert(key && strlen(key));
txtfmtAppendF(&v->fmt, "%*s%s %lld\n", 4 * v->indent, "", key, val);
}
header_function
void vdSint_Array (Viny_Dump_Array *va, Sint64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), va);
txtfmtAppendF(&v->fmt, "%*s%lld\n", 4 * v->indent, "", val);
}
#if defined(ENV_LANG_C)
# define vdSint(_vv, ...) \
_Generic((_vv) \
, Viny_Dump_Tuple *: vdSint_Tuple \
, Viny_Dump_Array *: vdSint_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function void vdSint (Viny_Dump_Tuple *vt, const Char *key, Sint64 val) { return vdSint_Tuple(vt, key, val); }
header_function void vdSint (Viny_Dump_Array *va, Sint64 val) { return vdSint_Array(va, val); }
#endif
header_function
void vdUint_Tuple (Viny_Dump_Tuple *vt, const Char *key, Uint64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
debugAssert(key && strlen(key));
txtfmtAppendF(&v->fmt, "%*s%s %llu\n", 4 * v->indent, "", key, val);
}
header_function
void vdUint_Array (Viny_Dump_Array *va, Uint64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), va);
txtfmtAppendF(&v->fmt, "%*s%llu\n", 4 * v->indent, "", val);
}
#if defined(ENV_LANG_C)
# define vdUint(_vv, ...) \
_Generic((_vv) \
, Viny_Dump_Tuple *: vdUint_Tuple \
, Viny_Dump_Array *: vdUint_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function void vdUint (Viny_Dump_Tuple *vt, const Char *key, Uint64 val) { return vdUint_Tuple(vt, key, val); }
header_function void vdUint (Viny_Dump_Array *va, Uint64 val) { return vdUint_Array(va, val); }
#endif
header_function
void vdFloat_Tuple (Viny_Dump_Tuple *vt, const Char *key, Float64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
debugAssert(key && strlen(key));
txtfmtAppendF(&v->fmt, "%*s%s %f <%a> /* Remove the hex if editing decimal */\n", 4 * v->indent, "", key, val, val);
}
header_function
void vdFloat_Array (Viny_Dump_Array *va, Float64 val)
{
Viny_Dump *v = cast_ptr(typeof(v), va);
txtfmtAppendF(&v->fmt, "%*s%f <%a> /* Remove the hex if editing decimal */\n", 4 * v->indent, "", val, val);
}
#if defined(ENV_LANG_C)
# define vdFloat(_vv, ...) \
_Generic((_vv) \
, Viny_Dump_Tuple *: vdFloat_Tuple \
, Viny_Dump_Array *: vdFloat_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function void vdFloat (Viny_Dump_Tuple *vt, const Char *key, Float64 val) { return vdFloat_Tuple(vt, key, val); }
header_function void vdFloat (Viny_Dump_Array *va, Float64 val) { return vdFloat_Array(va, val); }
#endif
header_function void vd_StringHelper (Viny_Dump *v, const Char *val);
header_function
void vdString_Tuple (Viny_Dump_Tuple *vt, const Char *key, const Char *val)
{
Viny_Dump *v = cast_ptr(typeof(v), vt);
debugAssert(key && strlen(key));
txtfmtAppendF(&v->fmt, "%*s%s \"", 4 * v->indent, "", key);
if (val) vd_StringHelper(v, val);
txtfmtAppendF(&v->fmt, "\"\n");
}
header_function
void vdString_Array (Viny_Dump_Array *va, const Char *val)
{
Viny_Dump *v = cast_ptr(typeof(v), va);
txtfmtAppendF(&v->fmt, "%*s\"", 4 * v->indent, "");
if (val) vd_StringHelper(v, val);
txtfmtAppendF(&v->fmt, "\"\n");
}
#if defined(ENV_LANG_C)
# define vdString(_vv, ...) \
_Generic((_vv) \
, Viny_Dump_Tuple *: vdString_Tuple \
, Viny_Dump_Array *: vdString_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function void vdString (Viny_Dump_Tuple *vt, const Char *key, const Char *val) { return vdString_Tuple(vt, key, val); }
header_function void vdString (Viny_Dump_Array *va, const Char *val) { return vdString_Array(va, val); }
#endif
header_function
void vd_StringHelper (Viny_Dump *v, const Char *val)
{
Sint begin = 0, end = 0;
while (val[end]) {
switch (val[end]) { // Skip any special characters (since we don't want to be handling them while parsing)
case '\'': case '\"': case '\?': case '\\': case '\a':
case '\b': case '\f': case '\n': case '\r': case '\t':
case '\v': {
if (end > begin) {
txtfmtAppendF(&v->fmt, "%.*s", end - begin, val + begin);
}
end++;
begin = end;
} break;
default: end++; break;
}
}
if (end > begin) {
txtfmtAppendF(&v->fmt, "%.*s", end - begin, val + begin);
}
}
typedef struct Viny_Load {
Txt txt;
Tokenizer toker;
} Viny_Load;
typedef struct Viny_Load_Tuple Viny_Load_Tuple;
typedef struct Viny_Load_Array Viny_Load_Array;
header_function
Viny_Load vlMake (Txt str, TOKENIZER_ERROR_FUNC((*err_func)), void *err_data)
{
Viny_Load viny = {
.txt = str,
.toker = tokenizerMake(str, err_func, err_data),
};
return viny;
}
header_function
Bool vlIsLeft (Viny_Load *v)
{
Bool result = tokenizerPeekToken(&v->toker).kind != Token_Kind_EOF;
return result;
}
header_function
Viny_Load_Tuple* vlRootTuple (Viny_Load *v)
{
return cast_ptr(Viny_Load_Tuple*, v);
}
// NOTE(naman): The first argument taken by these functions is always Viny_Load,
// but cast into various pointer types to enforce type safety, and
// Vinyasa syntax through that.
// NOTE(naman): All of these functions will restore the tokenizer state if
// they are returning false. In case of failure to parse, we
// move to next token only in the vl's tokenizerGetToken().
header_function
Viny_Load_Tuple* vlTupleBegin_InTuple (Viny_Load_Tuple *vt, const Char *key)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched;
return vt;
not_matched:
v->toker = toker_copy;
return nullptr;
}
header_function
Viny_Load_Tuple* vlTupleBegin_InArray (Viny_Load_Array *va)
{
Viny_Load *v = cast_ptr(typeof(v), va);
Tokenizer toker_copy = v->toker;
if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched;
return cast_ptr(Viny_Load_Tuple*, va);
not_matched:
v->toker = toker_copy;
return nullptr;
}
#if defined(ENV_LANG_C)
# define vlTupleBegin_1(_vv) \
_Generic((_vv) \
, Viny_Dump_Tuple* : vlTupleBegin_InTuple \
, Viny_Dump_Array* : vlTupleBegin_InArray \
)(_vv)
# define vlTupleBegin_2(_vv, _key) \
_Generic((_vv) \
, Viny_Dump_Tuple* : vlTupleBegin_InTuple \
, Viny_Dump_Array* : vlTupleBegin_InArray \
)(_vv, _key)
#define vlTupleBegin(...) VINY_TUPLE_BEGIN_SELECTOR_INTERNAL_(__VA_ARGS__, vlTupleBegin_2, vlTupleBegin_1)(__VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Viny_Load_Tuple* vlTupleBegin (Viny_Load_Tuple *vt, const Char *key) { return vlTupleBegin_InTuple(vt, key); }
header_function Viny_Load_Tuple* vlTupleBegin (Viny_Load_Array *va) { return vlTupleBegin_InArray(va); }
#endif
header_function
Bool vlTupleEnd (Viny_Load_Tuple **vtp)
{
Viny_Load *v = cast_ptr(typeof(v), *vtp);
if ((tokenizerPeekToken(&v->toker).kind == '}') || (tokenizerPeekToken(&v->toker).kind == Token_Kind_EOF)) {
tokenizerGetToken(&v->toker);
*vtp = nullptr;
return true;
}
return false;
}
header_function
Viny_Load_Array* vlArrayBegin (Viny_Load_Tuple *vt, const Char *key)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
if (tokenizerGetToken(&v->toker).kind != '[') goto not_matched;
if (tokenizerGetToken(&v->toker).kind != ']') goto not_matched;
if (tokenizerGetToken(&v->toker).kind != '{') goto not_matched;
return cast_ptr(Viny_Load_Array*, vt);
not_matched:
v->toker = toker_copy;
return nullptr;
}
header_function
Bool vlArrayEnd (Viny_Load_Array **vap)
{
Viny_Load *v = cast_ptr(typeof(v), *vap);
if ((tokenizerPeekToken(&v->toker).kind == '}') || (tokenizerPeekToken(&v->toker).kind == Token_Kind_EOF)) {
tokenizerGetToken(&v->toker);
*vap = nullptr;
return true;
}
return false;
}
header_function
void vlFailed (Viny_Load *v)
{
// Skip one token, and if that token was an open brace, skip until the matching close brace
if (tokenizerGetToken(&v->toker).kind == '{') {
Uint open_brace_count = 1;
while (open_brace_count && vlIsLeft(v)) {
Token tok = tokenizerGetToken(&v->toker);
if (tok.kind == '{') {
open_brace_count++;
} else if (tok.kind == '}') {
open_brace_count--;
}
}
}
}
header_function
Bool vlSint_Helper (Viny_Load *v, Sint64 *valptr)
{
Token valtok = tokenizerGetToken(&v->toker);
Bool negative = false;
if (valtok.kind == '-') {
negative = true;
valtok = tokenizerGetToken(&v->toker);
}
if (valtok.kind != Token_Kind_CONSTANT_INTEGER) return false;
Uint64 value = maximum(valtok.value.integer, cast_val(Uint64, INT64_MAX));
*valptr = cast_val(Sint64, value);
if (negative) {
*valptr = -(*valptr);
}
return true;
}
header_function
Bool vlSint_Tuple (Viny_Load_Tuple *vt, const Char *key, Sint64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
if (!vlSint_Helper(v, valptr)) goto not_matched;
return true;
not_matched:
v->toker = toker_copy;
return false;
}
header_function
Bool vlSint_Array (Viny_Load_Array *vt, Sint64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
if (!vlSint_Helper(v, valptr)) goto not_matched;
return true;
not_matched:
v->toker = toker_copy;
return false;
}
#if defined(ENV_LANG_C)
# define vlSint(_vv, ...) \
_Generic((_vv) \
, Viny_Load_Tuple *: vlSint_Tuple \
, Viny_Load_Array *: vlSint_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Bool vlSint (Viny_Load_Tuple *vt, const Char *key, Sint64 *val) { return vlSint_Tuple(vt, key, val); }
header_function Bool vlSint (Viny_Load_Array *va, Sint64 *val) { return vlSint_Array(va, val); }
#endif
header_function
Bool vlUint_Tuple (Viny_Load_Tuple *vt, const Char *key, Uint64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
Token valtok = tokenizerGetToken(&v->toker);
if (valtok.kind != Token_Kind_CONSTANT_INTEGER) goto not_matched;
*valptr = valtok.value.integer;
return true;
not_matched:
v->toker = toker_copy;
return false;
}
header_function
Bool vlUint_Array (Viny_Load_Array *va, Uint64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), va);
Tokenizer toker_copy = v->toker;
Token valtok = tokenizerGetToken(&v->toker);
if (valtok.kind != Token_Kind_CONSTANT_INTEGER) goto not_matched;
*valptr = valtok.value.integer;
return true;
not_matched:
v->toker = toker_copy;
return false;
}
#if defined(ENV_LANG_C)
# define vlUint(_vv, ...) \
_Generic((_vv) \
, Viny_Load_Tuple *: vlUint_Tuple \
, Viny_Load_Array *: vlUint_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Bool vlUint (Viny_Load_Tuple *vt, const Char *key, Uint64 *val) { return vlUint_Tuple(vt, key, val); }
header_function Bool vlUint (Viny_Load_Array *va, Uint64 *val) { return vlUint_Array(va, val); }
#endif
header_function
Bool vlFloat_Tuple (Viny_Load_Tuple *vt, const Char *key, Float64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
Token valdectok = tokenizerGetToken(&v->toker);
if (valdectok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched;
*valptr = valdectok.value.floating;
if (tokenizerGetToken(&v->toker).kind == '<') {
Token valhextok = tokenizerGetToken(&v->toker);
if (valhextok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched;
if (tokenizerGetToken(&v->toker).kind != '>') goto not_matched;
*valptr = valhextok.value.floating;
}
return true;
not_matched:
v->toker = toker_copy;
return false;
}
header_function
Bool vlFloat_Array (Viny_Load_Array *va, Float64 *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), va);
Tokenizer toker_copy = v->toker;
Token valdectok = tokenizerGetToken(&v->toker);
if (valdectok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched;
*valptr = valdectok.value.floating;
if (tokenizerGetToken(&v->toker).kind == '<') {
Token valhextok = tokenizerGetToken(&v->toker);
if (valhextok.kind != Token_Kind_CONSTANT_FLOATING) goto not_matched;
if (tokenizerGetToken(&v->toker).kind != '>') goto not_matched;
*valptr = valhextok.value.floating;
}
return true;
not_matched:
v->toker = toker_copy;
return false;
}
#if defined(ENV_LANG_C)
# define vlFloat(_vv, ...) \
_Generic((_vv) \
, Viny_Load_Tuple *: vlFloat_Tuple \
, Viny_Load_Array *: vlFloat_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Bool vlFloat (Viny_Load_Tuple *vt, const Char *key, Float64 *val) { return vlFloat_Tuple(vt, key, val); }
header_function Bool vlFloat (Viny_Load_Array *va, Float64 *val) { return vlFloat_Array(va, val); }
#endif
header_function
Bool vlString_Tuple (Viny_Load_Tuple *vt, const Char *key, Txt_Span *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), vt);
Tokenizer toker_copy = v->toker;
Token keytok = tokenizerGetToken(&v->toker);
if (keytok.kind != Token_Kind_IDENTIFIER) goto not_matched;
if (!txtspanIsEqualStr(keytok.span, key)) goto not_matched;
Token valtok = tokenizerGetToken(&v->toker);
if (valtok.kind != Token_Kind_CONSTANT_STRING) goto not_matched;
*valptr = valtok.span;
valptr->begin++; // Skip the opening double-quote
valptr->end--; // Ignore the closing double-quote
return true;
not_matched:
v->toker = toker_copy;
return false;
}
header_function
Bool vlString_Array (Viny_Load_Array *va, Txt_Span *valptr)
{
Viny_Load *v = cast_ptr(typeof(v), va);
Tokenizer toker_copy = v->toker;
Token valtok = tokenizerGetToken(&v->toker);
if (valtok.kind != Token_Kind_CONSTANT_STRING) goto not_matched;
*valptr = valtok.span;
valptr->begin++; // Skip the opening double-quote
valptr->end--; // Ignore the closing double-quote
return true;
not_matched:
v->toker = toker_copy;
return false;
}
#if defined(ENV_LANG_C)
# define vlString(_vv, ...) \
_Generic((_vv) \
, Viny_Load_Tuple *: vlString_Tuple \
, Viny_Load_Array *: vlString_Array \
)(_vv, __VA_ARGS__)
#elif defined(ENV_LANG_CXX)
header_function Bool vlString (Viny_Load_Tuple *vt, const Char *key, Txt_Span *val) { return vlString_Tuple(vt, key, val); }
header_function Bool vlString (Viny_Load_Array *va, Txt_Span *val) { return vlString_Array(va, val); }
#endif
/******************************************************************************************
* Path
*/
typedef struct Path {
Txt _data; // The type her eshould be OSString of some kind (maybe WTF-8)?
} Path;
typedef segarOf(const Path*) Paths;
# if defined(ENV_OS_WINDOWS)
# define PATH_SEPARATOR '\\'
# elif defined(ENV_OS_LINUX)
# define PATH_SEPARATOR '/'
# endif
header_function
const Char* pathGetStr (const Path *path)
{
debugAssert(path != nullptr);
if (path == nullptr) return nullptr;
const Char *result = txtStr(path->_data);
return result;
}
header_function
Size pathGetStrlen (const Path *path)
{
debugAssert(path != nullptr);
if (path == nullptr) return 0;
Size result = txtLen(path->_data);
return result;
}
// NOTE(naman): When creating a new path using pathCreate or pathJoin, if the last character is
// a separator, then the returned path also ends with a valid separator.
header_function
const Path* pathCreateL (Memory_Push *mempush, const Char *dirty_path, Size dirty_len)
{
Txt clean_path = txt_Allocate(mempush, dirty_len + 1);
Size clean_idx = 0;
for (Size dirty_idx = 0; dirty_idx < dirty_len; dirty_idx++, clean_idx++) {
Bool separator_found = false;
while ((dirty_path[dirty_idx] == '\\') || (dirty_path[dirty_idx] == '/')) {
separator_found = true;
dirty_idx++;
}
if (separator_found) {
clean_path.str[clean_idx] = PATH_SEPARATOR;
dirty_idx--; // dirty_idx is pointing to the first character after the separators
} else {
clean_path.str[clean_idx] = dirty_path[dirty_idx];
}
}
Txt_Header_ *hdr = containerof(clean_path.str, Txt_Header_, str);
hdr->len = cast_val(Uint32, clean_idx);
Path *result = cast_ptr(typeof(result), memPushAllot(mempush, sizeof(*result)));
result->_data = clean_path;
return result;
}
header_function
const Path* pathCreate (Memory_Push *mempush, const Char *dirty_path)
{
Size dirty_len = strlen(dirty_path);
const Path *result = pathCreateL(mempush, dirty_path, dirty_len);
return result;
}
#define pathJoin(_tfp, _mp, ...) path_Join((_tfp), (_mp), __VA_ARGS__, nullptr)
header_function
const Path* path_Join (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *root, ...)
{
Txt_Fmt fmt = txtfmtCreate(tfp);
txtfmtAppendC(&fmt, pathGetStr(root));
{
va_list args;
va_start(args, root);
const Char *p = va_arg(args, const Char *);
while (p) {
txtfmtAppendF(&fmt, "%c%s", PATH_SEPARATOR, p);
p = va_arg(args, const Char *);
}
va_end(args);
}
Char *path = txtStr(txtfmtFinish(&fmt, mempush));
const Path* result = pathCreate(mempush, path);
return result;
}
header_function
Txt pathGetName (Memory_Push *mempush, const Path *path)
{
const Char *last_separator = nullptr; {
pragma_msvc("warning ( push )");
pragma_msvc("warning ( disable: 6293 )"); // Ill-defined for loop
// The loop starts with "-2" since the last character being separator should be ignored anyways, and
// if it is not a separator, then no harm no foul.
for (Size i = pathGetStrlen(path) - 2; i < pathGetStrlen(path); i--) {
if (pathGetStr(path)[i] == PATH_SEPARATOR) {
last_separator = pathGetStr(path) + i + 1;
break;
}
}
pragma_msvc("warning ( pop )");
}
if (last_separator == nullptr) { // No separator found, the path may just be a single filename with no directory
last_separator = pathGetStr(path);
}
Txt name = txtNew(mempush, last_separator);
return name;
}
header_function
const Path* pathGetParent (Memory_Push *mempush, const Path *path)
{
// Get pointer to the last character
const Char *end_ptr = pathGetStr(path) + pathGetStrlen(path) - 1;
// Move one step back if the last character is a separator
if (*end_ptr == PATH_SEPARATOR) {
if ((pathGetStrlen(path) > 1) &&
(end_ptr[0] != ':') &&
(end_ptr[-1] != ':')) { // To avoid the edge case of root directory on Windows or Unix
end_ptr--;
} else {
return nullptr;
}
}
for (const Char *begin_ptr = pathGetStr(path); (begin_ptr != end_ptr) && (*end_ptr != PATH_SEPARATOR); end_ptr--);
if (end_ptr == pathGetStr(path)) {
return nullptr;
} else {
debugAssert(*end_ptr == PATH_SEPARATOR);
// NOTE(naman): pathCreate calls path_EnsureParent. That function then calls this function.
// So, if we were to try to create a path using pathCreate below, we would end
// up in a circular loop trying to get the parent of the parent of the parent
// and so on until we reached root and this function returned NULL.
// To avoid that, we manually create the Path* variable instead of going
// through the pathCreate route. It should be okay, since the argument is
// also a Path* variable, so it must have already gotten cleaned up having
// gone through the pathCreate gauntlet.
const Path *result = pathCreateL(mempush, pathGetStr(path), cast_val(Size, end_ptr - pathGetStr(path)));
return result;
}
}
header_function
Txt pathCanonicalize (Memory_Push *mempush, const Path *path)
{
Txt copy = txtNew(mempush, pathGetStr(path));
for (Size i = 0; i < txtLen(copy); i++) {
if ((txtStr(copy)[i] == '/') || (txtStr(copy)[i] == '\\')) {
txtStr(copy)[i] = '/';
}
}
return copy;
}
pragma_clang("clang diagnostic pop");
pragma_msvc("warning ( pop )");
#define STD_H_INCLUDE_GUARD
#endif
/*
* Creator: Naman Dixit
* Notice: © Copyright 2025 Naman Dixit
*/
#pragma once
#include "std.h"
/* Memory ***********************************************************************/
MEMORY_ALLOT_FUNC(osMemoryAllot);
MEMORY_REMIT_FUNC(osMemoryRemit);
/* Console Output ***********************************************************************/
void osLogOut (const Char *fmt, ...);
void osLogErr (const Char *fmt, ...);
/* Assertion ***********************************************************************/
Bool os_Assert(const Char *filename, Sint line, const Char *funcname, Bool condition, const Char *condition_str, const Char *fmt, ...);
# define osAssert(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__)
#if defined(BUILD_INTERNAL)
# define osAssertInternal(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__)
#else
# define osAssertInternal(_cond, _fmt, ...) (void)(_msg), (void)(_cond)
#endif
#if defined(BUILD_SLOW)
# define osAssertSlow(_cond, _fmt, ...) os_Assert(__FILE__, __LINE__, __func__, !!(_cond), #_cond, (_fmt) __VA_OPT__(,) __VA_ARGS__)
#else
# define osAssertSlow(_cond, _fmt, ...) (void)(_msg), (void)(_cond)
#endif
/* File System ***********************************************************************/
const Path* osGetProgramDirectory (Memory_Push *mempush);
const Path* osGetWorkingDirectory (Memory_Push *mempush);
const Path* osGetSettingDirectory (Memory_Push *mempush, const Char *organization, const Char *application);
Bool osCreateDirectory (const Path *path);
Bool osPathMove (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *oldpath, const Path *newpath);
Bool osPathCopy (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *source, const Path *destination);
Bool osPathDelete (const Path *path);
Bool osDoesPathExists (const Path *path);
Uint64 osPathGetChangedTime (const Path *path);
typedef struct OS_Path_Components {
const Path *path;
Txt name;
const Path *parent;
const Path *root;
Txt path_relative_to_root;
} OS_Path_Components;
typedef enum OS_Path_Recursor_Result {
OS_Path_Recursor_Result_CONTINUE,
OS_Path_Recursor_Result_SUCCESS,
OS_Path_Recursor_Result_FAILURE,
} OS_Path_Recursor_Result;
#define OS_PATH_RECURSOR_FUNC(_name) \
OS_Path_Recursor_Result \
_name (Txt_Fmt_Pool *tfp, Memory_Push *mempush, \
const OS_Path_Components *iter, \
Bool is_folder, /* true=folder, false=file*/ \
void *userdata)
Bool osPathRecursorLaunch (Txt_Fmt_Pool *tfp, Memory_Push *mempush,
const Path *folder, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata);
Bool osPathRecursorRecurse (Txt_Fmt_Pool *tfp, Memory_Push *mempush,
const OS_Path_Components *iter, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata);
typedef segarOf(OS_Path_Components) OS_Path_Collection;
Bool osCollectFilePaths (Txt_Fmt_Pool *tfp, Memory_Push *mempush,
const Path *root, const Txt prefix, const Txt suffix, Bool recursive,
OS_Path_Collection *output_file_list);
void* osFileLoad (Memory *memory, const Path *path, Size *size);
Bool osFileDump (void *data, Size size, const Path *path);
/* Process Control ***********************************************************************/
typedef struct OS_Command {
Memory_Push *mempush;
segarOf(const Char *) argv;
} OS_Command;
OS_Command osCommandInit (Memory_Push *mempush);
void osCommandAppend (OS_Command *cmd, const Char *arg);
Sint osCommandExecute (OS_Command *cmd, Memory_Push *mempush, Txt *output);
/* Environment *************************************************************************/
const Char* osEnvironmentVariableGet(const Char *key);
const Char* osEnvironmentVariableSet(const Char *key, const Char *val, Bool overwrite);
/* Time *************************************************************************/
typedef struct OS_Time {
Uint16 year;
Uint8 month; // [1, 12]
Uint8 day; // [1, 31]
Uint8 hour; // [0, 23]
Uint8 minute; // [0, 59]
Uint8 second; // [0, 60]
Uint8 day_of_week; // [0, 6]
Uint32 nanosecond; // [0, 999999999]
Sint32 utc_offset; // Seconds east of UTC
} OS_Time;
Uint64 osGetNanosecondsFromEpoch (void);
OS_Time osGetTime (Bool local_time);
Uint64 osTickCounter (void);
Uint64 osTickGetRate (void);
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////IMPLEMENTATION///////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
#if defined(SDL_h_)
/* Memory ***********************************************************************/
MEMORY_ALLOT_FUNC(osMemoryAllot)
{
unused_variable(userdata);
void *data = SDL_calloc(1, amount);
return data;
}
MEMORY_REMIT_FUNC(osMemoryRemit)
{
unused_variable(userdata);
SDL_free(ptr);
}
/* Console Output ***********************************************************************/
with_clang_gcc(__attribute__ (( format( __printf__, 1, 2 ))))
void osLogOut (const Char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, args);
va_end(args);
}
with_clang_gcc(__attribute__ (( format( __printf__, 1, 2 ))))
void osLogErr (const Char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, fmt, args);
va_end(args);
}
/* Assertion ***********************************************************************/
with_clang_gcc(__attribute__ (( format( __printf__, 6, 7 ))))
Bool os_Assert (const Char *filename, Sint line, const Char *funcname,
Bool condition, const Char *condition_str,
const Char *fmt, ...)
{
if (!condition) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Assertion failed: %s in %s (at %s:%d)", condition_str, funcname, filename, line);
va_list args;
va_start(args, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, fmt, args);
va_end(args);
SDL_assert(condition);
}
return condition;
}
/* File System ***********************************************************************/
const Path* osGetProgramDirectory (Memory_Push *mempush)
{
const char *basepath = SDL_GetBasePath();
const Path* result = pathCreate(mempush, basepath);
osAssert(result != nullptr, "Couldn't get the program directory: %s", SDL_GetError());
return result;
}
const Path* osGetWorkingDirectory (Memory_Push *mempush)
{
const char *cwd = SDL_GetCurrentDirectory();
const Path* result = pathCreate(mempush, cwd);
osAssert(result != nullptr, "Couldn't get the working directory: %s", SDL_GetError());
return result;
}
const Path* osGetSettingDirectory (Memory_Push *mempush, const Char *organization, const Char *application)
{
const char *cwd = SDL_GetPrefPath(organization, application);
const Path* result = pathCreate(mempush, cwd);
osAssert(result != nullptr, "Couldn't get the setting directory: %s", SDL_GetError());
return result;
}
Bool osCreateDirectory (const Path *path)
{
SDL_PathInfo info;
if (SDL_GetPathInfo(pathGetStr(path), &info)) {
if (info.type == SDL_PATHTYPE_DIRECTORY) {
return true; // Already exists
} else if ((info.type == SDL_PATHTYPE_FILE) || (info.type == SDL_PATHTYPE_OTHER)) {
return false; // Something else exists of that name
}
}
if (SDL_CreateDirectory(pathGetStr(path))) {
return true;
}
return false;
}
Bool osPathMove (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *oldpath, const Path *newpath)
{
if (SDL_RenamePath(pathGetStr(oldpath), pathGetStr(newpath))) {
return true;
} else { // The newpath and oldpath might be on different drives. First copy to new, then delete old.
SDL_PathInfo old_info;
if (!osAssert(SDL_GetPathInfo(pathGetStr(oldpath), &old_info),
"Could find information about %s: %s", pathGetStr(oldpath), SDL_GetError())) {
return false;
}
if (!osAssert(old_info.type != SDL_PATHTYPE_NONE,
"Source file %s doesn't exist", pathGetStr(oldpath))) {
return false;
}
if (!osAssert((old_info.type == SDL_PATHTYPE_FILE) || (old_info.type == SDL_PATHTYPE_DIRECTORY),
"Source %s is not a file or directory", pathGetStr(oldpath))) {
return false;
}
if (old_info.type == SDL_PATHTYPE_FILE) {
if (osPathCopy(tfp, mempush, oldpath, newpath)) {
SDL_RemovePath(pathGetStr(oldpath)); // No check, since there is nothing to be done in case of failure anyway
return true;
} else {
return false;
}
} else if (old_info.type == SDL_PATHTYPE_DIRECTORY) {
// TODO(naman): Implement directory renaming. First, create a directory of the name of
// destination path. Then if the directory is not empty, recurse over its
// contents and manually copy them one by one in the destination directory.
// Finally, delete the source directory (directly if empty, else first delete
// its contents and then delete it).
return osAssert(false, "Directory moving across drives hasn't yet been implemented");
}
return false;
}
}
Bool osPathCopy (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *source, const Path *destination)
{
SDL_PathInfo source_info;
if (!osAssert(SDL_GetPathInfo(pathGetStr(source), &source_info),
"Could find information about %s: %s", pathGetStr(source), SDL_GetError())) {
return false;
}
if (!osAssert(source_info.type != SDL_PATHTYPE_NONE,
"Source file %s doesn't exist", pathGetStr(source))) {
return false;
}
if (!osAssert((source_info.type == SDL_PATHTYPE_FILE) || (source_info.type == SDL_PATHTYPE_DIRECTORY),
"Source %s is not a file or directory", pathGetStr(source))) {
return false;
}
if (source_info.type == SDL_PATHTYPE_FILE) {
// We first copy to a temporary file in order to maintain atomicity (to prevent half-copied files).
Txt dest_temp = txtFormat(tfp, mempush, "%s.%x%x.partial_copy", pathGetStr(destination), SDL_rand_bits(), SDL_rand_bits());
if (SDL_CopyFile(pathGetStr(source), txtStr(dest_temp))) {
Bool result = SDL_CopyFile(txtStr(dest_temp), pathGetStr(destination));
SDL_RemovePath(txtStr(dest_temp)); // No check, since there is nothing to be done in case of failure anyway
return result;
} else {
return false;
}
} else if (source_info.type == SDL_PATHTYPE_DIRECTORY) {
return osAssert(false, "Directory copying hasn't yet been implemented");
}
return false;
}
Bool osPathDelete (const Path *path)
{
SDL_PathInfo info;
if (!osAssert(SDL_GetPathInfo(pathGetStr(path), &info),
"Could find information about %s: %s", pathGetStr(path), SDL_GetError())) {
return false;
}
if (!osAssert(info.type != SDL_PATHTYPE_NONE,
"Path %s doesn't exist", pathGetStr(path))) {
return false;
}
if (!osAssert((info.type == SDL_PATHTYPE_FILE) || (info.type == SDL_PATHTYPE_DIRECTORY),
"%s is not a file or directory", pathGetStr(path))) {
return false;
}
if (info.type == SDL_PATHTYPE_FILE) {
Bool result = SDL_RemovePath(pathGetStr(path));
return result;
} else if (info.type == SDL_PATHTYPE_DIRECTORY) {
// TODO(naman): Implement directory deleting by recursing over its contents.
return osAssert(false, "Directory deleting hasn't yet been implemented");
}
return false;
}
Bool osDoesPathExists (const Path *path)
{
Bool result = SDL_GetPathInfo(pathGetStr(path), nullptr);
return result;
}
Uint64 osPathGetChangedTime (const Path *path)
{
Uint64 result = 0;
SDL_PathInfo info;
if (SDL_GetPathInfo(pathGetStr(path), &info)) {
result = cast_val(Uint64, info.modify_time);
}
return result;
}
struct OS_Path_Recursor_Data_ {
Txt_Fmt_Pool *tfp;
Memory_Push *mempush;
OS_PATH_RECURSOR_FUNC((*iterator));
void *userdata;
const OS_Path_Components *iter;
};
#if defined(ENV_LANG_CXX)
extern "C"
#endif
SDL_EnumerationResult SDLCALL path_Iterator (void *userdata, const char *dirname, const char *fname);
SDL_EnumerationResult SDLCALL path_Iterator (void *userdata, const char *dirname, const char *fname)
{
struct OS_Path_Recursor_Data_ *data = cast_ptr(typeof(data), userdata);
const OS_Path_Components *iter = data->iter;
SDL_assert_paranoid(txtEq(pathCreate(data->mempush, dirname)->_data, iter->parent->_data)); // Just to check
const Path *path = pathJoin(data->tfp, data->mempush, iter->parent, fname);
SDL_PathInfo pathinfo;
SDL_assert(SDL_GetPathInfo(pathGetStr(path), &pathinfo)); // Should never fail, since SDL itself gave us the path
Bool path_is_folder = false; // assignment to prevent warnings
switch (pathinfo.type) {
case SDL_PATHTYPE_FILE: path_is_folder = false; break;
case SDL_PATHTYPE_DIRECTORY: path_is_folder = true; break;
case SDL_PATHTYPE_NONE: case SDL_PATHTYPE_OTHER: default: return SDL_ENUM_CONTINUE; /* Don't iterate over other stuff */
}
const Char *relative_path_ptr = pathGetStr(path) + pathGetStrlen(iter->root);
if (pathGetStr(iter->root)[pathGetStrlen(iter->root) - 1] != PATH_SEPARATOR) {
relative_path_ptr++;
}
Txt path_relative_to_root = txtNew(data->mempush, relative_path_ptr);
const OS_Path_Components iter_new = {
.path = path,
.name = txtNew(data->mempush, fname),
.parent = iter->parent,
.root = iter->root,
.path_relative_to_root = path_relative_to_root,
};
OS_Path_Recursor_Result result_raw = data->iterator(data->tfp, data->mempush, &iter_new, path_is_folder, data->userdata);
switch (result_raw) {
default: /* Won't ever come */
case OS_Path_Recursor_Result_CONTINUE: return SDL_ENUM_CONTINUE;
case OS_Path_Recursor_Result_SUCCESS: return SDL_ENUM_SUCCESS;
case OS_Path_Recursor_Result_FAILURE: return SDL_ENUM_FAILURE;
}
}
Bool osPathRecursorLaunch (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const Path *folder, OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata)
{
const OS_Path_Components iter = {
.parent = folder,
.root = folder,
};
struct OS_Path_Recursor_Data_ data = {
.tfp = tfp,
.mempush = mempush,
.iterator = iterator,
.userdata = userdata,
.iter = &iter,
};
Bool result = SDL_EnumerateDirectory(pathGetStr(folder), path_Iterator, &data);
return result;
}
Bool osPathRecursorRecurse (Txt_Fmt_Pool *tfp, Memory_Push *mempush, const OS_Path_Components *iter,
OS_PATH_RECURSOR_FUNC((*iterator)), void *userdata)
{
const Path *parent = iter->path;
SDL_PathInfo parent_info;
SDL_assert(SDL_GetPathInfo(pathGetStr(parent), &parent_info));
SDL_assert_release(parent_info.type == SDL_PATHTYPE_DIRECTORY);
const OS_Path_Components iter_new = {
.parent = parent,
.root = iter->root,
};
struct OS_Path_Recursor_Data_ data = {
.tfp = tfp,
.mempush = mempush,
.iterator = iterator,
.userdata = userdata,
.iter = &iter_new,
};
Bool result = SDL_EnumerateDirectory(pathGetStr(parent), path_Iterator, &data);
return result;
}
struct Path_Collector_Data {
OS_Path_Collection *file_list;
Txt prefix, suffix;
Bool recursive;
Byte _pad[7];
};
OS_PATH_RECURSOR_FUNC(osCollectFilePaths_Iterator);
OS_PATH_RECURSOR_FUNC(osCollectFilePaths_Iterator)
{
struct Path_Collector_Data *data = cast_ptr(typeof(data), userdata);
if (is_folder) {
if (data->recursive) {
if (osPathRecursorRecurse(tfp, mempush, iter, osCollectFilePaths_Iterator, userdata)) {
return OS_Path_Recursor_Result_CONTINUE;
} else {
return OS_Path_Recursor_Result_FAILURE;
}
} else {
return OS_Path_Recursor_Result_CONTINUE;
}
} else {
Bool prefix_matched = true;
Bool suffix_matched = true;
if (txtStr(data->prefix)) {
Bool name_is_long_enough = pathGetStrlen(iter->path) >= txtLen(data->prefix);
Bool prefix_is_same = strleq(pathGetStr(iter->path), txtLen(data->prefix), txtStr(data->prefix));
prefix_matched = name_is_long_enough && prefix_is_same;
}
if (txtStr(data->suffix)) {
Bool name_is_long_enough = pathGetStrlen(iter->path) >= txtLen(data->suffix);
const Char *path_suffix = pathGetStr(iter->path) + pathGetStrlen(iter->path) - txtLen(data->suffix);
Bool suffix_is_same = streq(path_suffix, txtStr(data->suffix));
suffix_matched = name_is_long_enough && suffix_is_same;
}
if (prefix_matched && suffix_matched) {
segarAdd(data->file_list, *iter);
}
return OS_Path_Recursor_Result_CONTINUE;
}
}
Bool osCollectFilePaths (Txt_Fmt_Pool *tfp, Memory_Push *mempush,
const Path *root, const Txt prefix, const Txt suffix, Bool recursive,
OS_Path_Collection *output_file_list)
{
struct Path_Collector_Data iter_data = {
.file_list = output_file_list,
.prefix = prefix,
.suffix = suffix,
.recursive = recursive,
};
Bool result = osPathRecursorLaunch(tfp, mempush, root, osCollectFilePaths_Iterator, &iter_data);
return result;
}
void* osFileLoad (Memory *memory, const Path *path, Size *size)
{
Size datasize;
void *data = SDL_LoadFile(pathGetStr(path), &datasize);
osAssert(data != nullptr, "Could read file %s: %s", pathGetStr(path), SDL_GetError());
if (size) {
*size = datasize;
}
void *result = memAllot(memory, datasize + 1);
memcpy(result, data, datasize + 1);
SDL_free(data);
return result;
}
Bool osFileDump (void *data, Size size, const Path *path)
{
Bool result = osAssert(SDL_SaveFile(pathGetStr(path), data, size), "Couldn't save file %s: %s", pathGetStr(path), SDL_GetError());
return result;
}
/* Process Control ***********************************************************************/
OS_Command osCommandInit (Memory_Push *mempush)
{
OS_Command cmd = {
.mempush = mempush,
.argv = segarMake(mempush),
};
return cmd;
}
void osCommandAppend (OS_Command *cmd, const Char *arg)
{
segarAdd(&cmd->argv, arg);
}
Sint osCommandExecute (OS_Command *cmd, Memory_Push *mempush, Txt *output)
{
osCommandAppend(cmd, nullptr);
const Char **argv = segarAcquireContiguousPushBuffer(&cmd->argv, mempush);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, argv);
SDL_SetBooleanProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_TO_STDOUT_BOOLEAN, true);
if (output) {
SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP);
}
SDL_Process *process = SDL_CreateProcessWithProperties(props);
osAssert(process != nullptr, "Couldn't create process : %s", SDL_GetError());
Sint exitcode = 0;
if (output) {
Size outsize;
void *out = SDL_ReadProcess(process, &outsize, &exitcode);
*output = txtNewL(cmd->mempush, cast_ptr(Char*, out), outsize);
} else {
SDL_WaitProcess(process, true, &exitcode);
}
SDL_DestroyProcess(process);
SDL_DestroyProperties(props);
return exitcode;
}
/* Environment *************************************************************************/
const Char* osEnvironmentVariableGet(const Char *key)
{
SDL_Environment *env = SDL_GetEnvironment();
osAssert(env != nullptr, "Couldn't get the process environment: %s", SDL_GetError());
const Char *result = SDL_GetEnvironmentVariable(env, key);
return result;
}
const Char* osEnvironmentVariableSet(const Char *key, const Char *val, Bool overwrite)
{
SDL_Environment *env = SDL_GetEnvironment();
osAssert(env != nullptr, "Couldn't get the process environment");
const Char *result = SDL_GetEnvironmentVariable(env, key);
osAssert(SDL_SetEnvironmentVariable(env, key, val, overwrite),
"Couldn't set the envionment variable %s to %s: %s", key, val, SDL_GetError());
return result; // Return the old value
}
/* Time *************************************************************************/
Uint64 osGetNanosecondsFromEpoch (void)
{
SDL_Time ns;
osAssert(SDL_GetCurrentTime(&ns), "Couldn't get nanoseconds from epoch: %s", SDL_GetError());
Uint64 result = cast_val(typeof(result), ns);
return result;
}
OS_Time osGetTime (Bool local_time)
{
Uint64 ns = osGetNanosecondsFromEpoch();
SDL_DateTime time;
osAssert(SDL_TimeToDateTime(cast_val(SDL_Time, ns), &time, local_time), "Couldn't convert nanoseconds into time: %s", SDL_GetError());
OS_Time result = {
.year = cast_val(Uint16, time.year),
.month = cast_val(Uint8, time.month),
.day = cast_val(Uint8, time.day),
.hour = cast_val(Uint8, time.hour),
.minute = cast_val(Uint8, time.minute),
.second = cast_val(Uint8, time.second),
.day_of_week = cast_val(Uint8, time.day_of_week),
.nanosecond = cast_val(Uint32, time.nanosecond),
.utc_offset = time.utc_offset,
};
return result;
}
Uint64 osTickCounter (void)
{
Uint64 result = SDL_GetPerformanceCounter();
return result;
}
Uint64 osTickGetRate (void)
{
Uint64 result = SDL_GetPerformanceFrequency();
return result;
}
#else
# error Platform not supported
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment