diff --git a/Base/dn_base.cpp b/Base/dn_base.cpp new file mode 100644 index 0000000..c25d238 --- /dev/null +++ b/Base/dn_base.cpp @@ -0,0 +1,743 @@ +#define DN_BASE_CPP + +#include "../dn_clangd.h" + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ +// $$ __$$\ +// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ +// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ +// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | +// $$ | $$ |$$ __$$ | \____$$\ $$ ____| +// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ +// \_______/ \_______|\_______/ \_______| +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// +#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) + #if defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + #include + #endif + +DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; + +DN_API DN_CPUIDResult DN_CPU_ID(DN_CPUIDArgs args) +{ + DN_CPUIDResult result = {}; + __cpuidex(result.values, args.eax, args.ecx); + return result; +} + +DN_API DN_USize DN_CPU_HasFeatureArray(DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size) +{ + DN_USize result = 0; + DN_USize const BITS = sizeof(report->features[0]) * 8; + DN_ForIndexU(feature_index, features_size) { + DN_CPUFeatureQuery *query = features + feature_index; + DN_USize chunk_index = query->feature / BITS; + DN_USize chunk_bit = query->feature % BITS; + DN_U64 chunk = report->features[chunk_index]; + query->available = chunk & (1ULL << chunk_bit); + result += DN_CAST(int) query->available; + } + + return result; +} + +DN_API bool DN_CPU_HasFeature(DN_CPUReport const *report, DN_CPUFeature feature) +{ + DN_CPUFeatureQuery query = {}; + query.feature = feature; + bool result = DN_CPU_HasFeatureArray(report, &query, 1) == 1; + return result; +} + +DN_API bool DN_CPU_HasAllFeatures(DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size) +{ + bool result = true; + for (DN_USize index = 0; result && index < features_size; index++) + result &= DN_CPU_HasFeature(report, features[index]); + return result; +} + +DN_API void DN_CPU_SetFeature(DN_CPUReport *report, DN_CPUFeature feature) +{ + DN_Assert(feature < DN_CPUFeature_Count); + DN_USize const BITS = sizeof(report->features[0]) * 8; + DN_USize chunk_index = feature / BITS; + DN_USize chunk_bit = feature % BITS; + report->features[chunk_index] |= (1ULL << chunk_bit); +} + +DN_API DN_CPUReport DN_CPU_Report() +{ + DN_CPUReport result = {}; + DN_CPUIDResult fn_0000_[500] = {}; + DN_CPUIDResult fn_8000_[500] = {}; + int const EXTENDED_FUNC_BASE_EAX = 0x8000'0000; + int const REGISTER_SIZE = sizeof(fn_0000_[0].reg.eax); + + // NOTE: Query standard/extended numbers /////////////////////////////////////////////////////// + { + DN_CPUIDArgs args = {}; + + // NOTE: Query standard function (e.g. eax = 0x0) for function count + cpu vendor + args = {}; + fn_0000_[0] = DN_CPU_ID(args); + + // NOTE: Query extended function (e.g. eax = 0x8000'0000) for function count + cpu vendor + args = {}; + args.eax = DN_CAST(int) EXTENDED_FUNC_BASE_EAX; + fn_8000_[0] = DN_CPU_ID(args); + } + + // NOTE: Extract function count //////////////////////////////////////////////////////////////// + int const STANDARD_FUNC_MAX_EAX = fn_0000_[0x0000].reg.eax; + int const EXTENDED_FUNC_MAX_EAX = fn_8000_[0x0000].reg.eax; + + // NOTE: Enumerate all CPUID results for the known function counts ///////////////////////////// + { + DN_AssertF((STANDARD_FUNC_MAX_EAX + 1) <= DN_ArrayCountI(fn_0000_), + "Max standard count is %d", + STANDARD_FUNC_MAX_EAX + 1); + DN_AssertF((DN_CAST(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1) <= DN_ArrayCountI(fn_8000_), + "Max extended count is %zu", + DN_CAST(DN_ISize) EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1); + + for (int eax = 1; eax <= STANDARD_FUNC_MAX_EAX; eax++) { + DN_CPUIDArgs args = {}; + args.eax = eax; + fn_0000_[eax] = DN_CPU_ID(args); + } + + for (int eax = EXTENDED_FUNC_BASE_EAX + 1, index = 1; eax <= EXTENDED_FUNC_MAX_EAX; eax++, index++) { + DN_CPUIDArgs args = {}; + args.eax = eax; + fn_8000_[index] = DN_CPU_ID(args); + } + } + + // NOTE: Query CPU vendor ////////////////////////////////////////////////////////////////////// + { + DN_Memcpy(result.vendor + 0, &fn_8000_[0x0000].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.vendor + 4, &fn_8000_[0x0000].reg.edx, REGISTER_SIZE); + DN_Memcpy(result.vendor + 8, &fn_8000_[0x0000].reg.ecx, REGISTER_SIZE); + } + + // NOTE: Query CPU brand /////////////////////////////////////////////////////////////////////// + if (EXTENDED_FUNC_MAX_EAX >= (EXTENDED_FUNC_BASE_EAX + 4)) { + DN_Memcpy(result.brand + 0, &fn_8000_[0x0002].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 4, &fn_8000_[0x0002].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 8, &fn_8000_[0x0002].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 12, &fn_8000_[0x0002].reg.edx, REGISTER_SIZE); + + DN_Memcpy(result.brand + 16, &fn_8000_[0x0003].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 20, &fn_8000_[0x0003].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 24, &fn_8000_[0x0003].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 28, &fn_8000_[0x0003].reg.edx, REGISTER_SIZE); + + DN_Memcpy(result.brand + 32, &fn_8000_[0x0004].reg.eax, REGISTER_SIZE); + DN_Memcpy(result.brand + 36, &fn_8000_[0x0004].reg.ebx, REGISTER_SIZE); + DN_Memcpy(result.brand + 40, &fn_8000_[0x0004].reg.ecx, REGISTER_SIZE); + DN_Memcpy(result.brand + 44, &fn_8000_[0x0004].reg.edx, REGISTER_SIZE); + + DN_Assert(result.brand[sizeof(result.brand) - 1] == 0); + } + + // NOTE: Query CPU features ////////////////////////////////////////////////////////////////// + for (DN_USize ext_index = 0; ext_index < DN_CPUFeature_Count; ext_index++) { + bool available = false; + + // NOTE: Mask bits taken from various manuals + // - AMD64 Architecture Programmer's Manual, Volumes 1-5 + // - https://en.wikipedia.org/wiki/CPUID#Calling_CPUID + switch (DN_CAST(DN_CPUFeature) ext_index) { + case DN_CPUFeature_3DNow: available = (fn_8000_[0x0001].reg.edx & (1 << 31)); break; + case DN_CPUFeature_3DNowExt: available = (fn_8000_[0x0001].reg.edx & (1 << 30)); break; + case DN_CPUFeature_ABM: available = (fn_8000_[0x0001].reg.ecx & (1 << 5)); break; + case DN_CPUFeature_AES: available = (fn_0000_[0x0001].reg.ecx & (1 << 25)); break; + case DN_CPUFeature_AVX: available = (fn_0000_[0x0001].reg.ecx & (1 << 28)); break; + case DN_CPUFeature_AVX2: available = (fn_0000_[0x0007].reg.ebx & (1 << 0)); break; + case DN_CPUFeature_AVX512F: available = (fn_0000_[0x0007].reg.ebx & (1 << 16)); break; + case DN_CPUFeature_AVX512DQ: available = (fn_0000_[0x0007].reg.ebx & (1 << 17)); break; + case DN_CPUFeature_AVX512IFMA: available = (fn_0000_[0x0007].reg.ebx & (1 << 21)); break; + case DN_CPUFeature_AVX512PF: available = (fn_0000_[0x0007].reg.ebx & (1 << 26)); break; + case DN_CPUFeature_AVX512ER: available = (fn_0000_[0x0007].reg.ebx & (1 << 27)); break; + case DN_CPUFeature_AVX512CD: available = (fn_0000_[0x0007].reg.ebx & (1 << 28)); break; + case DN_CPUFeature_AVX512BW: available = (fn_0000_[0x0007].reg.ebx & (1 << 30)); break; + case DN_CPUFeature_AVX512VL: available = (fn_0000_[0x0007].reg.ebx & (1 << 31)); break; + case DN_CPUFeature_AVX512VBMI: available = (fn_0000_[0x0007].reg.ecx & (1 << 1)); break; + case DN_CPUFeature_AVX512VBMI2: available = (fn_0000_[0x0007].reg.ecx & (1 << 6)); break; + case DN_CPUFeature_AVX512VNNI: available = (fn_0000_[0x0007].reg.ecx & (1 << 11)); break; + case DN_CPUFeature_AVX512BITALG: available = (fn_0000_[0x0007].reg.ecx & (1 << 12)); break; + case DN_CPUFeature_AVX512VPOPCNTDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 14)); break; + case DN_CPUFeature_AVX5124VNNIW: available = (fn_0000_[0x0007].reg.edx & (1 << 2)); break; + case DN_CPUFeature_AVX5124FMAPS: available = (fn_0000_[0x0007].reg.edx & (1 << 3)); break; + case DN_CPUFeature_AVX512VP2INTERSECT: available = (fn_0000_[0x0007].reg.edx & (1 << 8)); break; + case DN_CPUFeature_AVX512FP16: available = (fn_0000_[0x0007].reg.edx & (1 << 23)); break; + case DN_CPUFeature_CLZERO: available = (fn_8000_[0x0008].reg.ebx & (1 << 0)); break; + case DN_CPUFeature_CMPXCHG8B: available = (fn_0000_[0x0001].reg.edx & (1 << 8)); break; + case DN_CPUFeature_CMPXCHG16B: available = (fn_0000_[0x0001].reg.ecx & (1 << 13)); break; + case DN_CPUFeature_F16C: available = (fn_0000_[0x0001].reg.ecx & (1 << 29)); break; + case DN_CPUFeature_FMA: available = (fn_0000_[0x0001].reg.ecx & (1 << 12)); break; + case DN_CPUFeature_FMA4: available = (fn_8000_[0x0001].reg.ecx & (1 << 16)); break; + case DN_CPUFeature_FP128: available = (fn_8000_[0x001A].reg.eax & (1 << 0)); break; + case DN_CPUFeature_FP256: available = (fn_8000_[0x001A].reg.eax & (1 << 2)); break; + case DN_CPUFeature_FPU: available = (fn_0000_[0x0001].reg.edx & (1 << 0)); break; + case DN_CPUFeature_MMX: available = (fn_0000_[0x0001].reg.edx & (1 << 23)); break; + case DN_CPUFeature_MONITOR: available = (fn_0000_[0x0001].reg.ecx & (1 << 3)); break; + case DN_CPUFeature_MOVBE: available = (fn_0000_[0x0001].reg.ecx & (1 << 22)); break; + case DN_CPUFeature_MOVU: available = (fn_8000_[0x001A].reg.eax & (1 << 1)); break; + case DN_CPUFeature_MmxExt: available = (fn_8000_[0x0001].reg.edx & (1 << 22)); break; + case DN_CPUFeature_PCLMULQDQ: available = (fn_0000_[0x0001].reg.ecx & (1 << 1)); break; + case DN_CPUFeature_POPCNT: available = (fn_0000_[0x0001].reg.ecx & (1 << 23)); break; + case DN_CPUFeature_RDRAND: available = (fn_0000_[0x0001].reg.ecx & (1 << 30)); break; + case DN_CPUFeature_RDSEED: available = (fn_0000_[0x0007].reg.ebx & (1 << 18)); break; + case DN_CPUFeature_RDTSCP: available = (fn_8000_[0x0001].reg.edx & (1 << 27)); break; + case DN_CPUFeature_SHA: available = (fn_0000_[0x0007].reg.ebx & (1 << 29)); break; + case DN_CPUFeature_SSE: available = (fn_0000_[0x0001].reg.edx & (1 << 25)); break; + case DN_CPUFeature_SSE2: available = (fn_0000_[0x0001].reg.edx & (1 << 26)); break; + case DN_CPUFeature_SSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 0)); break; + case DN_CPUFeature_SSE41: available = (fn_0000_[0x0001].reg.ecx & (1 << 19)); break; + case DN_CPUFeature_SSE42: available = (fn_0000_[0x0001].reg.ecx & (1 << 20)); break; + case DN_CPUFeature_SSE4A: available = (fn_8000_[0x0001].reg.ecx & (1 << 6)); break; + case DN_CPUFeature_SSSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 9)); break; + case DN_CPUFeature_TSC: available = (fn_0000_[0x0001].reg.edx & (1 << 4)); break; + case DN_CPUFeature_TscInvariant: available = (fn_8000_[0x0007].reg.edx & (1 << 8)); break; + case DN_CPUFeature_VAES: available = (fn_0000_[0x0007].reg.ecx & (1 << 9)); break; + case DN_CPUFeature_VPCMULQDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 10)); break; + case DN_CPUFeature_Count: DN_InvalidCodePath; break; + } + + if (available) + DN_CPU_SetFeature(&result, DN_CAST(DN_CPUFeature) ext_index); + } + + return result; +} +#endif // !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex) +{ + unsigned int ticket = DN_Atomic_AddU32(&mutex->ticket, 1); + DN_TicketMutex_BeginTicket(mutex, ticket); +} + +DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex) +{ + DN_Atomic_AddU32(&mutex->serving, 1); +} + +DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex) +{ + DN_UInt result = DN_Atomic_AddU32(&mutex->ticket, 1); + return result; +} + +DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket) +{ + DN_AssertF(mutex->serving <= ticket, + "Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, " + "mutex->serving = %u", + ticket, + mutex->serving); + while (ticket != mutex->serving) { + // NOTE: Use spinlock intrinsic + _mm_pause(); + } +} + +DN_API bool DN_TicketMutex_CanLock(DN_TicketMutex const *mutex, DN_UInt ticket) +{ + bool result = (ticket == mutex->serving); + return result; +} + +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #if !defined(DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED) + #undef _CRT_SECURE_NO_WARNINGS + #endif +#endif + +// NOTE: DN_Bit //////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_Bit_UnsetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags & ~bitfield); +} + +DN_API void DN_Bit_SetInplace(DN_USize *flags, DN_USize bitfield) +{ + *flags = (*flags | bitfield); +} + +DN_API bool DN_Bit_IsSet(DN_USize bits, DN_USize bits_to_set) +{ + auto result = DN_CAST(bool)((bits & bits_to_set) == bits_to_set); + return result; +} + +DN_API bool DN_Bit_IsNotSet(DN_USize bits, DN_USize bits_to_check) +{ + auto result = !DN_Bit_IsSet(bits, bits_to_check); + return result; +} + +// NOTE: DN_Safe /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_I64 DN_Safe_AddI64(int64_t a, int64_t b) +{ + DN_I64 result = DN_CheckF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX; + return result; +} + +DN_API DN_I64 DN_Safe_MulI64(int64_t a, int64_t b) +{ + DN_I64 result = DN_CheckF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX; + return result; +} + +DN_API DN_U64 DN_Safe_AddU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX; + return result; +} + +DN_API DN_U64 DN_Safe_SubU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0; + return result; +} + +DN_API DN_U64 DN_Safe_MulU64(DN_U64 a, DN_U64 b) +{ + DN_U64 result = DN_CheckF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX; + return result; +} + +DN_API DN_U32 DN_Safe_SubU32(DN_U32 a, DN_U32 b) +{ + DN_U32 result = DN_CheckF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0; + return result; +} + +// NOTE: DN_SaturateCastUSizeToI* //////////////////////////////////////////////////////////// +// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is +// the highest possible rank (unsigned > signed). +DN_API int DN_SaturateCastUSizeToInt(DN_USize val) +{ + int result = DN_Check(DN_CAST(uintmax_t) val <= INT_MAX) ? DN_CAST(int) val : INT_MAX; + return result; +} + +DN_API int8_t DN_SaturateCastUSizeToI8(DN_USize val) +{ + int8_t result = DN_Check(DN_CAST(uintmax_t) val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastUSizeToI16(DN_USize val) +{ + DN_I16 result = DN_Check(DN_CAST(uintmax_t) val <= INT16_MAX) ? DN_CAST(DN_I16) val : INT16_MAX; + return result; +} + +DN_API DN_I32 DN_SaturateCastUSizeToI32(DN_USize val) +{ + DN_I32 result = DN_Check(DN_CAST(uintmax_t) val <= INT32_MAX) ? DN_CAST(DN_I32) val : INT32_MAX; + return result; +} + +DN_API int64_t DN_SaturateCastUSizeToI64(DN_USize val) +{ + int64_t result = DN_Check(DN_CAST(uintmax_t) val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX; + return result; +} + +// NOTE: DN_SaturateCastUSizeToU* //////////////////////////////////////////////////////////// +// Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API DN_U8 DN_SaturateCastUSizeToU8(DN_USize val) +{ + DN_U8 result = DN_Check(val <= UINT8_MAX) ? DN_CAST(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastUSizeToU16(DN_USize val) +{ + DN_U16 result = DN_Check(val <= UINT16_MAX) ? DN_CAST(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastUSizeToU32(DN_USize val) +{ + DN_U32 result = DN_Check(val <= UINT32_MAX) ? DN_CAST(DN_U32) val : UINT32_MAX; + return result; +} + +DN_API DN_U64 DN_SaturateCastUSizeToU64(DN_USize val) +{ + DN_U64 result = DN_Check(DN_CAST(DN_U64) val <= UINT64_MAX) ? DN_CAST(DN_U64) val : UINT64_MAX; + return result; +} + +// NOTE: DN_SaturateCastU64To* /////////////////////////////////////////////////////////////// +DN_API int DN_SaturateCastU64ToInt(DN_U64 val) +{ + int result = DN_Check(val <= INT_MAX) ? DN_CAST(int) val : INT_MAX; + return result; +} + +DN_API int8_t DN_SaturateCastU64ToI8(DN_U64 val) +{ + int8_t result = DN_Check(val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX; + return result; +} + +DN_API DN_I16 DN_SaturateCastU64ToI16(DN_U64 val) +{ + DN_I16 result = DN_Check(val <= INT16_MAX) ? DN_CAST(DN_I16) val : INT16_MAX; + return result; +} + +DN_API DN_I32 DN_SaturateCastU64ToI32(DN_U64 val) +{ + DN_I32 result = DN_Check(val <= INT32_MAX) ? DN_CAST(DN_I32) val : INT32_MAX; + return result; +} + +DN_API int64_t DN_SaturateCastU64ToI64(DN_U64 val) +{ + int64_t result = DN_Check(val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX; + return result; +} + +// Both operands are unsigned and the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API unsigned int DN_SaturateCastU64ToUInt(DN_U64 val) +{ + unsigned int result = DN_Check(val <= UINT8_MAX) ? DN_CAST(unsigned int) val : UINT_MAX; + return result; +} + +DN_API DN_U8 DN_SaturateCastU64ToU8(DN_U64 val) +{ + DN_U8 result = DN_Check(val <= UINT8_MAX) ? DN_CAST(DN_U8) val : UINT8_MAX; + return result; +} + +DN_API DN_U16 DN_SaturateCastU64ToU16(DN_U64 val) +{ + DN_U16 result = DN_Check(val <= UINT16_MAX) ? DN_CAST(DN_U16) val : UINT16_MAX; + return result; +} + +DN_API DN_U32 DN_SaturateCastU64ToU32(DN_U64 val) +{ + DN_U32 result = DN_Check(val <= UINT32_MAX) ? DN_CAST(DN_U32) val : UINT32_MAX; + return result; +} + +// NOTE: DN_SaturateCastISizeToI* //////////////////////////////////////////////////////////// +// Both operands are signed so the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API int DN_SaturateCastISizeToInt(DN_ISize val) +{ + DN_Assert(val >= INT_MIN && val <= INT_MAX); + int result = DN_CAST(int) DN_Clamp(val, INT_MIN, INT_MAX); + return result; +} + +DN_API int8_t DN_SaturateCastISizeToI8(DN_ISize val) +{ + DN_Assert(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastISizeToI16(DN_ISize val) +{ + DN_Assert(val >= INT16_MIN && val <= INT16_MAX); + DN_I16 result = DN_CAST(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_I32 DN_SaturateCastISizeToI32(DN_ISize val) +{ + DN_Assert(val >= INT32_MIN && val <= INT32_MAX); + DN_I32 result = DN_CAST(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); + return result; +} + +DN_API int64_t DN_SaturateCastISizeToI64(DN_ISize val) +{ + DN_Assert(DN_CAST(int64_t) val >= INT64_MIN && DN_CAST(int64_t) val <= INT64_MAX); + int64_t result = DN_CAST(int64_t) DN_Clamp(DN_CAST(int64_t) val, INT64_MIN, INT64_MAX); + return result; +} + +// NOTE: DN_SaturateCastISizeToU* //////////////////////////////////////////////////////////// +// If the value is a negative integer, we clamp to 0. Otherwise, we know that +// the value is >=0, we can upcast safely to bounds check against the maximum +// allowed value. +DN_API unsigned int DN_SaturateCastISizeToUInt(DN_ISize val) +{ + unsigned int result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT_MAX)) + result = DN_CAST(unsigned int) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_U8 DN_SaturateCastISizeToU8(DN_ISize val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT8_MAX)) + result = DN_CAST(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastISizeToU16(DN_ISize val) +{ + DN_U16 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT16_MAX)) + result = DN_CAST(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastISizeToU32(DN_ISize val) +{ + DN_U32 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT32_MAX)) + result = DN_CAST(DN_U32) val; + else + result = UINT32_MAX; + } + return result; +} + +DN_API DN_U64 DN_SaturateCastISizeToU64(DN_ISize val) +{ + DN_U64 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT64_MAX)) + result = DN_CAST(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +// NOTE: DN_SaturateCastI64To* /////////////////////////////////////////////////////////////// +// Both operands are signed so the lowest rank operand will be promoted to +// match the highest rank operand. +DN_API DN_ISize DN_SaturateCastI64ToISize(int64_t val) +{ + DN_Check(val >= DN_ISIZE_MIN && val <= DN_ISIZE_MAX); + DN_ISize result = DN_CAST(int64_t) DN_Clamp(val, DN_ISIZE_MIN, DN_ISIZE_MAX); + return result; +} + +DN_API int8_t DN_SaturateCastI64ToI8(int64_t val) +{ + DN_Check(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastI64ToI16(int64_t val) +{ + DN_Check(val >= INT16_MIN && val <= INT16_MAX); + DN_I16 result = DN_CAST(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_I32 DN_SaturateCastI64ToI32(int64_t val) +{ + DN_Check(val >= INT32_MIN && val <= INT32_MAX); + DN_I32 result = DN_CAST(DN_I32) DN_Clamp(val, INT32_MIN, INT32_MAX); + return result; +} + +DN_API unsigned int DN_SaturateCastI64ToUInt(int64_t val) +{ + unsigned int result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT_MAX)) + result = DN_CAST(unsigned int) val; + else + result = UINT_MAX; + } + return result; +} + +DN_API DN_ISize DN_SaturateCastI64ToUSize(int64_t val) +{ + DN_USize result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= DN_USIZE_MAX)) + result = DN_CAST(DN_USize) val; + else + result = DN_USIZE_MAX; + } + return result; +} + +DN_API DN_U8 DN_SaturateCastI64ToU8(int64_t val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT8_MAX)) + result = DN_CAST(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastI64ToU16(int64_t val) +{ + DN_U16 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT16_MAX)) + result = DN_CAST(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastI64ToU32(int64_t val) +{ + DN_U32 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT32_MAX)) + result = DN_CAST(DN_U32) val; + else + result = UINT32_MAX; + } + return result; +} + +DN_API DN_U64 DN_SaturateCastI64ToU64(int64_t val) +{ + DN_U64 result = 0; + if (DN_Check(val >= DN_CAST(int64_t) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT64_MAX)) + result = DN_CAST(DN_U64) val; + else + result = UINT64_MAX; + } + return result; +} + +DN_API int8_t DN_SaturateCastIntToI8(int val) +{ + DN_Check(val >= INT8_MIN && val <= INT8_MAX); + int8_t result = DN_CAST(int8_t) DN_Clamp(val, INT8_MIN, INT8_MAX); + return result; +} + +DN_API DN_I16 DN_SaturateCastIntToI16(int val) +{ + DN_Check(val >= INT16_MIN && val <= INT16_MAX); + DN_I16 result = DN_CAST(DN_I16) DN_Clamp(val, INT16_MIN, INT16_MAX); + return result; +} + +DN_API DN_U8 DN_SaturateCastIntToU8(int val) +{ + DN_U8 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT8_MAX)) + result = DN_CAST(DN_U8) val; + else + result = UINT8_MAX; + } + return result; +} + +DN_API DN_U16 DN_SaturateCastIntToU16(int val) +{ + DN_U16 result = 0; + if (DN_Check(val >= DN_CAST(DN_ISize) 0)) { + if (DN_Check(DN_CAST(uintmax_t) val <= UINT16_MAX)) + result = DN_CAST(DN_U16) val; + else + result = UINT16_MAX; + } + return result; +} + +DN_API DN_U32 DN_SaturateCastIntToU32(int val) +{ + static_assert(sizeof(val) <= sizeof(DN_U32), "Sanity check to allow simplifying of casting"); + DN_U32 result = 0; + if (DN_Check(val >= 0)) + result = DN_CAST(DN_U32) val; + return result; +} + +DN_API DN_U64 DN_SaturateCastIntToU64(int val) +{ + static_assert(sizeof(val) <= sizeof(DN_U64), "Sanity check to allow simplifying of casting"); + DN_U64 result = 0; + if (DN_Check(val >= 0)) + result = DN_CAST(DN_U64) val; + return result; +} + +// NOTE: DN_Asan ////////////////////////////////////////////////////////////////////////// //////// +static_assert(DN_IsPowerOfTwoAligned(DN_ASAN_POISON_GUARD_SIZE, DN_ASAN_POISON_ALIGNMENT), + "ASAN poison guard size must be a power-of-two and aligned to ASAN's alignment" + "requirement (8 bytes)"); + +DN_API void DN_ASAN_PoisonMemoryRegion(void const volatile *ptr, DN_USize size) +{ + if (!ptr || !size) + return; + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + DN_AssertF(DN_IsPowerOfTwoAligned(ptr, 8), + "Poisoning requires the pointer to be aligned on an 8 byte boundary"); + + __asan_poison_memory_region(ptr, size); + if (DN_ASAN_VET_POISON) { + DN_HardAssert(__asan_address_is_poisoned(ptr)); + DN_HardAssert(__asan_address_is_poisoned((char *)ptr + (size - 1))); + } +#else + (void)ptr; + (void)size; +#endif +} + +DN_API void DN_ASAN_UnpoisonMemoryRegion(void const volatile *ptr, DN_USize size) +{ + if (!ptr || !size) + return; + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + __asan_unpoison_memory_region(ptr, size); + if (DN_ASAN_VET_POISON) + DN_HardAssert(__asan_region_is_poisoned((void *)ptr, size) == 0); +#else + (void)ptr; + (void)size; +#endif +} diff --git a/Base/dn_base.h b/Base/dn_base.h new file mode 100644 index 0000000..9084b6d --- /dev/null +++ b/Base/dn_base.h @@ -0,0 +1,613 @@ +#if !defined(DN_BASE_H) +#define DN_BASE_H + +// NOTE: Macros //////////////////////////////////////////////////////////////////////////////////// +#define DN_Stringify(x) #x +#define DN_TokenCombine2(x, y) x ## y +#define DN_TokenCombine(x, y) DN_TokenCombine2(x, y) + +#include // va_list +#include +#include +#include +#include // PRIu64... + +#if !defined(DN_OS_WIN32) +#include // exit() +#endif + +#define DN_ForIndexU(index, size) for (DN_USize index = 0; index < size; index++) +#define DN_ForIndexI(index, size) for (DN_ISize index = 0; index < size; index++) +#define DN_ForItSize(it, T, array, size) for (struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < (size); it.index++, it.data++) +#define DN_ForIt(it, T, array) for (struct { DN_USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->size; it.index++, it.data++) +#define DN_ForItCArray(it, T, array) for (struct { DN_USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ArrayCountU(array); it.index++, it.data++) + +#define DN_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) +#define DN_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) +#define DN_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) +#define DN_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) + +// NOTE: String.h Dependencies ///////////////////////////////////////////////////////////////////// +#if !defined(DN_Memcpy) || !defined(DN_Memset) || !defined(DN_Memcmp) || !defined(DN_Memmove) + #include + #if !defined(DN_Memcpy) + #define DN_Memcpy(dest, src, count) memcpy((dest), (src), (count)) + #endif + #if !defined(DN_Memset) + #define DN_Memset(dest, value, count) memset((dest), (value), (count)) + #endif + #if !defined(DN_Memcmp) + #define DN_Memcmp(lhs, rhs, count) memcmp((lhs), (rhs), (count)) + #endif + #if !defined(DN_Memmove) + #define DN_Memmove(dest, src, count) memmove((dest), (src), (count)) + #endif +#endif + +// NOTE: Math.h Dependencies /////////////////////////////////////////////////////////////////////// +#if !defined(DN_SqrtF32) || !defined(DN_SinF32) || !defined(DN_CosF32) || !defined(DN_TanF32) + #include + #if !defined(DN_SqrtF32) + #define DN_SqrtF32(val) sqrtf(val) + #endif + #if !defined(DN_SinF32) + #define DN_SinF32(val) sinf(val) + #endif + #if !defined(DN_CosF32) + #define DN_CosF32(val) cosf(val) + #endif + #if !defined(DN_TanF32) + #define DN_TanF32(val) tanf(val) + #endif +#endif + +// NOTE: Math ////////////////////////////////////////////////////////////////////////////////////// +#define DN_PiF32 3.14159265359f + +#define DN_DegreesToRadians(degrees) ((degrees) * (DN_PI / 180.0f)) +#define DN_RadiansToDegrees(radians) ((radians) * (180.f * DN_PI)) + +#define DN_Abs(val) (((val) < 0) ? (-(val)) : (val)) +#define DN_Max(a, b) (((a) > (b)) ? (a) : (b)) +#define DN_Min(a, b) (((a) < (b)) ? (a) : (b)) +#define DN_Clamp(val, lo, hi) DN_Max(DN_Min(val, hi), lo) +#define DN_Squared(val) ((val) * (val)) + +#define DN_Swap(a, b) \ + do { \ + auto temp = a; \ + a = b; \ + b = temp; \ + } while (0) + +// NOTE: Size ////////////////////////////////////////////////////////////////////////////////////// +#define DN_SizeOfI(val) DN_CAST(ptrdiff_t)sizeof(val) +#define DN_ArrayCountU(array) (sizeof(array)/(sizeof((array)[0]))) +#define DN_ArrayCountI(array) (DN_ISize)DN_ArrayCountU(array) +#define DN_CharCountU(string) (sizeof(string) - 1) + +// NOTE: SI Byte /////////////////////////////////////////////////////////////////////////////////// +#define DN_Bytes(val) ((DN_U64)val) +#define DN_Kilobytes(val) ((DN_U64)1024 * DN_Bytes(val)) +#define DN_Megabytes(val) ((DN_U64)1024 * DN_Kilobytes(val)) +#define DN_Gigabytes(val) ((DN_U64)1024 * DN_Megabytes(val)) + +// NOTE: Time ////////////////////////////////////////////////////////////////////////////////////// +#define DN_SecondsToMs(val) ((val) * 1000) +#define DN_MinutesToSec(val) ((val) * 60ULL) +#define DN_HoursToSec(val) (DN_MinutesToSec(val) * 60ULL) +#define DN_DaysToSec(val) (DN_HoursToSec(val) * 24ULL) +#define DN_WeeksToSec(val) (DN_DaysToSec(val) * 7ULL) +#define DN_YearsToSec(val) (DN_WeeksToSec(val) * 52ULL) + +// NOTE: Debug Break /////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_DebugBreak) + #if defined(NDEBUG) + #define DN_DebugBreak + #else + #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #define DN_DebugBreak __debugbreak() + #elif DN_HAS_BUILTIN(__builtin_debugtrap) + #define DN_DebugBreak __builtin_debugtrap() + #elif DN_HAS_BUILTIN(__builtin_trap) || defined(DN_COMPILER_GCC) + #define DN_DebugBreak __builtin_trap() + #else + #include + #if defined(SIGTRAP) + #define DN_DebugBreak raise(SIGTRAP) + #else + #define DN_DebugBreak raise(SIGABRT) + #endif + #endif + #endif +#endif + +// NOTE: Byte swaps //////////////////////////////////////////////////////////////////////////////// +#define DN_ByteSwap64(u64) (((((u64) >> 56) & 0xFF) << 0) | \ + ((((u64) >> 48) & 0xFF) << 8) | \ + ((((u64) >> 40) & 0xFF) << 16) | \ + ((((u64) >> 32) & 0xFF) << 24) | \ + ((((u64) >> 24) & 0xFF) << 32) | \ + ((((u64) >> 16) & 0xFF) << 40) | \ + ((((u64) >> 8) & 0xFF) << 48) | \ + ((((u64) >> 0) & 0xFF) << 56)) + +// NOTE: Defer Macro /////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +template +struct DN_Defer +{ + Procedure proc; + DN_Defer(Procedure p) : proc(p) {} + ~DN_Defer() { proc(); } +}; + +struct DN_DeferHelper +{ + template + DN_Defer operator+(Lambda lambda) { return DN_Defer(lambda); }; +}; + +#define DN_UniqueName(prefix) DN_TokenCombine(prefix, __LINE__) +#define DN_DEFER const auto DN_UniqueName(defer_lambda_) = DN_DeferHelper() + [&]() +#endif // defined(__cplusplus) + +#define DN_DEFER_LOOP(begin, end) \ + for (bool DN_UniqueName(once) = (begin, true); \ + DN_UniqueName(once); \ + end, DN_UniqueName(once) = false) + +// NOTE: Types ///////////////////////////////////////////////////////////////////////////////////// +typedef intptr_t DN_ISize; +typedef uintptr_t DN_USize; + +typedef int8_t DN_I8; +typedef int16_t DN_I16; +typedef int32_t DN_I32; +typedef int64_t DN_I64; + +typedef uint8_t DN_U8; +typedef uint16_t DN_U16; +typedef uint32_t DN_U32; +typedef uint64_t DN_U64; + +typedef float DN_F32; +typedef double DN_F64; +typedef unsigned int DN_UInt; +typedef DN_I32 DN_B32; + +#define DN_F32_MAX 3.402823466e+38F +#define DN_F32_MIN 1.175494351e-38F +#define DN_F64_MAX 1.7976931348623158e+308 +#define DN_F64_MIN 2.2250738585072014e-308 +#define DN_USIZE_MAX UINTPTR_MAX +#define DN_ISIZE_MAX INTPTR_MAX +#define DN_ISIZE_MIN INTPTR_MIN + +enum DN_ZeroMem +{ + DN_ZeroMem_No, // Memory can be handed out without zero-ing it out + DN_ZeroMem_Yes, // Memory should be zero-ed out before giving to the callee +}; + +struct DN_Str8 +{ + char *data; // The bytes of the string + DN_USize size; // The number of bytes in the string + + char const *begin() const { return data; } + char const *end() const { return data + size; } + char *begin() { return data; } + char *end() { return data + size; } +}; + +struct DN_Str16 // A pointer and length style string that holds slices to UTF16 bytes. +{ + wchar_t *data; // The UTF16 bytes of the string + DN_USize size; // The number of characters in the string + + #if defined(__cplusplus) + wchar_t const *begin() const { return data; } // Const begin iterator for range-for loops + wchar_t const *end() const { return data + size; } // Const end iterator for range-for loops + wchar_t *begin() { return data; } // Begin iterator for range-for loops + wchar_t *end() { return data + size; } // End iterator for range-for loops + #endif +}; + +template +struct DN_Slice // A pointer and length container of data +{ + T *data; + DN_USize size; + + T *begin() { return data; } + T *end() { return data + size; } + T const *begin() const { return data; } + T const *end() const { return data + size; } +}; + +// NOTE: DN_CallSite /////////////////////////////////////////////////////////////////////////////// +struct DN_CallSite +{ + DN_Str8 file; + DN_Str8 function; + DN_U32 line; +}; + +#define DN_CALL_SITE \ + DN_CallSite \ + { \ + DN_STR8(__FILE__), DN_STR8(__func__), __LINE__ \ + } + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Atomic_Add/Exchange return the previous value store in the target +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #include + #define DN_Atomic_CompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) + #define DN_Atomic_CompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) + #define DN_Atomic_AddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) + #define DN_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) + #define DN_Atomic_SubU32(target, value) DN_Atomic_AddU32(DN_CAST(long volatile *) target, (long)-value) + #define DN_Atomic_SubU64(target, value) DN_Atomic_AddU64(target, (DN_U64) - value) + + #define DN_CountLeadingZerosU64(value) __lzcnt64(value) + + #define DN_CPU_TSC() __rdtsc() + #define DN_CompilerReadBarrierAndCPUReadFence \ + _ReadBarrier(); \ + _mm_lfence() + #define DN_CompilerWriteBarrierAndCPUWriteFence \ + _WriteBarrier(); \ + _mm_sfence() +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + #if defined(__ANDROID__) + #elif defined(DN_PLATFORM_EMSCRIPTEN) + #include + #else + #include + #endif + + #define DN_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + #define DN_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) + + #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) + #if defined(DN_COMPILER_GCC) + #define DN_CPU_TSC() __rdtsc() + #else + #define DN_CPU_TSC() __builtin_readcyclecounter() + #endif + #if defined(DN_PLATFORM_EMSCRIPTEN) + #define DN_CompilerReadBarrierAndCPUReadFence + #define DN_CompilerWriteBarrierAndCPUWriteFence + #else + #define DN_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory") + #define DN_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory") + #endif +#else + #error "Compiler not supported" +#endif + +#if !defined(DN_PLATFORM_ARM64) +struct DN_CPURegisters +{ + int eax; + int ebx; + int ecx; + int edx; +}; + +union DN_CPUIDResult +{ + DN_CPURegisters reg; + int values[4]; +}; + +struct DN_CPUIDArgs +{ + int eax; + int ecx; +}; + + #define DN_CPU_FEAT_XMACRO \ + DN_CPU_FEAT_XENTRY(3DNow) \ + DN_CPU_FEAT_XENTRY(3DNowExt) \ + DN_CPU_FEAT_XENTRY(ABM) \ + DN_CPU_FEAT_XENTRY(AES) \ + DN_CPU_FEAT_XENTRY(AVX) \ + DN_CPU_FEAT_XENTRY(AVX2) \ + DN_CPU_FEAT_XENTRY(AVX512F) \ + DN_CPU_FEAT_XENTRY(AVX512DQ) \ + DN_CPU_FEAT_XENTRY(AVX512IFMA) \ + DN_CPU_FEAT_XENTRY(AVX512PF) \ + DN_CPU_FEAT_XENTRY(AVX512ER) \ + DN_CPU_FEAT_XENTRY(AVX512CD) \ + DN_CPU_FEAT_XENTRY(AVX512BW) \ + DN_CPU_FEAT_XENTRY(AVX512VL) \ + DN_CPU_FEAT_XENTRY(AVX512VBMI) \ + DN_CPU_FEAT_XENTRY(AVX512VBMI2) \ + DN_CPU_FEAT_XENTRY(AVX512VNNI) \ + DN_CPU_FEAT_XENTRY(AVX512BITALG) \ + DN_CPU_FEAT_XENTRY(AVX512VPOPCNTDQ) \ + DN_CPU_FEAT_XENTRY(AVX5124VNNIW) \ + DN_CPU_FEAT_XENTRY(AVX5124FMAPS) \ + DN_CPU_FEAT_XENTRY(AVX512VP2INTERSECT) \ + DN_CPU_FEAT_XENTRY(AVX512FP16) \ + DN_CPU_FEAT_XENTRY(CLZERO) \ + DN_CPU_FEAT_XENTRY(CMPXCHG8B) \ + DN_CPU_FEAT_XENTRY(CMPXCHG16B) \ + DN_CPU_FEAT_XENTRY(F16C) \ + DN_CPU_FEAT_XENTRY(FMA) \ + DN_CPU_FEAT_XENTRY(FMA4) \ + DN_CPU_FEAT_XENTRY(FP128) \ + DN_CPU_FEAT_XENTRY(FP256) \ + DN_CPU_FEAT_XENTRY(FPU) \ + DN_CPU_FEAT_XENTRY(MMX) \ + DN_CPU_FEAT_XENTRY(MONITOR) \ + DN_CPU_FEAT_XENTRY(MOVBE) \ + DN_CPU_FEAT_XENTRY(MOVU) \ + DN_CPU_FEAT_XENTRY(MmxExt) \ + DN_CPU_FEAT_XENTRY(PCLMULQDQ) \ + DN_CPU_FEAT_XENTRY(POPCNT) \ + DN_CPU_FEAT_XENTRY(RDRAND) \ + DN_CPU_FEAT_XENTRY(RDSEED) \ + DN_CPU_FEAT_XENTRY(RDTSCP) \ + DN_CPU_FEAT_XENTRY(SHA) \ + DN_CPU_FEAT_XENTRY(SSE) \ + DN_CPU_FEAT_XENTRY(SSE2) \ + DN_CPU_FEAT_XENTRY(SSE3) \ + DN_CPU_FEAT_XENTRY(SSE41) \ + DN_CPU_FEAT_XENTRY(SSE42) \ + DN_CPU_FEAT_XENTRY(SSE4A) \ + DN_CPU_FEAT_XENTRY(SSSE3) \ + DN_CPU_FEAT_XENTRY(TSC) \ + DN_CPU_FEAT_XENTRY(TscInvariant) \ + DN_CPU_FEAT_XENTRY(VAES) \ + DN_CPU_FEAT_XENTRY(VPCMULQDQ) + +enum DN_CPUFeature +{ + #define DN_CPU_FEAT_XENTRY(label) DN_CPUFeature_##label, + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_CPUFeature_Count, +}; + +struct DN_CPUFeatureDecl +{ + DN_CPUFeature value; + DN_Str8 label; +}; + +struct DN_CPUFeatureQuery +{ + DN_CPUFeature feature; + bool available; +}; + +struct DN_CPUReport +{ + char vendor[4 /*bytes*/ * 3 /*EDX, ECX, EBX*/ + 1 /*null*/]; + char brand[48]; + DN_U64 features[(DN_CPUFeature_Count / (sizeof(DN_U64) * 8)) + 1]; +}; + +extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; +#endif // DN_PLATFORM_ARM64 + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +struct DN_TicketMutex +{ + unsigned int volatile ticket; // The next ticket to give out to the thread taking the mutex + unsigned int volatile serving; // The ticket ID to block the mutex on until it is returned +}; + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64 (DN_U64 volatile *target, DN_U64 value); +DN_FORCE_INLINE long DN_Atomic_SetValue32 (long volatile *target, long value); + +#if !defined (DN_PLATFORM_ARM64) +DN_API DN_CPUIDResult DN_CPU_ID (DN_CPUIDArgs args); +DN_API DN_USize DN_CPU_HasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); +DN_API bool DN_CPU_HasFeature (DN_CPUReport const *report, DN_CPUFeature feature); +DN_API bool DN_CPU_HasAllFeatures (DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size); +template +bool DN_CPU_HasAllFeaturesCArray(DN_CPUReport const *report, DN_CPUFeature const (&features)[N]); +DN_API void DN_CPU_SetFeature (DN_CPUReport *report, DN_CPUFeature feature); +DN_API DN_CPUReport DN_CPU_Report (); +#endif + +// NOTE: DN_TicketMutex //////////////////////////////////////////////////////////////////////////// +DN_API void DN_TicketMutex_Begin (DN_TicketMutex *mutex); +DN_API void DN_TicketMutex_End (DN_TicketMutex *mutex); +DN_API DN_UInt DN_TicketMutex_MakeTicket (DN_TicketMutex *mutex); +DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket); +DN_API bool DN_TicketMutex_CanLock (DN_TicketMutex const *mutex, DN_UInt ticket); + +// NOTE: DN_DLList ///////////////////////////////////////////////////////////////////////////////// +#define DN_DLList_Init(list) \ + (list)->next = (list)->prev = (list) + +#define DN_DLList_IsSentinel(list, item) ((list) == (item)) + +#define DN_DLList_InitArena(list, T, arena) \ + do { \ + (list) = DN_Arena_New(arena, T, DN_ZeroMem_Yes); \ + DN_DLList_Init(list); \ + } while (0) + +#define DN_DLList_InitPool(list, T, pool) \ + do { \ + (list) = DN_Pool_New(pool, T); \ + DN_DLList_Init(list); \ + } while (0) + +#define DN_DLList_Detach(item) \ + do { \ + if (item) { \ + (item)->prev->next = (item)->next; \ + (item)->next->prev = (item)->prev; \ + (item)->next = nullptr; \ + (item)->prev = nullptr; \ + } \ + } while (0) + +#define DN_DLList_Dequeue(list, dest_ptr) \ + if (DN_DLList_HasItems(list)) { \ + dest_ptr = (list)->next; \ + DN_DLList_Detach(dest_ptr); \ + } + +#define DN_DLList_Append(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_DLList_Detach(item); \ + (item)->next = (list)->next; \ + (item)->prev = (list); \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +#define DN_DLList_Prepend(list, item) \ + do { \ + if (item) { \ + if ((item)->next) \ + DN_DLList_Detach(item); \ + (item)->next = (list); \ + (item)->prev = (list)->prev; \ + (item)->next->prev = (item); \ + (item)->prev->next = (item); \ + } \ + } while (0) + +#define DN_DLList_IsEmpty(list) \ + (!(list) || ((list) == (list)->next)) + +#define DN_DLList_IsInit(list) \ + ((list)->next && (list)->prev) + +#define DN_DLList_HasItems(list) \ + ((list) && ((list) != (list)->next)) + +#define DN_DLList_ForEach(it_name, list) \ + auto *it_name = (list)->next; \ + (it_name) != (list); \ + (it_name) = (it_name)->next + +// NOTE: Intrinsics //////////////////////////////////////////////////////////////////////////////// +DN_FORCE_INLINE DN_U64 DN_Atomic_SetValue64(DN_U64 volatile *target, DN_U64 value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + __int64 result; + do { + result = *target; + } while (DN_Atomic_CompareExchange64(target, value, result) != result); + return DN_CAST(DN_U64) result; +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + DN_U64 result = __sync_lock_test_and_set(target, value); + return result; +#else + #error Unsupported compiler +#endif +} + +DN_FORCE_INLINE long DN_Atomic_SetValue32(long volatile *target, long value) +{ +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + long result; + do { + result = *target; + } while (DN_Atomic_CompareExchange32(target, value, result) != result); + return result; +#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) + long result = __sync_lock_test_and_set(target, value); + return result; +#else + #error Unsupported compiler +#endif +} + +template +bool DN_CPU_HasAllFeaturesCArray(DN_CPUReport const *report, DN_CPUFeature const (&features)[N]) +{ + bool result = DN_CPU_HasAllFeatures(report, features, N); + return result; +} + +// NOTE: DN_Bit //////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_Bit_UnsetInplace (DN_USize *flags, DN_USize bitfield); +DN_API void DN_Bit_SetInplace (DN_USize *flags, DN_USize bitfield); +DN_API bool DN_Bit_IsSet (DN_USize bits, DN_USize bits_to_set); +DN_API bool DN_Bit_IsNotSet (DN_USize bits, DN_USize bits_to_check); +#define DN_Bit_ClearNextLSB(value) (value) & ((value) - 1) + +// NOTE: DN_Safe /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_I64 DN_Safe_AddI64 (DN_I64 a, DN_I64 b); +DN_API DN_I64 DN_Safe_MulI64 (DN_I64 a, DN_I64 b); + +DN_API DN_U64 DN_Safe_AddU64 (DN_U64 a, DN_U64 b); +DN_API DN_U64 DN_Safe_MulU64 (DN_U64 a, DN_U64 b); + +DN_API DN_U64 DN_Safe_SubU64 (DN_U64 a, DN_U64 b); +DN_API DN_U32 DN_Safe_SubU32 (DN_U32 a, DN_U32 b); + +DN_API int DN_SaturateCastUSizeToInt (DN_USize val); +DN_API DN_I8 DN_SaturateCastUSizeToI8 (DN_USize val); +DN_API DN_I16 DN_SaturateCastUSizeToI16 (DN_USize val); +DN_API DN_I32 DN_SaturateCastUSizeToI32 (DN_USize val); +DN_API DN_I64 DN_SaturateCastUSizeToI64 (DN_USize val); + +DN_API int DN_SaturateCastU64ToInt (DN_U64 val); +DN_API DN_I8 DN_SaturateCastU8ToI8 (DN_U64 val); +DN_API DN_I16 DN_SaturateCastU16ToI16 (DN_U64 val); +DN_API DN_I32 DN_SaturateCastU32ToI32 (DN_U64 val); +DN_API DN_I64 DN_SaturateCastU64ToI64 (DN_U64 val); +DN_API DN_UInt DN_SaturateCastU64ToUInt (DN_U64 val); +DN_API DN_U8 DN_SaturateCastU64ToU8 (DN_U64 val); +DN_API DN_U16 DN_SaturateCastU64ToU16 (DN_U64 val); +DN_API DN_U32 DN_SaturateCastU64ToU32 (DN_U64 val); + +DN_API DN_U8 DN_SaturateCastUSizeToU8 (DN_USize val); +DN_API DN_U16 DN_SaturateCastUSizeToU16 (DN_USize val); +DN_API DN_U32 DN_SaturateCastUSizeToU32 (DN_USize val); +DN_API DN_U64 DN_SaturateCastUSizeToU64 (DN_USize val); + +DN_API int DN_SaturateCastISizeToInt (DN_ISize val); +DN_API DN_I8 DN_SaturateCastISizeToI8 (DN_ISize val); +DN_API DN_I16 DN_SaturateCastISizeToI16 (DN_ISize val); +DN_API DN_I32 DN_SaturateCastISizeToI32 (DN_ISize val); +DN_API DN_I64 DN_SaturateCastISizeToI64 (DN_ISize val); + +DN_API DN_UInt DN_SaturateCastISizeToUInt (DN_ISize val); +DN_API DN_U8 DN_SaturateCastISizeToU8 (DN_ISize val); +DN_API DN_U16 DN_SaturateCastISizeToU16 (DN_ISize val); +DN_API DN_U32 DN_SaturateCastISizeToU32 (DN_ISize val); +DN_API DN_U64 DN_SaturateCastISizeToU64 (DN_ISize val); + +DN_API DN_ISize DN_SaturateCastI64ToISize (DN_I64 val); +DN_API DN_I8 DN_SaturateCastI64ToI8 (DN_I64 val); +DN_API DN_I16 DN_SaturateCastI64ToI16 (DN_I64 val); +DN_API DN_I32 DN_SaturateCastI64ToI32 (DN_I64 val); + +DN_API DN_UInt DN_SaturateCastI64ToUInt (DN_I64 val); +DN_API DN_ISize DN_SaturateCastI64ToUSize (DN_I64 val); +DN_API DN_U8 DN_SaturateCastI64ToU8 (DN_I64 val); +DN_API DN_U16 DN_SaturateCastI64ToU16 (DN_I64 val); +DN_API DN_U32 DN_SaturateCastI64ToU32 (DN_I64 val); +DN_API DN_U64 DN_SaturateCastI64ToU64 (DN_I64 val); + +DN_API DN_I8 DN_SaturateCastIntToI8 (int val); +DN_API DN_I16 DN_SaturateCastIntToI16 (int val); +DN_API DN_U8 DN_SaturateCastIntToU8 (int val); +DN_API DN_U16 DN_SaturateCastIntToU16 (int val); +DN_API DN_U32 DN_SaturateCastIntToU32 (int val); +DN_API DN_U64 DN_SaturateCastIntToU64 (int val); + +// NOTE: DN_Asan /////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_ASAN_PoisonMemoryRegion (void const volatile *ptr, DN_USize size); +DN_API void DN_ASAN_UnpoisonMemoryRegion (void const volatile *ptr, DN_USize size); +#endif // !defined(DN_BASE_H) diff --git a/Base/dn_base_assert.h b/Base/dn_base_assert.h new file mode 100644 index 0000000..e1a3cfa --- /dev/null +++ b/Base/dn_base_assert.h @@ -0,0 +1,90 @@ +#if !defined(DN_BASE_ASSERT_H) +#define DN_BASE_ASSERT_H + +#define DN_HardAssert(expr) DN_HardAssertF(expr, "") + +#if defined(DN_FREESTANDING) + #define DN_HardAssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_DebugBreak; \ + (*(int *)0) = 0; \ + } \ + } while (0) +#else + #define DN_HardAssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) +#endif + +#if defined(DN_NO_ASSERT) + #define DN_Assert(...) + #define DN_AssertOnce(...) + #define DN_AssertF(...) + #define DN_AssertFOnce(...) +#else + #if defined(DN_FREESTANDING) + #define DN_AssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_DebugBreak; \ + (*(int *)0) = 0; \ + } \ + } while (0) + + #else + #define DN_AssertF(expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) + #endif + + #define DN_AssertFOnce(expr, fmt, ...) \ + do { \ + static bool once = true; \ + if (!(expr) && once) { \ + once = false; \ + DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ + DN_STR_FMT(stack_trace_), \ + ##__VA_ARGS__); \ + DN_DebugBreak; \ + } \ + } while (0) + + #define DN_Assert(expr) DN_AssertF((expr), "") + #define DN_AssertOnce(expr) DN_AssertFOnce((expr), "") +#endif + +#define DN_InvalidCodePathF(fmt, ...) DN_HardAssertF(0, fmt, ##__VA_ARGS__) +#define DN_InvalidCodePath DN_InvalidCodePathF("Invalid code path triggered") + +// NOTE: Check macro /////////////////////////////////////////////////////////////////////////////// +#define DN_Check(expr) DN_CheckF(expr, "") + +#if defined(DN_FREESTANDING) + #define DN_CheckF(expr, fmt, ...) (expr) +#else + #if defined(DN_NO_CHECK_BREAK) + #define DN_CheckF(expr, fmt, ...) \ + ((expr) ? true : (DN_LOG_WarningF(fmt, ##__VA_ARGS__), false)) + #else + #define DN_CheckF(expr, fmt, ...) \ + ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTrace_Print(128 /*limit*/), DN_DebugBreak, false)) + #endif +#endif + +#endif diff --git a/Base/dn_base_compiler.h b/Base/dn_base_compiler.h new file mode 100644 index 0000000..cd8ff40 --- /dev/null +++ b/Base/dn_base_compiler.h @@ -0,0 +1,160 @@ +#if !defined(DN_BASE_COMPILER_H) +#define DN_BASE_COMPILER_H + +// NOTE: Compiler identification /////////////////////////////////////////////////////////////////// +// Warning! Order is important here, clang-cl on Windows defines _MSC_VER +#if defined(_MSC_VER) + #if defined(__clang__) + #define DN_COMPILER_CLANG_CL + #define DN_COMPILER_CLANG + #else + #define DN_COMPILER_MSVC + #endif +#elif defined(__clang__) + #define DN_COMPILER_CLANG +#elif defined(__GNUC__) + #define DN_COMPILER_GCC +#endif + +// NOTE: __has_feature ///////////////////////////////////////////////////////////////////////////// +// MSVC for example does not support the feature detection macro for instance so we compile it out +#if defined(__has_feature) + #define DN_HAS_FEATURE(expr) __has_feature(expr) +#else + #define DN_HAS_FEATURE(expr) 0 +#endif + +// NOTE: __has_builtin ///////////////////////////////////////////////////////////////////////////// +// MSVC for example does not support the feature detection macro for instance so we compile it out +#if defined(__has_builtin) + #define DN_HAS_BUILTIN(expr) __has_builtin(expr) +#else + #define DN_HAS_BUILTIN(expr) 0 +#endif + +// NOTE: Warning suppression macros //////////////////////////////////////////////////////////////// +#if defined(DN_COMPILER_MSVC) + #define DN_MSVC_WARNING_PUSH __pragma(warning(push)) + #define DN_MSVC_WARNING_DISABLE(...) __pragma(warning(disable :##__VA_ARGS__)) + #define DN_MSVC_WARNING_POP __pragma(warning(pop)) +#else + #define DN_MSVC_WARNING_PUSH + #define DN_MSVC_WARNING_DISABLE(...) + #define DN_MSVC_WARNING_POP +#endif + +#if defined(DN_COMPILER_CLANG) || defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG_CL) + #define DN_GCC_WARNING_PUSH _Pragma("GCC diagnostic push") + #define DN_GCC_WARNING_DISABLE_HELPER_0(x) #x + #define DN_GCC_WARNING_DISABLE_HELPER_1(y) DN_GCC_WARNING_DISABLE_HELPER_0(GCC diagnostic ignored #y) + #define DN_GCC_WARNING_DISABLE(warning) _Pragma(DN_GCC_WARNING_DISABLE_HELPER_1(warning)) + #define DN_GCC_WARNING_POP _Pragma("GCC diagnostic pop") +#else + #define DN_GCC_WARNING_PUSH + #define DN_GCC_WARNING_DISABLE(...) + #define DN_GCC_WARNING_POP +#endif + +// NOTE: Host OS identification //////////////////////////////////////////////////////////////////// +#if defined(_WIN32) + #define DN_OS_WIN32 +#elif defined(__gnu_linux__) || defined(__linux__) + #define DN_OS_UNIX +#endif + +// NOTE: Platform identification /////////////////////////////////////////////////////////////////// +#if !defined(DN_PLATFORM_EMSCRIPTEN) && \ + !defined(DN_PLATFORM_POSIX) && \ + !defined(DN_PLATFORM_WIN32) + #if defined(__aarch64__) || defined(_M_ARM64) + #define DN_PLATFORM_ARM64 + #elif defined(__EMSCRIPTEN__) + #define DN_PLATFORM_EMSCRIPTEN + #elif defined(DN_OS_WIN32) + #define DN_PLATFORM_WIN32 + #else + #define DN_PLATFORM_POSIX + #endif +#endif + +// NOTE: Windows crap ////////////////////////////////////////////////////////////////////////////// +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #if defined(_CRT_SECURE_NO_WARNINGS) + #define DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED + #else + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +// NOTE: Force Inline ////////////////////////////////////////////////////////////////////////////// +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #define DN_FORCE_INLINE __forceinline +#else + #define DN_FORCE_INLINE inline __attribute__((always_inline)) +#endif + +// NOTE: Function/Variable Annotations ///////////////////////////////////////////////////////////// +#if defined(DN_STATIC_API) + #define DN_API static +#else + #define DN_API +#endif + +// NOTE: C/CPP Literals //////////////////////////////////////////////////////////////////////////// +// Declare struct literals that work in both C and C++ because the syntax is different between +// languages. +#if 0 + struct Foo { int a; } + struct Foo foo = DN_LITERAL(Foo){32}; // Works on both C and C++ +#endif + +#if defined(__cplusplus) + #define DN_LITERAL(T) T +#else + #define DN_LITERAL(T) (T) +#endif + +// NOTE: Thread Locals ///////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) + #define DN_THREAD_LOCAL thread_local +#else + #define DN_THREAD_LOCAL _Thread_local +#endif + +// NOTE: C variadic argument annotations /////////////////////////////////////////////////////////// +// TODO: Other compilers +#if defined(DN_COMPILER_MSVC) + #define DN_FMT_ATTRIB _Printf_format_string_ +#else + #define DN_FMT_ATTRIB +#endif + +// NOTE: Type Cast ///////////////////////////////////////////////////////////////////////////////// +#define DN_CAST(val) (val) + +// NOTE: Zero initialisation macro ///////////////////////////////////////////////////////////////// +#if defined(__cplusplus) + #define DN_ZeroInit {} +#else + #define DN_ZeroInit {0} +#endif + +// NOTE: Address sanitizer ///////////////////////////////////////////////////////////////////////// +#if !defined(DN_ASAN_POISON) + #define DN_ASAN_POISON 0 +#endif + +#if !defined(DN_ASAN_VET_POISON) + #define DN_ASAN_VET_POISON 0 +#endif + +#define DN_ASAN_POISON_ALIGNMENT 8 + +#if !defined(DN_ASAN_POISON_GUARD_SIZE) + #define DN_ASAN_POISON_GUARD_SIZE 128 +#endif + +#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + #include +#endif +#endif // !defined(DN_BASE_COMPILER_H) diff --git a/Base/dn_base_containers.cpp b/Base/dn_base_containers.cpp new file mode 100644 index 0000000..cd93ed6 --- /dev/null +++ b/Base/dn_base_containers.cpp @@ -0,0 +1,1290 @@ +#define DN_CONTAINERS_CPP + +// NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_ArrayEraseResult DN_CArray_EraseRange(T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!data || !size || *size == 0 || count == 0) + return result; + + DN_AssertF(count != -1, "There's a bug with negative element erases, see the DN_VArray section in dn_docs.cpp"); + + // NOTE: Caculate the end index of the erase range + DN_ISize abs_count = DN_Abs(count); + DN_USize end_index = 0; + if (count < 0) { + end_index = begin_index - (abs_count - 1); + if (end_index > begin_index) + end_index = 0; + } else { + end_index = begin_index + (abs_count - 1); + if (end_index < begin_index) + end_index = (*size) - 1; + } + + // NOTE: Ensure begin_index < one_past_end_index + if (end_index < begin_index) { + DN_USize tmp = begin_index; + begin_index = end_index; + end_index = tmp; + } + + // NOTE: Ensure indexes are within valid bounds + begin_index = DN_Min(begin_index, *size); + end_index = DN_Min(end_index, *size - 1); + + // NOTE: Erase the items in the range [begin_index, one_past_end_index) + DN_USize one_past_end_index = end_index + 1; + DN_USize erase_count = one_past_end_index - begin_index; + if (erase_count) { + T *end = data + *size; + T *dest = data + begin_index; + if (erase == DN_ArrayErase_Stable) { + T *src = dest + erase_count; + DN_Memmove(dest, src, (end - src) * sizeof(T)); + } else { + T *src = end - erase_count; + DN_Memcpy(dest, src, (end - src) * sizeof(T)); + } + *size -= erase_count; + } + + result.items_erased = erase_count; + result.it_index = begin_index; + return result; +} + +template +T *DN_CArray_MakeArray(T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!data || !size || count == 0) + return nullptr; + + if (!DN_CheckF((*size + count) <= max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, *size, max)) + return nullptr; + + // TODO: Use placement new? Why doesn't this work? + T *result = data + *size; + *size += count; + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result, 0, sizeof(*result) * count); + return result; +} + +template +T *DN_CArray_InsertArray(T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!data || !size || !items || count <= 0 || ((*size + count) > max)) + return result; + + DN_USize clamped_index = DN_Min(index, *size); + if (clamped_index != *size) { + char const *src = DN_CAST(char *)(data + clamped_index); + char const *dest = DN_CAST(char *)(data + (clamped_index + count)); + char const *end = DN_CAST(char *)(data + (*size)); + DN_USize bytes_to_move = end - src; + DN_Memmove(DN_CAST(void *) dest, src, bytes_to_move); + } + + result = data + clamped_index; + DN_Memcpy(result, items, sizeof(T) * count); + *size += count; + return result; +} + +template +T DN_CArray_PopFront(T *data, DN_USize *size, DN_USize count) +{ + T result = {}; + if (!data || !size || *size <= 0) + return result; + + result = data[0]; + DN_USize pop_count = DN_Min(count, *size); + DN_Memmove(data, data + pop_count, (*size - pop_count) * sizeof(T)); + *size -= pop_count; + return result; +} + +template +T DN_CArray_PopBack(T *data, DN_USize *size, DN_USize count) +{ + T result = {}; + if (!data || !size || *size <= 0) + return result; + + DN_USize pop_count = DN_Min(count, *size); + result = data[(*size - 1)]; + *size -= pop_count; + return result; +} + +template +DN_ArrayFindResult DN_CArray_Find(T *data, DN_USize size, T const &value) +{ + DN_ArrayFindResult result = {}; + if (!data || size <= 0) + return result; + + for (DN_USize index = 0; !result.data && index < size; index++) { + T *item = data + index; + if (*item == value) { + result.data = item; + result.index = index; + } + } + + return result; +} + +#if !defined(DN_NO_SARRAY) +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_SArray DN_SArray_Init(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_SArray result = {}; + if (!arena || !size) + return result; + result.data = DN_Arena_NewArray(arena, T, size, zero_mem); + if (result.data) + result.max = size; + return result; +} + +template +DN_SArray DN_SArray_InitSlice(DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_USize max = DN_Max(slice.size, size); + DN_SArray result = DN_SArray_Init(arena, max, DN_ZeroMem_No); + if (DN_SArray_IsValid(&result)) { + DN_SArray_AddArray(&result, slice.data, slice.size); + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result.data + result.size, 0, (result.max - result.size) * sizeof(T)); + } + return result; +} + +template +DN_SArray DN_SArray_InitCArray(DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem zero_mem) +{ + DN_SArray result = DN_SArray_InitSlice(arena, DN_Slice_Init(DN_CAST(T *) array, N), size, zero_mem); + return result; +} + +template +DN_SArray DN_SArray_InitBuffer(T *buffer, DN_USize size) +{ + DN_SArray result = {}; + result.data = buffer; + result.max = size; + return result; +} + +template +bool DN_SArray_IsValid(DN_SArray const *array) +{ + bool result = array && array->data && array->size <= array->max; + return result; +} + +template +DN_Slice DN_SArray_Slice(DN_SArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(DN_CAST(T *) array->data, array->size); + return result; +} + +template +T *DN_SArray_MakeArray(DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_SArray_IsValid(array)) + return nullptr; + T *result = DN_CArray_MakeArray(array->data, &array->size, array->max, count, zero_mem); + return result; +} + +template +T *DN_SArray_Make(DN_SArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_SArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_SArray_AddArray(DN_SArray *array, T const *items, DN_USize count) +{ + T *result = DN_SArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_SArray_AddCArray(DN_SArray *array, T const (&items)[N]) +{ + T *result = DN_SArray_AddArray(array, items, N); + return result; +} + +template +T *DN_SArray_Add(DN_SArray *array, T const &item) +{ + T *result = DN_SArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_SArray_InsertArray(DN_SArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_SArray_IsValid(array)) + return result; + result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + return result; +} + +template +T *DN_SArray_InsertCArray(DN_SArray *array, DN_USize index, T const (&items)[N]) +{ + T *result = DN_SArray_InsertArray(array, index, items, N); + return result; +} + +template +T *DN_SArray_Insert(DN_SArray *array, DN_USize index, T const &item) +{ + T *result = DN_SArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T DN_SArray_PopFront(DN_SArray *array, DN_USize count) +{ + T result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T DN_SArray_PopBack(DN_SArray *array, DN_USize count) +{ + T result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayEraseResult DN_SArray_EraseRange(DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_SArray_IsValid(array) || array->size == 0 || count == 0) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_SArray_Clear(DN_SArray *array) +{ + if (array) + array->size = 0; +} +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +// NOTE: DN_FArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_FArray DN_FArray_Init(T const *array, DN_USize count) +{ + DN_FArray result = {}; + bool added = DN_FArray_AddArray(&result, array, count); + DN_Assert(added); + return result; +} + +template +DN_FArray DN_FArray_InitSlice(DN_Slice slice) +{ + DN_FArray result = DN_FArray_Init(slice.data, slice.size); + return result; +} + +template +DN_FArray DN_FArray_InitCArray(T const (&items)[K]) +{ + DN_FArray result = DN_FArray_Init(items, K); + return result; +} + +template +bool DN_FArray_IsValid(DN_FArray const *array) +{ + bool result = array && array->size <= DN_ArrayCountU(array->data); + return result; +} + +template +DN_Slice DN_FArray_Slice(DN_FArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(DN_CAST(T *) array->data, array->size); + return result; +} + +template +T *DN_FArray_AddArray(DN_FArray *array, T const *items, DN_USize count) +{ + T *result = DN_FArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_FArray_AddCArray(DN_FArray *array, T const (&items)[K]) +{ + T *result = DN_FArray_MakeArray(array, K, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, K * sizeof(T)); + return result; +} + +template +T *DN_FArray_Add(DN_FArray *array, T const &item) +{ + T *result = DN_FArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_FArray_MakeArray(DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_FArray_IsValid(array)) + return nullptr; + T *result = DN_CArray_MakeArray(array->data, &array->size, N, count, zero_mem); + return result; +} + +template +T *DN_FArray_Make(DN_FArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_FArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_FArray_InsertArray(DN_FArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_FArray_IsValid(array)) + return result; + result = DN_CArray_InsertArray(array->data, &array->size, N, index, items, count); + return result; +} + +template +T *DN_FArray_InsertCArray(DN_FArray *array, DN_USize index, T const (&items)[K]) +{ + T *result = DN_FArray_InsertArray(array, index, items, K); + return result; +} + +template +T *DN_FArray_Insert(DN_FArray *array, DN_USize index, T const &item) +{ + T *result = DN_FArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T DN_FArray_PopFront(DN_FArray *array, DN_USize count) +{ + T result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T DN_FArray_PopBack(DN_FArray *array, DN_USize count) +{ + T result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayFindResult DN_FArray_Find(DN_FArray *array, T const &find) +{ + DN_ArrayFindResult result = DN_CArray_Find(array->data, array->size, find); + return result; +} + +template +DN_ArrayEraseResult DN_FArray_EraseRange(DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_FArray_IsValid(array) || array->size == 0 || count == 0) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_FArray_Clear(DN_FArray *array) +{ + if (array) + array->size = 0; +} +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_SLICE) +template +DN_Slice DN_Slice_Init(T *const data, DN_USize size) +{ + DN_Slice result = {}; + if (data) { + result.data = data; + result.size = size; + } + return result; +} + +template +DN_Slice DN_Slice_InitCArrayCopy(DN_Arena *arena, T const (&array)[N]) +{ + DN_Slice result = DN_Slice_Alloc(arena, N, DN_ZeroMem_No); + if (result.data) + DN_Memcpy(result.data, array, sizeof(T) * N); + return result; +} + +template +DN_Slice DN_Slice_CopyPtr(DN_Arena *arena, T *const data, DN_USize size) +{ + T *copy = DN_Arena_NewArrayCopy(arena, T, data, size); + DN_Slice result = DN_Slice_Init(copy, copy ? size : 0); + return result; +} + +template +DN_Slice DN_Slice_Copy(DN_Arena *arena, DN_Slice slice) +{ + DN_Slice result = DN_Slice_CopyPtr(arena, slice.data, slice.size); + return result; +} + +template +DN_Slice DN_Slice_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Slice result = {}; + if (!arena || size == 0) + return result; + result.data = DN_Arena_NewArray(arena, T, size, zero_mem); + if (result.data) + result.size = size; + return result; +} + +#endif // !defined(DN_NO_SLICE) + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; +DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; + +template +DN_DSMap DN_DSMap_Init(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) +{ + DN_DSMap result = {}; + if (!DN_CheckF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size)) + return result; + if (size <= 0) + return result; + if (!DN_Check(arena)) + return result; + result.arena = arena; + result.pool = DN_Pool_Init(arena, DN_POOL_DEFAULT_ALIGN); + result.hash_to_slot = DN_Arena_NewArray(result.arena, DN_U32, size, DN_ZeroMem_Yes); + result.slots = DN_Arena_NewArray(result.arena, DN_DSMapSlot, size, DN_ZeroMem_Yes); + result.occupied = 1; // For sentinel + result.size = size; + result.initial_size = size; + result.flags = flags; + DN_AssertF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?"); + return result; +} + +template +void DN_DSMap_Deinit(DN_DSMap *map, DN_ZeroMem zero_mem) +{ + if (!map) + return; + // TODO(doyle): Use zero_mem + (void)zero_mem; + DN_Arena_Deinit(map->arena); + *map = {}; +} + +template +bool DN_DSMap_IsValid(DN_DSMap const *map) +{ + bool result = map && + map->arena && + map->hash_to_slot && // Hash to slot mapping array must be allocated + map->slots && // Slots array must be allocated + (map->size & (map->size - 1)) == 0 && // Must be power of two size + map->occupied >= 1; // DN_DS_MAP_SENTINEL_SLOT takes up one slot + return result; +} + +template +DN_U32 DN_DSMap_Hash(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_U32 result = 0; + if (!map) + return result; + + if (key.type == DN_DSMapKeyType_U64NoHash) { + result = DN_CAST(DN_U32) key.u64; + return result; + } + + if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { + result = key.hash; + return result; + } + + DN_U32 seed = map->hash_seed ? map->hash_seed : DN_DS_MAP_DEFAULT_HASH_SEED; + if (map->hash_function) { + map->hash_function(key, seed); + } else { + // NOTE: Courtesy of Demetri Spanos (which this hash table was inspired + // from), the following is a hashing function snippet provided for + // reliable, quick and simple quality hashing functions for hash table + // use. + // Source: https://github.com/demetri/scribbles/blob/c475464756c104c91bab83ed4e14badefef12ab5/hashing/ub_aware_hash_functions.c + + char const *key_ptr = nullptr; + DN_U32 len = 0; + DN_U32 h = seed; + switch (key.type) { + case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ + case DN_DSMapKeyType_U64NoHash: DN_InvalidCodePath; /*FALLTHRU*/ + case DN_DSMapKeyType_Invalid: break; + + case DN_DSMapKeyType_Buffer: + key_ptr = DN_CAST(char const *) key.buffer_data; + len = key.buffer_size; + break; + + case DN_DSMapKeyType_U64: + key_ptr = DN_CAST(char const *) & key.u64; + len = sizeof(key.u64); + break; + } + + // Murmur3 32-bit without UB unaligned accesses + // DN_U32 mur3_32_no_UB(const void *key, int len, DN_U32 h) + + // main body, work on 32-bit blocks at a time + for (DN_U32 i = 0; i < len / 4; i++) { + DN_U32 k; + memcpy(&k, &key_ptr[i * 4], sizeof(k)); + + k *= 0xcc9e2d51; + k = ((k << 15) | (k >> 17)) * 0x1b873593; + h = (((h ^ k) << 13) | ((h ^ k) >> 19)) * 5 + 0xe6546b64; + } + + // load/mix up to 3 remaining tail bytes into a tail block + DN_U32 t = 0; + uint8_t *tail = ((uint8_t *)key_ptr) + 4 * (len / 4); + switch (len & 3) { + case 3: t ^= tail[2] << 16; + case 2: t ^= tail[1] << 8; + case 1: { + t ^= tail[0] << 0; + h ^= ((0xcc9e2d51 * t << 15) | (0xcc9e2d51 * t >> 17)) * 0x1b873593; + } + } + + // finalization mix, including key length + h = ((h ^ len) ^ ((h ^ len) >> 16)) * 0x85ebca6b; + h = (h ^ (h >> 13)) * 0xc2b2ae35; + result = h ^ (h >> 16); + } + return result; +} + +template +DN_U32 DN_DSMap_HashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_Assert(key.type != DN_DSMapKeyType_Invalid); + DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; + if (!DN_DSMap_IsValid(map)) + return result; + + result = key.hash & (map->size - 1); + for (;;) { + if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved + result++; + + if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use + return result; + + DN_DSMapSlot *slot = map->slots + map->hash_to_slot[result]; + if (slot->key.type == DN_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key)) + return result; + + result = (result + 1) & (map->size - 1); + } +} + +template +DN_DSMapResult DN_DSMap_Find(DN_DSMap const *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (DN_DSMap_IsValid(map)) { + DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); + if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { + result.slot = map->slots; // NOTE: Set to sentinel value + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.found = true; + } + result.value = &result.slot->value; + } + return result; +} + +template +DN_DSMapResult DN_DSMap_Make(DN_DSMap *map, DN_DSMapKey key) +{ + DN_DSMapResult result = {}; + if (!DN_DSMap_IsValid(map)) + return result; + + DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); + if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { + // NOTE: Create the slot + if (index != DN_DS_MAP_SENTINEL_SLOT) + map->hash_to_slot[index] = map->occupied++; + + // NOTE: Check if resize is required + bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3); + if (map_is_75pct_full) { + if (!DN_DSMap_Resize(map, map->size * 2)) + return result; + result = DN_DSMap_Make(map, key); + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.slot->key = key; // NOTE: Assign key to new slot + if ((key.type == DN_DSMapKeyType_Buffer || + key.type == DN_DSMapKeyType_BufferAsU64NoHash) && + !key.no_copy_buffer) + result.slot->key.buffer_data = DN_Pool_NewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); + } + } else { + result.slot = map->slots + map->hash_to_slot[index]; + result.found = true; + } + + result.value = &result.slot->value; + DN_Assert(result.slot->key.type != DN_DSMapKeyType_Invalid); + return result; +} + +template +DN_DSMapResult DN_DSMap_Set(DN_DSMap *map, DN_DSMapKey key, T const &value) +{ + DN_DSMapResult result = {}; + if (!DN_DSMap_IsValid(map)) + return result; + + result = DN_DSMap_Make(map, key); + result.slot->value = value; + return result; +} + +template +DN_DSMapResult DN_DSMap_FindKeyU64(DN_DSMap const *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Find(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_MakeKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Make(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_SetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + DN_DSMapResult result = DN_DSMap_Set(map, map_key, value); + return result; +} + +template +DN_DSMapResult DN_DSMap_FindKeyStr8(DN_DSMap const *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Find(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_MakeKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Make(map, map_key); + return result; +} + +template +DN_DSMapResult DN_DSMap_SetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + DN_DSMapResult result = DN_DSMap_Set(map, map_key); + return result; +} + +template +bool DN_DSMap_Resize(DN_DSMap *map, DN_U32 size) +{ + if (!DN_DSMap_IsValid(map) || size < map->occupied || size < map->initial_size) + return false; + + DN_Arena *prev_arena = map->arena; + DN_Arena new_arena = {}; + new_arena.mem_funcs = prev_arena->mem_funcs; + new_arena.flags = prev_arena->flags; + new_arena.label = prev_arena->label; + new_arena.prev = prev_arena->prev; + new_arena.next = prev_arena->next; + + DN_DSMap new_map = DN_DSMap_Init(&new_arena, size, map->flags); + if (!DN_DSMap_IsValid(&new_map)) + return false; + + new_map.initial_size = map->initial_size; + for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) { + DN_DSMapSlot *old_slot = map->slots + old_index; + DN_DSMapKey old_key = old_slot->key; + if (old_key.type == DN_DSMapKeyType_Invalid) + continue; + DN_DSMap_Set(&new_map, old_key, old_slot->value); + } + + if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) + DN_DSMap_Deinit(map, DN_ZeroMem_No); + *map = new_map; // Update the map inplace + map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed + *map->arena = new_arena; // Re-init the old arena with the new data + map->pool.arena = map->arena; + return true; +} + +template +bool DN_DSMap_Erase(DN_DSMap *map, DN_DSMapKey key) +{ + if (!DN_DSMap_IsValid(map)) + return false; + + DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); + if (index == 0) + return true; + + DN_U32 slot_index = map->hash_to_slot[index]; + if (slot_index == DN_DS_MAP_SENTINEL_SLOT) + return false; + + // NOTE: Mark the slot as unoccupied + map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT; + + DN_DSMapSlot *slot = map->slots + slot_index; + if (!slot->key.no_copy_buffer) + DN_Pool_Dealloc(&map->pool, DN_CAST(void *) slot->key.buffer_data); + *slot = {}; // TODO: Optional? + + if (map->occupied > 1 /*Sentinel*/) { + // NOTE: Repair the hash chain, e.g. rehash all the items after the removed + // element and reposition them if necessary. + for (DN_U32 probe_index = index;;) { + probe_index = (probe_index + 1) & (map->size - 1); + if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT) + break; + + DN_DSMapSlot *probe = map->slots + map->hash_to_slot[probe_index]; + DN_U32 new_index = probe->key.hash & (map->size - 1); + if (index <= probe_index) { + if (index < new_index && new_index <= probe_index) + continue; + } else { + if (index < new_index || new_index <= probe_index) + continue; + } + + map->hash_to_slot[index] = map->hash_to_slot[probe_index]; + map->hash_to_slot[probe_index] = DN_DS_MAP_SENTINEL_SLOT; + index = probe_index; + } + + // NOTE: We have erased a slot from the hash table, this leaves a gap + // in our contiguous array. After repairing the chain, the hash mapping + // is correct. + // We will now fill in the vacant spot that we erased using the last + // element in the slot list. + if (map->occupied >= 3 /*Ignoring sentinel, at least 2 other elements to unstable erase*/) { + DN_U32 last_index = map->occupied - 1; + if (last_index != slot_index) { + // NOTE: Copy in last slot to the erase slot + DN_DSMapSlot *last_slot = map->slots + last_index; + map->slots[slot_index] = *last_slot; + + // NOTE: Update the hash-to-slot mapping for the value that was copied in + DN_U32 hash_to_slot_index = DN_DSMap_HashToSlotIndex(map, last_slot->key); + map->hash_to_slot[hash_to_slot_index] = slot_index; + *last_slot = {}; // TODO: Optional? + } + } + } + + map->occupied--; + bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1); + if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size) + DN_DSMap_Resize(map, map->size / 2); + + return true; +} + +template +bool DN_DSMap_EraseKeyU64(DN_DSMap *map, DN_U64 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); + bool result = DN_DSMap_Erase(map, map_key); + return result; +} + +template +bool DN_DSMap_EraseKeyStr8(DN_DSMap *map, DN_Str8 key) +{ + DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); + bool result = DN_DSMap_Erase(map, map_key); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyBuffer(DN_DSMap const *map, void const *data, DN_USize size) +{ + DN_Assert(size > 0 && size <= UINT32_MAX); + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_Buffer; + result.buffer_data = data; + result.buffer_size = DN_CAST(DN_U32) size; + result.hash = DN_DSMap_Hash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_U32 size) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_BufferAsU64NoHash; + result.buffer_data = data; + result.buffer_size = DN_CAST(DN_U32) size; + DN_Assert(size >= sizeof(result.hash)); + DN_Memcpy(&result.hash, data, sizeof(result.hash)); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyU64(DN_DSMap const *map, DN_U64 u64) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_U64; + result.u64 = u64; + result.hash = DN_DSMap_Hash(map, result); + return result; +} + +template +DN_DSMapKey DN_DSMap_KeyStr8(DN_DSMap const *map, DN_Str8 string) +{ + DN_DSMapKey result = DN_DSMap_KeyBuffer(map, string.data, string.size); + return result; +} +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +// NOTE: DN_List /////////////////////////////////////////////////////////////////////////////////// +template +DN_List DN_List_Init(DN_USize chunk_size) +{ + DN_List result = {}; + result.chunk_size = chunk_size; + return result; +} + +template +DN_List DN_List_InitCArray(DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]) +{ + DN_List result = DN_List_Init(arena, chunk_size); + DN_ForIndexU(index, N) + DN_List_Add(&result, array[index]); + return result; +} + +template +DN_List DN_List_InitSliceCopy(DN_Arena *arena, DN_USize chunk_size, DN_Slice slice) +{ + DN_List result = DN_List_Init(arena, chunk_size); + DN_ForIndexU(index, slice.size) + DN_List_Add(&result, slice.data[index]); + return result; +} + +template +DN_API bool DN_List_AttachTail_(DN_List *list, DN_ListChunk *tail) +{ + if (!tail) + return false; + + if (list->tail) { + list->tail->next = tail; + tail->prev = list->tail; + } + + list->tail = tail; + + if (!list->head) + list->head = list->tail; + return true; +} + +template +DN_API DN_ListChunk *DN_List_AllocArena_(DN_List *list, DN_Arena *arena, DN_USize count) +{ + auto *result = DN_Arena_New(arena, DN_ListChunk, DN_ZeroMem_Yes); + DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(arena); + if (!result) + return nullptr; + + DN_USize items = DN_Max(list->chunk_size, count); + result->data = DN_Arena_NewArray(arena, T, items, DN_ZeroMem_Yes); + result->size = items; + if (!result->data) { + DN_Arena_TempMemEnd(tmem); + result = nullptr; + } + + DN_List_AttachTail_(list, result); + return result; +} + +template +DN_API DN_ListChunk *DN_List_AllocPool_(DN_List *list, DN_Pool *pool, DN_USize count) +{ + auto *result = DN_Pool_New(pool, DN_ListChunk); + if (!result) + return nullptr; + + DN_USize items = DN_Max(list->chunk_size, count); + result->data = DN_Pool_NewArray(pool, T, items); + result->size = items; + if (!result->data) { + DN_Pool_Dealloc(result); + result = nullptr; + } + + DN_List_AttachTail_(list, result); + return result; +} + +template +DN_API T *DN_List_MakeArena(DN_List *list, DN_Arena *arena, DN_USize count) +{ + if (list->chunk_size == 0) + list->chunk_size = 128; + + if (!list->tail || (list->tail->count + count) > list->tail->size) { + if (!DN_List_AllocArena_(list, arena, count)) + return nullptr; + } + + T *result = list->tail->data + list->tail->count; + list->tail->count += count; + list->count += count; + return result; +} + +template +DN_API T *DN_List_MakePool(DN_List *list, DN_Pool *pool, DN_USize count) +{ + if (list->chunk_size == 0) + list->chunk_size = 128; + + if (!list->tail || (list->tail->count + count) > list->tail->size) { + if (!DN_List_AllocPool_(list, pool, count)) + return nullptr; + } + + T *result = list->tail->data + list->tail->count; + list->tail->count += count; + list->count += count; + return result; +} + +template +DN_API T *DN_List_AddArena(DN_List *list, DN_Arena *arena, T const &value) +{ + T *result = DN_List_MakeArena(list, arena, 1); + *result = value; + return result; +} + +template +DN_API T *DN_List_AddPool(DN_List *list, DN_Pool *pool, T const &value) +{ + T *result = DN_List_MakePool(list, pool, 1); + *result = value; + return result; +} + +template +DN_API bool DN_List_AddCArray(DN_List *list, T const (&array)[N]) +{ + if (!list) + return false; + for (T const &item : array) + if (!DN_List_Add(list, item)) + return false; + return true; +} + +template +DN_API void DN_List_AddListArena(DN_List *list, DN_Arena *arena, DN_List other) +{ + if (!list) + return; + // TODO(doyle): Copy chunk by chunk + for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/);) + DN_List_AddArena(list, arena, *it.data); +} + +template +DN_API void DN_List_AddListPool(DN_List *list, DN_Pool *pool, DN_List other) +{ + if (!list) + return; + // TODO(doyle): Copy chunk by chunk + for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/);) + DN_List_AddPool(list, pool, *it.data); +} + +template +void DN_List_Clear(DN_List *list) +{ + if (!list) + return; + list->head = list->tail = nullptr; + list->count = 0; +} + +template +DN_API bool DN_List_Iterate(DN_List *list, DN_ListIterator *it, DN_USize start_index) +{ + bool result = false; + if (!list || !it || list->chunk_size <= 0) + return result; + + if (it->init) { + it->index++; + } else { + *it = {}; + if (start_index == 0) { + it->chunk = list->head; + } else { + DN_List_At(list, start_index, &it->chunk); + if (list->chunk_size > 0) + it->chunk_data_index = start_index % list->chunk_size; + } + + it->init = true; + } + + if (it->chunk) { + if (it->chunk_data_index >= it->chunk->count) { + it->chunk = it->chunk->next; + it->chunk_data_index = 0; + } + + if (it->chunk) { + it->data = it->chunk->data + it->chunk_data_index++; + result = true; + } + } + + if (!it->chunk) + DN_Assert(result == false); + return result; +} + +template +DN_API T *DN_List_At(DN_List *list, DN_USize index, DN_ListChunk **at_chunk) +{ + if (!list || index >= list->count || !list->head) + return nullptr; + + // NOTE: Scan forwards to the chunk we need. We don't have random access to chunks + DN_ListChunk **chunk = &list->head; + DN_USize running_index = index; + for (; (*chunk) && running_index >= (*chunk)->size; chunk = &((*chunk)->next)) + running_index -= (*chunk)->size; + + T *result = nullptr; + if (*chunk) + result = (*chunk)->data + running_index; + + if (result && at_chunk) + *at_chunk = *chunk; + + return result; +} + +template +DN_Slice DN_List_ToSliceCopy(DN_List const *list, DN_Arena *arena) +{ + // TODO(doyle): Chunk memcopies is much faster + DN_Slice result = DN_Slice_Alloc(arena, list->count, DN_ZeroMem_No); + if (result.size) { + DN_USize slice_index = 0; + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6011) // Dereferencing NULL pointer 'x' + for (DN_ListIterator it = {}; DN_List_Iterate(DN_CAST(DN_List *) list, &it, 0);) + result.data[slice_index++] = *it.data; + DN_MSVC_WARNING_POP + DN_Assert(slice_index == result.size); + } + return result; +} +#endif // !defined(DN_NO_LIST) + +// NOTE: DN_Slice ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_Slice_Str8Render(DN_Arena *arena, DN_Slice array, DN_Str8 separator) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) + total_size += separator.size; + DN_Str8 item = array.data[index]; + total_size += item.size; + } + + result = DN_Str8_Alloc(arena, total_size, DN_ZeroMem_No); + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size); + write_index += separator.size; + } + DN_Str8 item = array.data[index]; + DN_Memcpy(result.data + write_index, item.data, item.size); + write_index += item.size; + } + } + + return result; +} + +DN_API DN_Str8 DN_Slice_Str8RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) +{ + DN_Str8 result = DN_Slice_Str8Render(arena, array, DN_STR8(" ")); + return result; +} + +DN_API DN_Str16 DN_Slice_Str16Render(DN_Arena *arena, DN_Slice array, DN_Str16 separator) +{ + DN_Str16 result = {}; + if (!arena) + return result; + + DN_USize total_size = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) + total_size += separator.size; + DN_Str16 item = array.data[index]; + total_size += item.size; + } + + result = {DN_Arena_NewArray(arena, wchar_t, total_size + 1, DN_ZeroMem_No), total_size}; + if (result.data) { + DN_USize write_index = 0; + for (DN_USize index = 0; index < array.size; index++) { + if (index) { + DN_Memcpy(result.data + write_index, separator.data, separator.size * sizeof(result.data[0])); + write_index += separator.size; + } + DN_Str16 item = array.data[index]; + DN_Memcpy(result.data + write_index, item.data, item.size * sizeof(result.data[0])); + write_index += item.size; + } + } + + result.data[total_size] = 0; + return result; +} + +DN_API DN_Str16 DN_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) +{ + DN_Str16 result = DN_Slice_Str16Render(arena, array, DN_STR16(L" ")); + return result; +} + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash(DN_U64 u64) +{ + DN_DSMapKey result = {}; + result.type = DN_DSMapKeyType_U64NoHash; + result.u64 = u64; + result.hash = DN_CAST(DN_U32) u64; + return result; +} + +DN_API bool DN_DSMap_KeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs) +{ + bool result = false; + if (lhs.type == rhs.type && lhs.hash == rhs.hash) { + switch (lhs.type) { + case DN_DSMapKeyType_Invalid: result = true; break; + case DN_DSMapKeyType_U64NoHash: result = true; break; + case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break; + + case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ + case DN_DSMapKeyType_Buffer: { + if (lhs.buffer_size == rhs.buffer_size) + result = DN_Memcmp(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0; + } break; + } + } + return result; +} + +DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) +{ + bool result = DN_DSMap_KeyEquals(lhs, rhs); + return result; +} +#endif // !defined(DN_NO_DSMAP) diff --git a/Base/dn_base_containers.h b/Base/dn_base_containers.h new file mode 100644 index 0000000..38d8b39 --- /dev/null +++ b/Base/dn_base_containers.h @@ -0,0 +1,277 @@ +#if !defined(DN_CONTAINERS_H) +#define DN_CONTAINERS_H + +// NOTE: DN_CArray ///////////////////////////////////////////////////////////////////////////////// +enum DN_ArrayErase +{ + DN_ArrayErase_Unstable, + DN_ArrayErase_Stable, +}; + +struct DN_ArrayEraseResult +{ + // The next index your for-index should be set to such that you can continue + // to iterate the remainder of the array, e.g: + // + // for (DN_USize index = 0; index < array.size; index++) { + // if (erase) + // index = DN_FArray_EraseRange(&array, index, -3, DN_ArrayErase_Unstable); + // } + DN_USize it_index; + DN_USize items_erased; // The number of items erased +}; + +template struct DN_ArrayFindResult +{ + T *data; // Pointer to the value if a match is found, null pointer otherwise + DN_USize index; // Index to the value if a match is found, 0 otherwise +}; + +#if !defined(DN_NO_SARRAY) +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +template struct DN_SArray +{ + T *data; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + DN_USize max; // Maximum number of items this array can store + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +// NOTE: DN_FArray ///////////////////////////////////////////////////////////////////////////////// +template struct DN_FArray +{ + T data[N]; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; + +template using DN_FArray8 = DN_FArray; +template using DN_FArray16 = DN_FArray; +template using DN_FArray32 = DN_FArray; +template using DN_FArray64 = DN_FArray; +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_DSMAP) +// NOTE: DN_DSMap ////////////////////////////////////////////////////////////////////////////////// +enum DN_DSMapKeyType +{ + // Key | Key Hash | Map Index + DN_DSMapKeyType_Invalid, + DN_DSMapKeyType_U64, // U64 | Hash(U64) | Hash(U64) % map_size + DN_DSMapKeyType_U64NoHash, // U64 | U64 | U64 % map_size + DN_DSMapKeyType_Buffer, // Buffer | Hash(buffer) | Hash(buffer) % map_size + DN_DSMapKeyType_BufferAsU64NoHash, // Buffer | U64(buffer[0:4]) | U64(buffer[0:4]) % map_size +}; + +struct DN_DSMapKey +{ + DN_DSMapKeyType type; + DN_U32 hash; // Hash to lookup in the map. If it equals, we check that the original key payload matches + void const *buffer_data; + DN_U32 buffer_size; + DN_U64 u64; + bool no_copy_buffer; +}; + +template +struct DN_DSMapSlot +{ + DN_DSMapKey key; // Hash table lookup key + T value; // Hash table value +}; + +typedef DN_U32 DN_DSMapFlags; +enum DN_DSMapFlags_ +{ + DN_DSMapFlags_Nil = 0, + DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, +}; + +using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); +template struct DN_DSMap +{ + DN_U32 *hash_to_slot; // Mapping from hash to a index in the slots array + DN_DSMapSlot *slots; // Values of the array stored contiguously, non-sorted order + DN_U32 size; // Total capacity of the map and is a power of two + DN_U32 occupied; // Number of slots used in the hash table + DN_Arena *arena; // Backing arena for the hash table + DN_Pool pool; // Allocator for keys that are variable-sized buffers + DN_U32 initial_size; // Initial map size, map cannot shrink on erase below this size + DN_DSMapHashFunction *hash_function; // Custom hashing function to use if field is set + DN_U32 hash_seed; // Seed for the hashing function, when 0, DN_DS_MAP_DEFAULT_HASH_SEED is used + DN_DSMapFlags flags; +}; + +template struct DN_DSMapResult +{ + bool found; + DN_DSMapSlot *slot; + T *value; +}; +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +// NOTE: DN_List /////////////////////////////////////////////////////////////////////////////////// +template struct DN_ListChunk +{ + T *data; + DN_USize size; + DN_USize count; + DN_ListChunk *next; + DN_ListChunk *prev; +}; + +template struct DN_ListIterator +{ + DN_B32 init; // True if DN_List_Iterate has been called at-least once on this iterator + DN_ListChunk *chunk; // The chunk that the iterator is reading from + DN_USize chunk_data_index; // The index in the chunk the iterator is referencing + DN_USize index; // The index of the item in the list as if it was flat array + T *data; // Pointer to the data the iterator is referencing. Nullptr if invalid. +}; + +template struct DN_List +{ + DN_USize count; // Cumulative count of all items made across all list chunks + DN_USize chunk_size; // When new ListChunk's are required, the minimum 'data' entries to allocate for that node. + DN_ListChunk *head; + DN_ListChunk *tail; +}; +#endif // !defined(DN_NO_LIST) + +template DN_ArrayEraseResult DN_CArray_EraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template T * DN_CArray_MakeArray (T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_CArray_InsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count); +template T DN_CArray_PopFront (T *data, DN_USize *size, DN_USize count); +template T DN_CArray_PopBack (T *data, DN_USize *size, DN_USize count); +template DN_ArrayFindResult DN_CArray_Find (T *data, DN_USize size, T const &value); + +// NOTE: DN_SArray ///////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_SARRAY) +template DN_SArray DN_SArray_Init (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); +template DN_SArray DN_SArray_InitSlice (DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem); +template DN_SArray DN_SArray_InitCArray (DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem); +template DN_SArray DN_SArray_InitBuffer (T* buffer, DN_USize size); +template bool DN_SArray_IsValid (DN_SArray const *array); +template DN_Slice DN_SArray_Slice (DN_SArray const *array); +template T * DN_SArray_AddArray (DN_SArray *array, T const *items, DN_USize count); +template T * DN_SArray_AddCArray (DN_SArray *array, T const (&items)[N]); +template T * DN_SArray_Add (DN_SArray *array, T const &item); +#define DN_SArray_AddArrayAssert(...) DN_HardAssert(DN_SArray_AddArray(__VA_ARGS__)) +#define DN_SArray_AddCArrayAssert(...) DN_HardAssert(DN_SArray_AddCArray(__VA_ARGS__)) +#define DN_SArray_AddAssert(...) DN_HardAssert(DN_SArray_Add(__VA_ARGS__)) +template T * DN_SArray_MakeArray (DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_SArray_Make (DN_SArray *array, DN_ZeroMem zero_mem); +#define DN_SArray_MakeArrayAssert(...) DN_HardAssert(DN_SArray_MakeArray(__VA_ARGS__)) +#define DN_SArray_MakeAssert(...) DN_HardAssert(DN_SArray_Make(__VA_ARGS__)) +template T * DN_SArray_InsertArray (DN_SArray *array, DN_USize index, T const *items, DN_USize count); +template T * DN_SArray_InsertCArray (DN_SArray *array, DN_USize index, T const (&items)[N]); +template T * DN_SArray_Insert (DN_SArray *array, DN_USize index, T const &item); +#define DN_SArray_InsertArrayAssert(...) DN_HardAssert(DN_SArray_InsertArray(__VA_ARGS__)) +#define DN_SArray_InsertCArrayAssert(...) DN_HardAssert(DN_SArray_InsertCArray(__VA_ARGS__)) +#define DN_SArray_InsertAssert(...) DN_HardAssert(DN_SArray_Insert(__VA_ARGS__)) +template T DN_SArray_PopFront (DN_SArray *array, DN_USize count); +template T DN_SArray_PopBack (DN_SArray *array, DN_USize count); +template DN_ArrayEraseResult DN_SArray_EraseRange (DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_SArray_Clear (DN_SArray *array); +#endif // !defined(DN_NO_SARRAY) + +#if !defined(DN_NO_FARRAY) +#define DN_FArray_ToSArray(array) DN_SArray_InitBuffer((array)->data, DN_ArrayCountU((array)->data)) +template DN_FArray DN_FArray_Init (T const *array, DN_USize count); +#define DN_FArray_HasData(array) ((array).data && (array).size) +template DN_FArray DN_FArray_InitSlice (DN_Slice slice); +template DN_FArray DN_FArray_InitCArray (T const (&items)[K]); +template bool DN_FArray_IsValid (DN_FArray const *array); +template DN_USize DN_FArray_Max (DN_FArray const *) { return N; } +template DN_Slice DN_FArray_Slice (DN_FArray const *array); +template T * DN_FArray_AddArray (DN_FArray *array, T const *items, DN_USize count); +template T * DN_FArray_AddCArray (DN_FArray *array, T const (&items)[K]); +template T * DN_FArray_Add (DN_FArray *array, T const &item); +#define DN_FArray_AddArrayAssert(...) DN_HardAssert(DN_FArray_AddArray(__VA_ARGS__)) +#define DN_FArray_AddCArrayAssert(...) DN_HardAssert(DN_FArray_AddCArray(__VA_ARGS__)) +#define DN_FArray_AddAssert(...) DN_HardAssert(DN_FArray_Add(__VA_ARGS__)) +template T * DN_FArray_MakeArray (DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_FArray_Make (DN_FArray *array, DN_ZeroMem zero_mem); +#define DN_FArray_MakeArrayAssert(...) DN_HardAssert(DN_FArray_MakeArray(__VA_ARGS__)) +#define DN_FArray_MakeAssert(...) DN_HardAssert(DN_FArray_Make(__VA_ARGS__)) +template T * DN_FArray_InsertArray (DN_FArray *array, T const &item, DN_USize index); +template T * DN_FArray_InsertCArray (DN_FArray *array, DN_USize index, T const (&items)[K]); +template T * DN_FArray_Insert (DN_FArray *array, DN_USize index, T const &item); +#define DN_FArray_InsertArrayAssert(...) DN_HardAssert(DN_FArray_InsertArray(__VA_ARGS__)) +#define DN_FArray_InsertAssert(...) DN_HardAssert(DN_FArray_Insert(__VA_ARGS__)) +template T DN_FArray_PopFront (DN_FArray *array, DN_USize count); +template T DN_FArray_PopBack (DN_FArray *array, DN_USize count); +template DN_ArrayFindResult DN_FArray_Find (DN_FArray *array, T const &find); +template DN_ArrayEraseResult DN_FArray_EraseRange (DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_FArray_Clear (DN_FArray *array); +#endif // !defined(DN_NO_FARRAY) + +#if !defined(DN_NO_SLICE) +#define DN_TO_SLICE(val) DN_Slice_Init((val)->data, (val)->size) +#define DN_Slice_InitCArray(array) DN_Slice_Init(array, DN_ArrayCountU(array)) +template DN_Slice DN_Slice_Init (T* const data, DN_USize size); +template DN_Slice DN_Slice_InitCArrayCopy (DN_Arena *arena, T const (&array)[N]); +template DN_Slice DN_Slice_Copy (DN_Arena *arena, DN_Slice slice); +template DN_Slice DN_Slice_CopyPtr (DN_Arena *arena, T* const data, DN_USize size); +template DN_Slice DN_Slice_Alloc (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); + DN_Str8 DN_Slice_Str8Render (DN_Arena *arena, DN_Slice array, DN_Str8 separator); + DN_Str8 DN_Slice_Str8RenderSpaceSeparated (DN_Arena *arena, DN_Slice array); + DN_Str16 DN_Slice_Str16Render (DN_Arena *arena, DN_Slice array, DN_Str16 separator); + DN_Str16 DN_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array); +#endif // !defined(DN_NO_SLICE) + +#if !defined(DN_NO_DSMAP) +template DN_DSMap DN_DSMap_Init (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); +template void DN_DSMap_Deinit (DN_DSMap *map, DN_ZeroMem zero_mem); +template bool DN_DSMap_IsValid (DN_DSMap const *map); +template DN_U32 DN_DSMap_Hash (DN_DSMap const *map, DN_DSMapKey key); +template DN_U32 DN_DSMap_HashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Find (DN_DSMap const *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Make (DN_DSMap *map, DN_DSMapKey key); +template DN_DSMapResult DN_DSMap_Set (DN_DSMap *map, DN_DSMapKey key, T const &value); +template DN_DSMapResult DN_DSMap_FindKeyU64 (DN_DSMap const *map, DN_U64 key); +template DN_DSMapResult DN_DSMap_MakeKeyU64 (DN_DSMap *map, DN_U64 key); +template DN_DSMapResult DN_DSMap_SetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); +template DN_DSMapResult DN_DSMap_FindKeyStr8 (DN_DSMap const *map, DN_Str8 key); +template DN_DSMapResult DN_DSMap_MakeKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapResult DN_DSMap_SetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); +template bool DN_DSMap_Resize (DN_DSMap *map, DN_U32 size); +template bool DN_DSMap_Erase (DN_DSMap *map, DN_DSMapKey key); +template bool DN_DSMap_EraseKeyU64 (DN_DSMap *map, DN_U64 key); +template bool DN_DSMap_EraseKeyStr8 (DN_DSMap *map, DN_Str8 key); +template DN_DSMapKey DN_DSMap_KeyBuffer (DN_DSMap const *map, void const *data, DN_U32 size); +template DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_U32 size); +template DN_DSMapKey DN_DSMap_KeyU64 (DN_DSMap const *map, DN_U64 u64); +template DN_DSMapKey DN_DSMap_KeyStr8 (DN_DSMap const *map, DN_Str8 string); +#define DN_DSMap_KeyCStr8(map, string) DN_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) +DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash (DN_U64 u64); +DN_API bool DN_DSMap_KeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); +DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); +#endif // !defined(DN_NO_DSMAP) + +#if !defined(DN_NO_LIST) +template DN_List DN_List_Init (DN_USize chunk_size); +template DN_List DN_List_InitCArray (DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]); +template T * DN_List_At (DN_List *list, DN_USize index, DN_ListChunk **at_chunk); +template void DN_List_Clear (DN_List *list); +template bool DN_List_Iterate (DN_List *list, DN_ListIterator *it, DN_USize start_index); +template T * DN_List_MakeArena (DN_List *list, DN_Arena *arena, DN_USize count); +template T * DN_List_MakePool (DN_List *list, DN_Pool *pool, DN_USize count); +template T * DN_List_AddArena (DN_List *list, DN_Arena *arena, T const &value); +template T * DN_List_AddPool (DN_List *list, DN_Pool *pool, T const &value); +template void DN_List_AddListArena (DN_List *list, DN_Arena *arena, DN_List other); +template void DN_List_AddListArena (DN_List *list, DN_Pool *pool, DN_List other); +template DN_Slice DN_List_ToSliceCopy (DN_List const *list, DN_Arena* arena); +#endif // !defined(DN_NO_LIST) +#endif // !defined(DN_CONTAINER_H) diff --git a/Base/dn_base_convert.cpp b/Base/dn_base_convert.cpp new file mode 100644 index 0000000..425976e --- /dev/null +++ b/Base/dn_base_convert.cpp @@ -0,0 +1,364 @@ +#define DN_CONVERT_CPP + +DN_API int DN_CVT_FmtBuffer3DotTruncate(char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int size_required = DN_VSNPrintF(buffer, size, fmt, args); + int result = DN_Max(DN_Min(size_required, size - 1), 0); + if (result == size - 1) { + buffer[size - 2] = '.'; + buffer[size - 3] = '.'; + } + va_end(args); + return result; +} + +DN_API DN_CVTU64Str8 DN_CVT_U64ToStr8(uint64_t val, char separator) +{ + DN_CVTU64Str8 result = {}; + if (val == 0) { + result.data[result.size++] = '0'; + } else { + // NOTE: The number is written in reverse because we form the string by + // dividing by 10, so we write it in, then reverse it out after all is + // done. + DN_CVTU64Str8 temp = {}; + for (DN_USize digit_count = 0; val > 0; digit_count++) { + if (separator && (digit_count != 0) && (digit_count % 3 == 0)) + temp.data[temp.size++] = separator; + + auto digit = DN_CAST(char)(val % 10); + temp.data[temp.size++] = '0' + digit; + val /= 10; + } + + // NOTE: Reverse the string + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop + DN_MSVC_WARNING_DISABLE(6385) // Reading invalid data from 'temp.data' unsigned overflow is valid for loop termination + for (DN_USize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { + char ch = temp.data[temp_index]; + result.data[result.size++] = ch; + } + DN_MSVC_WARNING_POP + } + + return result; +} + +DN_API DN_CVTU64ByteSize DN_CVT_U64ToByteSize(uint64_t bytes, DN_CVTU64ByteSizeType desired_type) +{ + DN_CVTU64ByteSize result = {}; + result.bytes = DN_CAST(DN_F64) bytes; + if (!DN_Check(desired_type != DN_CVTU64ByteSizeType_Count)) { + result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); + return result; + } + + if (desired_type == DN_CVTU64ByteSizeType_Auto) + for (; result.type < DN_CVTU64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + else + for (; result.type < desired_type; result.type = DN_CAST(DN_CVTU64ByteSizeType)(DN_CAST(DN_USize) result.type + 1)) + result.bytes /= 1024.0; + + result.suffix = DN_CVT_U64ByteSizeTypeString(result.type); + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToByteSizeStr8(DN_Arena *arena, uint64_t bytes, DN_CVTU64ByteSizeType desired_type) +{ + DN_CVTU64ByteSize byte_size = DN_CVT_U64ToByteSize(bytes, desired_type); + DN_Str8 result = DN_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DN_STR_FMT(byte_size.suffix)); + return result; +} + +DN_API DN_Str8 DN_CVT_U64ByteSizeTypeString(DN_CVTU64ByteSizeType type) +{ + DN_Str8 result = DN_STR8(""); + switch (type) { + case DN_CVTU64ByteSizeType_B: result = DN_STR8("B"); break; + case DN_CVTU64ByteSizeType_KiB: result = DN_STR8("KiB"); break; + case DN_CVTU64ByteSizeType_MiB: result = DN_STR8("MiB"); break; + case DN_CVTU64ByteSizeType_GiB: result = DN_STR8("GiB"); break; + case DN_CVTU64ByteSizeType_TiB: result = DN_STR8("TiB"); break; + case DN_CVTU64ByteSizeType_Count: result = DN_STR8(""); break; + case DN_CVTU64ByteSizeType_Auto: result = DN_STR8(""); break; + } + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToAge(DN_Arena *arena, DN_U64 age_s, DN_CVTU64AgeUnit unit) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + char buffer[512]; + DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); + DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); + DN_U64 remainder = age_s; + + if (unit & DN_CVTU64AgeUnit_Year) { + DN_USize value = remainder / DN_YearsToSec(1); + remainder -= DN_YearsToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuyr", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Week) { + DN_USize value = remainder / DN_WeeksToSec(1); + remainder -= DN_WeeksToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuw", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Day) { + DN_USize value = remainder / DN_DaysToSec(1); + remainder -= DN_DaysToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zud", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Hr) { + DN_USize value = remainder / DN_HoursToSec(1); + remainder -= DN_HoursToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zuh", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Min) { + DN_USize value = remainder / DN_MinutesToSec(1); + remainder -= DN_MinutesToSec(value); + if (value) + DN_Str8Builder_AppendF(&builder, "%s%zum", builder.string_size ? " " : "", value); + } + + if (unit & DN_CVTU64AgeUnit_Sec) { + DN_USize value = remainder; + if (value || builder.string_size == 0) + DN_Str8Builder_AppendF(&builder, "%s%zus", builder.string_size ? " " : "", value); + } + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_CVT_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit unit) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + char buffer[128]; + DN_Arena stack_arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_NoPoison); + DN_Str8Builder builder = DN_Str8Builder_Init(&stack_arena); + DN_F64 remainder = age_s; + + if (unit & DN_CVTU64AgeUnit_Year) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_YearsToSec(1); + if (value >= 1.0) { + remainder -= DN_YearsToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fyr", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Week) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_WeeksToSec(1); + if (value >= 1.0) { + remainder -= DN_WeeksToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fw", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Day) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_DaysToSec(1); + if (value >= 1.0) { + remainder -= DN_WeeksToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fd", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Hr) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_HoursToSec(1); + if (value >= 1.0) { + remainder -= DN_HoursToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fh", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Min) { + DN_F64 value = remainder / DN_CAST(DN_F64) DN_MinutesToSec(1); + if (value >= 1.0) { + remainder -= DN_MinutesToSec(value); + DN_Str8Builder_AppendF(&builder, "%s%.1fm", builder.string_size ? " " : "", value); + } + } + + if (unit & DN_CVTU64AgeUnit_Sec) { + DN_F64 value = remainder; + DN_Str8Builder_AppendF(&builder, "%s%.1fs", builder.string_size ? " " : "", value); + } + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API uint64_t DN_CVT_HexToU64(DN_Str8 hex) +{ + DN_Str8 real_hex = DN_Str8_TrimPrefix(DN_Str8_TrimPrefix(hex, DN_STR8("0x")), DN_STR8("0X")); + DN_USize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/; + DN_Assert(real_hex.size <= max_hex_size); + + DN_USize size = DN_Min(max_hex_size, real_hex.size); + uint64_t result = 0; + for (DN_USize index = 0; index < size; index++) { + char ch = real_hex.data[index]; + DN_CharHexToU8 val = DN_Char_HexToU8(ch); + if (!val.success) + break; + result = (result << 4) | val.value; + } + return result; +} + +DN_API DN_Str8 DN_CVT_U64ToHex(DN_Arena *arena, uint64_t number, uint32_t flags) +{ + DN_Str8 prefix = {}; + if ((flags & DN_CVTHexU64Str8Flags_0xPrefix)) + prefix = DN_STR8("0x"); + + char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; + DN_USize required_size = DN_CStr8_FSize(fmt, number) + prefix.size; + DN_Str8 result = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); + + if (DN_Str8_HasData(result)) { + DN_Memcpy(result.data, prefix.data, prefix.size); + int space = DN_CAST(int) DN_Max((result.size - prefix.size) + 1, 0); /*null-terminator*/ + DN_SNPrintF(result.data + prefix.size, space, fmt, number); + } + return result; +} + +DN_API DN_CVTU64HexStr8 DN_CVT_U64ToHexStr8(uint64_t number, DN_CVTHexU64Str8Flags flags) +{ + DN_Str8 prefix = {}; + if (flags & DN_CVTHexU64Str8Flags_0xPrefix) + prefix = DN_STR8("0x"); + + DN_CVTU64HexStr8 result = {}; + DN_Memcpy(result.data, prefix.data, prefix.size); + result.size += DN_CAST(int8_t) prefix.size; + + char const *fmt = (flags & DN_CVTHexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; + int size = DN_SNPrintF(result.data + result.size, DN_ArrayCountU(result.data) - result.size, fmt, number); + result.size += DN_CAST(uint8_t) size; + DN_Assert(result.size < DN_ArrayCountU(result.data)); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not, but, always null terminates so + // the last byte is wasted. + result.size = DN_Min(result.size, DN_ArrayCountU(result.data) - 1); + return result; +} + +DN_API bool DN_CVT_BytesToHexPtr(void const *src, DN_USize src_size, char *dest, DN_USize dest_size) +{ + if (!src || !dest) + return false; + + if (!DN_Check(dest_size >= src_size * 2)) + return false; + + char const *HEX = "0123456789abcdef"; + unsigned char const *src_u8 = DN_CAST(unsigned char const *) src; + for (DN_USize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index++] = HEX[(int)hex01]; + dest[dest_index++] = HEX[(int)hex02]; + } + + return true; +} + +DN_API DN_Str8 DN_CVT_BytesToHex(DN_Arena *arena, void const *src, DN_USize size) +{ + DN_Str8 result = {}; + if (!src || size <= 0) + return result; + + result = DN_Str8_Alloc(arena, size * 2, DN_ZeroMem_No); + result.data[result.size] = 0; + bool converted = DN_CVT_BytesToHexPtr(src, size, result.data, result.size); + DN_Assert(converted); + return result; +} + +DN_API DN_USize DN_CVT_HexToBytesPtrUnchecked(DN_Str8 hex, void *dest, DN_USize dest_size) +{ + DN_USize result = 0; + unsigned char *dest_u8 = DN_CAST(unsigned char *) dest; + + for (DN_USize hex_index = 0; hex_index < hex.size; hex_index += 2, result += 1) { + char hex01 = hex.data[hex_index]; + char hex02 = (hex_index + 1 < hex.size) ? hex.data[hex_index + 1] : 0; + char bit4_01 = DN_Char_HexToU8(hex01).value; + char bit4_02 = DN_Char_HexToU8(hex02).value; + char byte = (bit4_01 << 4) | (bit4_02 << 0); + dest_u8[result] = byte; + } + + DN_Assert(result <= dest_size); + return result; +} + +DN_API DN_USize DN_CVT_HexToBytesPtr(DN_Str8 hex, void *dest, DN_USize dest_size) +{ + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); + + DN_USize result = 0; + if (!DN_Str8_HasData(hex)) + return result; + + // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" + // Either way, the size can be odd or even, hence we round up to the nearest + // multiple of two to ensure that we calculate the min buffer size orrectly. + DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); + DN_USize min_buffer_size = hex_size_rounded_up / 2; + if (hex.size <= 0 || !DN_Check(dest_size >= min_buffer_size)) + return result; + + result = DN_CVT_HexToBytesPtrUnchecked(hex, dest, dest_size); + return result; +} + +DN_API DN_Str8 DN_CVT_HexToBytesUnchecked(DN_Arena *arena, DN_Str8 hex) +{ + DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); + DN_Str8 result = DN_Str8_Alloc(arena, (hex_size_rounded_up / 2), DN_ZeroMem_No); + if (result.data) { + DN_USize bytes_written = DN_CVT_HexToBytesPtr(hex, result.data, result.size); + DN_Assert(bytes_written == result.size); + } + return result; +} + +DN_API DN_Str8 DN_CVT_HexToBytes(DN_Arena *arena, DN_Str8 hex) +{ + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); + hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); + + DN_Str8 result = {}; + if (!DN_Str8_HasData(hex)) + return result; + + if (!DN_Check(DN_Str8_IsAll(hex, DN_Str8IsAll_Hex))) + return result; + + result = DN_CVT_HexToBytesUnchecked(arena, hex); + return result; +} diff --git a/Base/dn_base_convert.h b/Base/dn_base_convert.h new file mode 100644 index 0000000..d9d7897 --- /dev/null +++ b/Base/dn_base_convert.h @@ -0,0 +1,77 @@ +#if !defined(DN_BASE_CONVERT_H) +#define DN_BASE_CONVERT_H + +struct DN_CVTU64Str8 +{ + char data[27 + 1]; // NOTE(dn): 27 is the maximum size of DN_U64 including a separator + DN_U8 size; +}; + +enum DN_CVTU64ByteSizeType +{ + DN_CVTU64ByteSizeType_B, + DN_CVTU64ByteSizeType_KiB, + DN_CVTU64ByteSizeType_MiB, + DN_CVTU64ByteSizeType_GiB, + DN_CVTU64ByteSizeType_TiB, + DN_CVTU64ByteSizeType_Count, + DN_CVTU64ByteSizeType_Auto, +}; + +struct DN_CVTU64ByteSize +{ + DN_CVTU64ByteSizeType type; + DN_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c + DN_F64 bytes; +}; + +struct DN_CVTU64HexStr8 +{ + char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; + DN_U8 size; +}; + +typedef DN_U32 DN_CVTHexU64Str8Flags; +enum DN_CVTHexU64Str8Flags_ +{ + DN_CVTHexU64Str8Flags_Nil = 0, + DN_CVTHexU64Str8Flags_0xPrefix = 1 << 0, /// Add the '0x' prefix from the string + DN_CVTHexU64Str8Flags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex +}; + +typedef DN_U32 DN_CVTU64AgeUnit; +enum DN_CVTU64AgeUnit_ +{ + DN_CVTU64AgeUnit_Sec = 1 << 0, + DN_CVTU64AgeUnit_Min = 1 << 1, + DN_CVTU64AgeUnit_Hr = 1 << 2, + DN_CVTU64AgeUnit_Day = 1 << 3, + DN_CVTU64AgeUnit_Week = 1 << 4, + DN_CVTU64AgeUnit_Year = 1 << 5, + DN_CVTU64AgeUnit_HMS = DN_CVTU64AgeUnit_Sec | DN_CVTU64AgeUnit_Min | DN_CVTU64AgeUnit_Hr, + DN_CVTU64AgeUnit_All = DN_CVTU64AgeUnit_HMS | DN_CVTU64AgeUnit_Day | DN_CVTU64AgeUnit_Week | DN_CVTU64AgeUnit_Year, +}; + +DN_API int DN_CVT_FmtBuffer3DotTruncate (char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_CVTU64Str8 DN_CVT_U64ToStr8 (DN_U64 val, char separator); +DN_API DN_CVTU64ByteSize DN_CVT_U64ToByteSize (DN_U64 bytes, DN_CVTU64ByteSizeType type); +DN_API DN_Str8 DN_CVT_U64ToByteSizeStr8 (DN_Arena *arena, DN_U64 bytes, DN_CVTU64ByteSizeType desired_type); +DN_API DN_Str8 DN_CVT_U64ByteSizeTypeString (DN_CVTU64ByteSizeType type); +DN_API DN_Str8 DN_CVT_U64ToAge (DN_Arena *arena, DN_U64 age_s, DN_CVTU64AgeUnit unit); +DN_API DN_Str8 DN_CVT_F64ToAge (DN_Arena *arena, DN_F64 age_s, DN_CVTU64AgeUnit unit); + +DN_API DN_U64 DN_CVT_HexToU64 (DN_Str8 hex); +DN_API DN_Str8 DN_CVT_U64ToHex (DN_Arena *arena, DN_U64 number, DN_CVTHexU64Str8Flags flags); +DN_API DN_CVTU64HexStr8 DN_CVT_U64ToHexStr8 (DN_U64 number, DN_U32 flags); + +DN_API bool DN_CVT_BytesToHexPtr (void const *src, DN_USize src_size, char *dest, DN_USize dest_size); +DN_API DN_Str8 DN_CVT_BytesToHex (DN_Arena *arena, void const *src, DN_USize size); +#define DN_CVT_BytesToHexFromTLS(...) DN_CVT_BytesToHex(DN_OS_TLSTopArena(), __VA_ARGS__) + +DN_API DN_USize DN_CVT_HexToBytesPtrUnchecked (DN_Str8 hex, void *dest, DN_USize dest_size); +DN_API DN_USize DN_CVT_HexToBytesPtr (DN_Str8 hex, void *dest, DN_USize dest_size); +DN_API DN_Str8 DN_CVT_HexToBytesUnchecked (DN_Arena *arena, DN_Str8 hex); +#define DN_CVT_HexToBytesUncheckedFromTLS(...) DN_CVT_HexToBytesUnchecked(DN_OS_TLSTopArena(), __VA_ARGS__) +DN_API DN_Str8 DN_CVT_HexToBytes (DN_Arena *arena, DN_Str8 hex); +#define DN_CVT_HexToBytesFromTLS(...) DN_CVT_HexToBytes(DN_OS_TLSTopArena(), __VA_ARGS__) +#endif // defined(DN_BASE_CONVERT_H) diff --git a/Base/dn_base_log.cpp b/Base/dn_base_log.cpp new file mode 100644 index 0000000..b573ac5 --- /dev/null +++ b/Base/dn_base_log.cpp @@ -0,0 +1,125 @@ +#define DN_BASE_LOG_CPP + +#include "../dn_clangd.h" + +static DN_LOGEmitFromTypeFVFunc *g_dn_base_log_emit_from_type_fv_func_; +static void *g_dn_base_log_emit_from_type_fv_user_context_; + +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType colour, DN_U8 r, DN_U8 g, DN_U8 b) +{ + DN_THREAD_LOCAL char buffer[32]; + buffer[0] = 0; + DN_Str8 result = {}; + result.size = DN_SNPrintF(buffer, + DN_ArrayCountU(buffer), + "\x1b[%d;2;%u;%u;%um", + colour == DN_LOGColourType_Fg ? 38 : 48, + r, + g, + b); + result.data = buffer; + return result; +} + +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromU32(DN_LOGColourType colour, DN_U32 value) +{ + DN_U8 r = DN_CAST(DN_U8)(value >> 24); + DN_U8 g = DN_CAST(DN_U8)(value >> 16); + DN_U8 b = DN_CAST(DN_U8)(value >> 8); + DN_Str8 result = DN_LOG_ColourEscapeCodeStr8FromRGB(colour, r, g, b); + return result; +} + +DN_API DN_LOGPrefixSize DN_LOG_MakePrefix(DN_LOGStyle style, DN_LOGTypeParam type, DN_CallSite call_site, DN_LOGDate date, char *dest, DN_USize dest_size) +{ + DN_Str8 type_str8 = type.str8; + if (type.is_u32_enum) { + switch (type.u32) { + case DN_LOGType_Debug: type_str8 = DN_STR8("DEBUG"); break; + case DN_LOGType_Info: type_str8 = DN_STR8("INFO "); break; + case DN_LOGType_Warning: type_str8 = DN_STR8("WARN"); break; + case DN_LOGType_Error: type_str8 = DN_STR8("ERROR"); break; + case DN_LOGType_Count: type_str8 = DN_STR8("BADXX"); break; + } + } + + static DN_USize max_type_length = 0; + max_type_length = DN_Max(max_type_length, type_str8.size); + int type_padding = DN_CAST(int)(max_type_length - type_str8.size); + + DN_Str8 colour_esc = {}; + DN_Str8 bold_esc = {}; + DN_Str8 reset_esc = {}; + if (style.colour) { + bold_esc = DN_STR8(DN_LOG_BoldEscapeCode); + reset_esc = DN_STR8(DN_LOG_ResetEscapeCode); + colour_esc = DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b); + } + + DN_Str8 file_name = DN_Str8_FileNameFromPath(call_site.file); + DN_GCC_WARNING_PUSH + DN_GCC_WARNING_DISABLE(-Wformat) + DN_GCC_WARNING_DISABLE(-Wformat-extra-args) + int size = DN_SNPrintF(dest, + DN_CAST(int)dest_size, + "%04u-%02u-%02uT%02u:%02u:%02u" // date + "%S" // colour + "%S" // bold + "%S" // type + "%.*s" // type padding + "%S" // reset + " %S" // file name + ":%05I32u " // line number + , + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second, + colour_esc, // colour + bold_esc, // bold + type_str8, // type + DN_CAST(int) type_padding, + "", // type padding + reset_esc, // reset + file_name, // file name + call_site.line); // line number + DN_GCC_WARNING_POP + + static DN_USize max_header_length = 0; + DN_USize size_no_ansi_codes = size - colour_esc.size - reset_esc.size - bold_esc.size; + max_header_length = DN_Max(max_header_length, size_no_ansi_codes); + DN_USize header_padding = max_header_length - size_no_ansi_codes; + + DN_LOGPrefixSize result = {}; + result.size = size; + result.padding = header_padding; + return result; +} + +DN_API void DN_LOG_SetEmitFromTypeFVFunc(DN_LOGEmitFromTypeFVFunc *print_func, void *user_data) +{ + g_dn_base_log_emit_from_type_fv_func_ = print_func; + g_dn_base_log_emit_from_type_fv_user_context_ = user_data; +} + +DN_API void DN_LOG_EmitFromType(DN_LOGTypeParam type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_LOGEmitFromTypeFVFunc *func = g_dn_base_log_emit_from_type_fv_func_; + void *user_context = g_dn_base_log_emit_from_type_fv_user_context_; + if (func) { + va_list args; + va_start(args, fmt); + func(type, user_context, call_site, fmt, args); + va_end(args); + } +} + +DN_API DN_LOGTypeParam DN_LOG_MakeU32LogTypeParam(DN_LOGType type) +{ + DN_LOGTypeParam result = {}; + result.is_u32_enum = true; + result.u32 = type; + return result; +} diff --git a/Base/dn_base_log.h b/Base/dn_base_log.h new file mode 100644 index 0000000..e6e1db6 --- /dev/null +++ b/Base/dn_base_log.h @@ -0,0 +1,72 @@ +#if !defined(DN_BASE_LOG_H) +#define DN_BASE_LOG_H + +enum DN_LOGType +{ + DN_LOGType_Debug, + DN_LOGType_Info, + DN_LOGType_Warning, + DN_LOGType_Error, + DN_LOGType_Count, +}; + +enum DN_LOGBold +{ + DN_LOGBold_No, + DN_LOGBold_Yes, +}; + +struct DN_LOGStyle +{ + DN_LOGBold bold; + bool colour; + DN_U8 r, g, b; +}; + +struct DN_LOGTypeParam +{ + bool is_u32_enum; + DN_U32 u32; + DN_Str8 str8; +}; + +enum DN_LOGColourType +{ + DN_LOGColourType_Fg, + DN_LOGColourType_Bg, +}; + +struct DN_LOGDate +{ + DN_U16 year; + DN_U8 month; + DN_U8 day; + + DN_U8 hour; + DN_U8 minute; + DN_U8 second; +}; + +struct DN_LOGPrefixSize +{ + DN_USize size; + DN_USize padding; +}; + +typedef void DN_LOGEmitFromTypeFVFunc(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); + +#define DN_LOG_ResetEscapeCode "\x1b[0m" +#define DN_LOG_BoldEscapeCode "\x1b[1m" +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType colour, DN_U8 r, DN_U8 g, DN_U8 b); +DN_API DN_Str8 DN_LOG_ColourEscapeCodeStr8FromU32(DN_LOGColourType colour, DN_U32 value); +DN_API DN_LOGPrefixSize DN_LOG_MakePrefix (DN_LOGStyle style, DN_LOGTypeParam type, DN_CallSite call_site, DN_LOGDate date, char *dest, DN_USize dest_size); +DN_API void DN_LOG_SetEmitFromTypeFVFunc (DN_LOGEmitFromTypeFVFunc *print_func, void *user_data); +DN_API void DN_LOG_EmitFromType (DN_LOGTypeParam type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_LOGTypeParam DN_LOG_MakeU32LogTypeParam (DN_LOGType type); +#define DN_LOG_DebugF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Debug), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_InfoF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Info), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_WarningF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Warning), DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_LOG_ErrorF(fmt, ...) DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Error), DN_CALL_SITE, fmt, ##__VA_ARGS__) + + +#endif // !defined(DN_BASE_LOG_H) diff --git a/Base/dn_base_mem.cpp b/Base/dn_base_mem.cpp new file mode 100644 index 0000000..6460508 --- /dev/null +++ b/Base/dn_base_mem.cpp @@ -0,0 +1,553 @@ +#define DN_BASE_MEM_CPP + +static DN_ArenaBlock *DN_Arena_BlockInitFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_ArenaMemFuncs mem_funcs) +{ + DN_ArenaBlock *result = nullptr; + switch (mem_funcs.type) { + case DN_ArenaMemFuncType_Nil: + break; + + case DN_ArenaMemFuncType_Basic: { + DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); + result = DN_CAST(DN_ArenaBlock *) mem_funcs.basic_alloc(reserve); + if (!result) + return result; + + result->used = DN_ARENA_HEADER_SIZE; + result->commit = reserve; + result->reserve = reserve; + } break; + + case DN_ArenaMemFuncType_VMem: { + DN_AssertF(mem_funcs.vmem_page_size, "Page size must be set to a non-zero, power of two value"); + DN_Assert(DN_IsPowerOfTwo(mem_funcs.vmem_page_size)); + + DN_USize const page_size = mem_funcs.vmem_page_size; + DN_U64 real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE; + DN_U64 real_commit = commit ? commit : DN_ARENA_COMMIT_SIZE; + real_reserve = DN_AlignUpPowerOfTwo(real_reserve, page_size); + real_commit = DN_Min(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve); + DN_AssertF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve); + + DN_MemCommit mem_commit = real_reserve == real_commit ? DN_MemCommit_Yes : DN_MemCommit_No; + result = DN_CAST(DN_ArenaBlock *) mem_funcs.vmem_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == DN_MemCommit_No && !mem_funcs.vmem_commit(result, real_commit, DN_MemPage_ReadWrite)) { + mem_funcs.vmem_release(result, real_reserve); + return result; + } + + result->used = DN_ARENA_HEADER_SIZE; + result->commit = real_commit; + result->reserve = real_reserve; + } break; + } + + if (track_alloc && result) + DN_Debug_TrackAlloc(result, result->reserve, alloc_can_leak); + + return result; +} + +static DN_ArenaBlock *DN_Arena_BlockInitFlagsFromMemFuncs_(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs) +{ + bool track_alloc = (flags & DN_ArenaFlags_NoAllocTrack) == 0; + bool alloc_can_leak = flags & DN_ArenaFlags_AllocCanLeak; + DN_ArenaBlock *result = DN_Arena_BlockInitFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); + if (result && ((flags & DN_ArenaFlags_NoPoison) == 0)) + DN_ASAN_PoisonMemoryRegion(DN_CAST(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); + return result; +} + +static void DN_Arena_UpdateStatsOnNewBlock_(DN_Arena *arena, DN_ArenaBlock const *block) +{ + DN_Assert(arena); + if (block) { + arena->stats.info.used += block->used; + arena->stats.info.commit += block->commit; + arena->stats.info.reserve += block->reserve; + arena->stats.info.blocks += 1; + + arena->stats.hwm.used = DN_Max(arena->stats.hwm.used, arena->stats.info.used); + arena->stats.hwm.commit = DN_Max(arena->stats.hwm.commit, arena->stats.info.commit); + arena->stats.hwm.reserve = DN_Max(arena->stats.hwm.reserve, arena->stats.info.reserve); + arena->stats.hwm.blocks = DN_Max(arena->stats.hwm.blocks, arena->stats.info.blocks); + } +} + +DN_API DN_Arena DN_Arena_InitFromBuffer(void *buffer, DN_USize size, DN_ArenaFlags flags) +{ + DN_Assert(buffer); + DN_AssertF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE); + DN_AssertF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size); + + // NOTE: Init block + DN_ArenaBlock *block = DN_CAST(DN_ArenaBlock *) buffer; + block->commit = size; + block->reserve = size; + block->used = DN_ARENA_HEADER_SIZE; + if (block && ((flags & DN_ArenaFlags_NoPoison) == 0)) + DN_ASAN_PoisonMemoryRegion(DN_CAST(char *) block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE); + + DN_Arena result = {}; + result.flags = flags | DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_UserBuffer; + result.curr = block; + DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs) +{ + DN_Arena result = {}; + result.flags = flags; + result.mem_funcs = mem_funcs; + result.flags |= DN_ArenaFlags_MemFuncs; + result.curr = DN_Arena_BlockInitFlagsFromMemFuncs_(reserve, commit, flags, mem_funcs); + DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); + return result; +} + +static void DN_Arena_BlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block) +{ + DN_USize release_size = block->reserve; + if (DN_Bit_IsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack)) + DN_Debug_TrackDealloc(block); + DN_ASAN_UnpoisonMemoryRegion(block, block->commit); + if (arena->flags & DN_ArenaFlags_MemFuncs) { + if (arena->mem_funcs.type == DN_ArenaMemFuncType_Basic) + arena->mem_funcs.basic_dealloc(block); + else + arena->mem_funcs.vmem_release(block, release_size); + } +} + +DN_API void DN_Arena_Deinit(DN_Arena *arena) +{ + for (DN_ArenaBlock *block = arena ? arena->curr : nullptr; block;) { + DN_ArenaBlock *block_to_free = block; + block = block->prev; + DN_Arena_BlockDeinit_(arena, block_to_free); + } + if (arena) + *arena = {}; +} + +DN_API bool DN_Arena_CommitTo(DN_Arena *arena, DN_U64 pos) +{ + if (!arena || !arena->curr) + return false; + + DN_ArenaBlock *curr = arena->curr; + if (pos <= curr->commit) + return true; + + DN_U64 real_pos = pos; + if (!DN_Check(pos <= curr->reserve)) + real_pos = curr->reserve; + + DN_Assert(arena->mem_funcs.vmem_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, arena->mem_funcs.vmem_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_CAST(char *) curr + curr->commit; + if (!arena->mem_funcs.vmem_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return false; + + bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); + if (poison) + DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + + curr->commit = end_commit; + return true; +} + +DN_API bool DN_Arena_Commit(DN_Arena *arena, DN_U64 size) +{ + if (!arena || !arena->curr) + return false; + DN_U64 pos = DN_Min(arena->curr->reserve, arena->curr->commit + size); + bool result = DN_Arena_CommitTo(arena, pos); + return result; +} + +DN_API bool DN_Arena_Grow(DN_Arena *arena, DN_U64 reserve, DN_U64 commit) +{ + if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer)) + return false; + + bool result = false; + DN_ArenaBlock *new_block = DN_Arena_BlockInitFlagsFromMemFuncs_(reserve, commit, arena->flags, arena->mem_funcs); + if (new_block) { + result = true; + new_block->prev = arena->curr; + arena->curr = new_block; + new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; + DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); + } + return result; +} + +DN_API void *DN_Arena_Alloc(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) +{ + if (!arena) + return nullptr; + + if (!arena->curr) { + arena->curr = DN_Arena_BlockInitFlagsFromMemFuncs_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, arena->flags, arena->mem_funcs); + DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); + } + + if (!arena->curr) + return nullptr; + + try_alloc_again: + DN_ArenaBlock *curr = arena->curr; + bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); + uint8_t real_align = poison ? DN_Max(align, DN_ASAN_POISON_ALIGNMENT) : align; + DN_U64 offset_pos = DN_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DN_ASAN_POISON_GUARD_SIZE : 0); + DN_U64 end_pos = offset_pos + size; + DN_U64 alloc_size = end_pos - curr->used; + + if (end_pos > curr->reserve) { + if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer)) + return nullptr; + DN_USize new_reserve = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE); + DN_USize new_commit = DN_Max(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE); + if (!DN_Arena_Grow(arena, new_reserve, new_commit)) + return nullptr; + goto try_alloc_again; + } + + DN_USize prev_arena_commit = curr->commit; + if (end_pos > curr->commit) { + DN_Assert(arena->mem_funcs.vmem_page_size); + DN_Assert(arena->mem_funcs.type == DN_ArenaMemFuncType_VMem); + DN_Assert((arena->flags & DN_ArenaFlags_UserBuffer) == 0); + DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, arena->mem_funcs.vmem_page_size); + DN_USize commit_size = end_commit - curr->commit; + char *commit_ptr = DN_CAST(char *) curr + curr->commit; + if (!arena->mem_funcs.vmem_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + return nullptr; + if (poison) + DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); + curr->commit = end_commit; + arena->stats.info.commit += commit_size; + arena->stats.hwm.commit = DN_Max(arena->stats.hwm.commit, arena->stats.info.commit); + } + + void *result = DN_CAST(char *) curr + offset_pos; + curr->used += alloc_size; + arena->stats.info.used += alloc_size; + arena->stats.hwm.used = DN_Max(arena->stats.hwm.used, arena->stats.info.used); + DN_ASAN_UnpoisonMemoryRegion(result, size); + + if (zero_mem == DN_ZeroMem_Yes) { + DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); + DN_Memset(result, 0, reused_bytes); + } + + DN_Assert(arena->stats.hwm.used >= arena->stats.info.used); + DN_Assert(arena->stats.hwm.commit >= arena->stats.info.commit); + DN_Assert(arena->stats.hwm.reserve >= arena->stats.info.reserve); + DN_Assert(arena->stats.hwm.blocks >= arena->stats.info.blocks); + return result; +} + +DN_API void *DN_Arena_AllocContiguous(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) +{ + DN_ArenaFlags prev_flags = arena->flags; + arena->flags |= (DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoPoison); + void *memory = DN_Arena_Alloc(arena, size, align, zero_mem); + arena->flags = prev_flags; + return memory; +} + +DN_API void *DN_Arena_Copy(DN_Arena *arena, void const *data, DN_U64 size, uint8_t align) +{ + if (!arena || !data || size == 0) + return nullptr; + void *result = DN_Arena_Alloc(arena, size, align, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, data, size); + return result; +} + +DN_API void DN_Arena_PopTo(DN_Arena *arena, DN_U64 init_used) +{ + if (!arena || !arena->curr) + return; + DN_U64 used = DN_Max(DN_ARENA_HEADER_SIZE, init_used); + DN_ArenaBlock *curr = arena->curr; + while (curr->reserve_sum >= used) { + DN_ArenaBlock *block_to_free = curr; + arena->stats.info.used -= block_to_free->used; + arena->stats.info.commit -= block_to_free->commit; + arena->stats.info.reserve -= block_to_free->reserve; + arena->stats.info.blocks -= 1; + if (arena->flags & DN_ArenaFlags_UserBuffer) + break; + curr = curr->prev; + DN_Arena_BlockDeinit_(arena, block_to_free); + } + + arena->stats.info.used -= curr->used; + arena->curr = curr; + curr->used = used - curr->reserve_sum; + char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT); + DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr; + DN_ASAN_PoisonMemoryRegion(poison_ptr, poison_size); + arena->stats.info.used += curr->used; +} + +DN_API void DN_Arena_Pop(DN_Arena *arena, DN_U64 amount) +{ + DN_ArenaBlock *curr = arena->curr; + DN_USize used_sum = curr->reserve_sum + curr->used; + if (!DN_Check(amount <= used_sum)) + amount = used_sum; + DN_USize pop_to = used_sum - amount; + DN_Arena_PopTo(arena, pop_to); +} + +DN_API DN_U64 DN_Arena_Pos(DN_Arena const *arena) +{ + DN_U64 result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0; + return result; +} + +DN_API void DN_Arena_Clear(DN_Arena *arena) +{ + DN_Arena_PopTo(arena, 0); +} + +DN_API bool DN_Arena_OwnsPtr(DN_Arena const *arena, void *ptr) +{ + bool result = false; + uintptr_t uint_ptr = DN_CAST(uintptr_t) ptr; + for (DN_ArenaBlock const *block = arena ? arena->curr : nullptr; !result && block; block = block->prev) { + uintptr_t begin = DN_CAST(uintptr_t) block + DN_ARENA_HEADER_SIZE; + uintptr_t end = begin + block->reserve; + result = uint_ptr >= begin && uint_ptr <= end; + } + return result; +} + +DN_API DN_ArenaStats DN_Arena_SumStatsArray(DN_ArenaStats const *array, DN_USize size) +{ + DN_ArenaStats result = {}; + DN_ForItSize(it, DN_ArenaStats const, array, size) + { + DN_ArenaStats stats = *it.data; + result.info.used += stats.info.used; + result.info.commit += stats.info.commit; + result.info.reserve += stats.info.reserve; + result.info.blocks += stats.info.blocks; + + result.hwm.used = DN_Max(result.hwm.used, result.info.used); + result.hwm.commit = DN_Max(result.hwm.commit, result.info.commit); + result.hwm.reserve = DN_Max(result.hwm.reserve, result.info.reserve); + result.hwm.blocks = DN_Max(result.hwm.blocks, result.info.blocks); + } + return result; +} + +DN_API DN_ArenaStats DN_Arena_SumStats(DN_ArenaStats lhs, DN_ArenaStats rhs) +{ + DN_ArenaStats array[] = {lhs, rhs}; + DN_ArenaStats result = DN_Arena_SumStatsArray(array, DN_ArrayCountU(array)); + return result; +} + +DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats(DN_Arena const *array, DN_USize size) +{ + DN_ArenaStats result = {}; + for (DN_USize index = 0; index < size; index++) { + DN_Arena const *arena = array + index; + result = DN_Arena_SumStats(result, arena->stats); + } + return result; +} + +DN_API DN_ArenaTempMem DN_Arena_TempMemBegin(DN_Arena *arena) +{ + DN_ArenaTempMem result = {}; + if (arena) { + DN_ArenaBlock *curr = arena->curr; + result = {arena, curr ? curr->reserve_sum + curr->used : 0}; + } + return result; +}; + +DN_API void DN_Arena_TempMemEnd(DN_ArenaTempMem mem) +{ + DN_Arena_PopTo(mem.arena, mem.used_sum); +}; + +DN_ArenaTempMemScope::DN_ArenaTempMemScope(DN_Arena *arena) +{ + mem = DN_Arena_TempMemBegin(arena); +} + +DN_ArenaTempMemScope::~DN_ArenaTempMemScope() +{ + DN_Arena_TempMemEnd(mem); +} + +// NOTE: DN_Pool /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Pool DN_Pool_Init(DN_Arena *arena, uint8_t align) +{ + DN_Pool result = {}; + if (arena) { + result.arena = arena; + result.align = align ? align : DN_POOL_DEFAULT_ALIGN; + } + return result; +} + +DN_API bool DN_Pool_IsValid(DN_Pool const *pool) +{ + bool result = pool && pool->arena && pool->align; + return result; +} + +DN_API void *DN_Pool_Alloc(DN_Pool *pool, DN_USize size) +{ + void *result = nullptr; + if (!DN_Pool_IsValid(pool)) + return result; + + DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size; + DN_USize const size_to_slot_offset = 5; // __lzcnt64(32) e.g. DN_PoolSlotSize_32B + DN_USize slot_index = 0; + if (required_size > 32) { + // NOTE: Round up if not PoT as the low bits are set. + DN_USize dist_to_next_msb = DN_CountLeadingZerosU64(required_size) + 1; + dist_to_next_msb -= DN_CAST(DN_USize)(!DN_IsPowerOfTwo(required_size)); + + DN_USize const register_size = sizeof(DN_USize) * 8; + DN_Assert(register_size >= dist_to_next_msb + size_to_slot_offset); + slot_index = register_size - dist_to_next_msb - size_to_slot_offset; + } + + if (!DN_CheckF(slot_index < DN_PoolSlotSize_Count, "Chunk pool does not support the requested allocation size")) + return result; + + DN_USize slot_size_in_bytes = 1ULL << (slot_index + size_to_slot_offset); + DN_Assert(required_size <= (slot_size_in_bytes << 0)); + DN_Assert(required_size >= (slot_size_in_bytes >> 1)); + + DN_PoolSlot *slot = nullptr; + if (pool->slots[slot_index]) { + slot = pool->slots[slot_index]; + pool->slots[slot_index] = slot->next; + DN_Memset(slot->data, 0, size); + DN_Assert(DN_IsPowerOfTwoAligned(slot->data, pool->align)); + } else { + void *bytes = DN_Arena_Alloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZeroMem_Yes); + slot = DN_CAST(DN_PoolSlot *) bytes; + + // NOTE: The raw pointer is round up to the next 'pool->align'-ed + // address ensuring at least 1 byte of padding between the raw pointer + // and the pointer given to the user and that the user pointer is + // aligned to the pool's alignment. + // + // This allows us to smuggle 1 byte behind the user pointer that has + // the offset to the original pointer. + slot->data = DN_CAST(void *) DN_AlignDownPowerOfTwo(DN_CAST(uintptr_t) slot + sizeof(DN_PoolSlot) + pool->align, pool->align); + + uintptr_t offset_to_original_ptr = DN_CAST(uintptr_t) slot->data - DN_CAST(uintptr_t) bytes; + DN_Assert(slot->data > bytes); + DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); + + // NOTE: Store the offset to the original pointer behind the user's + // pointer. + char *offset_to_original_storage = DN_CAST(char *) slot->data - 1; + DN_Memcpy(offset_to_original_storage, &offset_to_original_ptr, 1); + } + + // NOTE: Smuggle the slot type in the next pointer so that we know, when the + // pointer gets returned which free list to return the pointer to. + result = slot->data; + slot->next = DN_CAST(DN_PoolSlot *) slot_index; + return result; +} + +DN_API DN_Str8 DN_Pool_AllocStr8FV(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = {}; + if (!DN_Pool_IsValid(pool)) + return result; + + DN_USize size_required = DN_CStr8_FVSize(fmt, args); + result.data = DN_CAST(char *) DN_Pool_Alloc(pool, size_required + 1); + if (result.data) { + result.size = size_required; + DN_VSNPrintF(result.data, DN_CAST(int)(result.size + 1), fmt, args); + } + return result; +} + +DN_API DN_Str8 DN_Pool_AllocStr8F(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Pool_AllocStr8FV(pool, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Pool_AllocStr8Copy(DN_Pool *pool, DN_Str8 string) +{ + DN_Str8 result = {}; + if (!DN_Pool_IsValid(pool)) + return result; + + if (!DN_Str8_HasData(string)) + return result; + + char *data = DN_CAST(char *) DN_Pool_Alloc(pool, string.size + 1); + if (!data) + return result; + + DN_Memcpy(data, string.data, string.size); + data[string.size] = 0; + result = DN_Str8_Init(data, string.size); + return result; +} + +DN_API void DN_Pool_Dealloc(DN_Pool *pool, void *ptr) +{ + if (!DN_Pool_IsValid(pool) || !ptr) + return; + + DN_Assert(DN_Arena_OwnsPtr(pool->arena, ptr)); + + char const *one_byte_behind_ptr = DN_CAST(char *) ptr - 1; + DN_USize offset_to_original_ptr = 0; + DN_Memcpy(&offset_to_original_ptr, one_byte_behind_ptr, 1); + DN_Assert(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); + + char *original_ptr = DN_CAST(char *) ptr - offset_to_original_ptr; + DN_PoolSlot *slot = DN_CAST(DN_PoolSlot *) original_ptr; + DN_PoolSlotSize slot_index = DN_CAST(DN_PoolSlotSize)(DN_CAST(uintptr_t) slot->next); + DN_Assert(slot_index < DN_PoolSlotSize_Count); + + slot->next = pool->slots[slot_index]; + pool->slots[slot_index] = slot; +} + +DN_API void *DN_Pool_Copy(DN_Pool *pool, void const *data, DN_U64 size, uint8_t align) +{ + if (!pool || !data || size == 0) + return nullptr; + + // TODO: Hmm should align be part of the alloc interface in general? I'm not going to worry + // about this until we crash because of misalignment. + DN_Assert(pool->align >= align); + + void *result = DN_Pool_Alloc(pool, size); + if (result) + DN_Memcpy(result, data, size); + return result; +} diff --git a/Base/dn_base_mem.h b/Base/dn_base_mem.h new file mode 100644 index 0000000..b9104f8 --- /dev/null +++ b/Base/dn_base_mem.h @@ -0,0 +1,253 @@ +#if !defined(DN_BASE_MEM_H) +#define DN_BASE_MEM_H + +#include "../dn_clangd.h" + +enum DN_MemCommit +{ + DN_MemCommit_No, + DN_MemCommit_Yes, +}; + +typedef DN_U32 DN_MemPage; +enum DN_MemPage_ +{ + // Exception on read/write with a page. This flag overrides the read/write + // access. + DN_MemPage_NoAccess = 1 << 0, + + DN_MemPage_Read = 1 << 1, // Only read permitted on the page. + + // Only write permitted on the page. On Windows this is not supported and + // will be promoted to read+write permissions. + DN_MemPage_Write = 1 << 2, + + DN_MemPage_ReadWrite = DN_MemPage_Read | DN_MemPage_Write, + + // Modifier used in conjunction with previous flags. Raises exception on + // first access to the page, then, the underlying protection flags are + // active. This is supported on Windows, on other OS's using this flag will + // set the OS equivalent of DN_MemPage_NoAccess. + // This flag must only be used in DN_Mem_Protect + DN_MemPage_Guard = 1 << 3, + + // If leak tracing is enabled, this flag will allow the allocation recorded + // from the reserve call to be leaked, e.g. not printed when leaks are + // dumped to the console. + DN_MemPage_AllocRecordLeakPermitted = 1 << 4, + + // If leak tracing is enabled this flag will prevent any allocation record + // from being created in the allocation table at all. If this flag is + // enabled, 'OSMemPage_AllocRecordLeakPermitted' has no effect since the + // record will never be created. + DN_MemPage_NoAllocRecordEntry = 1 << 5, + + // [INTERNAL] Do not use. All flags together do not constitute a correct + // configuration of pages. + DN_MemPage_All = DN_MemPage_NoAccess | + DN_MemPage_ReadWrite | + DN_MemPage_Guard | + DN_MemPage_AllocRecordLeakPermitted | + DN_MemPage_NoAllocRecordEntry, +}; + +#if !defined(DN_ARENA_RESERVE_SIZE) + #define DN_ARENA_RESERVE_SIZE DN_Megabytes(64) +#endif + +#if !defined(DN_ARENA_COMMIT_SIZE) + #define DN_ARENA_COMMIT_SIZE DN_Kilobytes(64) +#endif + +struct DN_ArenaBlock +{ + DN_ArenaBlock *prev; + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 reserve_sum; +}; + +typedef uint32_t DN_ArenaFlags; + +enum DN_ArenaFlags_ +{ + DN_ArenaFlags_Nil = 0, + DN_ArenaFlags_NoGrow = 1 << 0, + DN_ArenaFlags_NoPoison = 1 << 1, + DN_ArenaFlags_NoAllocTrack = 1 << 2, + DN_ArenaFlags_AllocCanLeak = 1 << 3, + + // NOTE: Internal flags. Do not use + DN_ArenaFlags_UserBuffer = 1 << 4, + DN_ArenaFlags_MemFuncs = 1 << 5, +}; + +struct DN_ArenaInfo +{ + DN_U64 used; + DN_U64 commit; + DN_U64 reserve; + DN_U64 blocks; +}; + +struct DN_ArenaStats +{ + DN_ArenaInfo info; + DN_ArenaInfo hwm; +}; + +enum DN_ArenaMemFuncType +{ + DN_ArenaMemFuncType_Nil, + DN_ArenaMemFuncType_Basic, + DN_ArenaMemFuncType_VMem, +}; + +typedef void *(DN_ArenaMemBasicAllocFunc)(DN_USize size); +typedef void (DN_ArenaMemBasicDeallocFunc)(void *ptr); +typedef void *(DN_ArenaMemVMemReserveFunc)(DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +typedef bool (DN_ArenaMemVMemCommitFunc)(void *ptr, DN_USize size, DN_U32 page_flags); +typedef void (DN_ArenaMemVMemReleaseFunc)(void *ptr, DN_USize size); +struct DN_ArenaMemFuncs +{ + DN_ArenaMemFuncType type; + DN_ArenaMemBasicAllocFunc *basic_alloc; + DN_ArenaMemBasicDeallocFunc *basic_dealloc; + + DN_U32 vmem_page_size; + DN_ArenaMemVMemReserveFunc *vmem_reserve; + DN_ArenaMemVMemCommitFunc *vmem_commit; + DN_ArenaMemVMemReleaseFunc *vmem_release; +}; + +struct DN_Arena +{ + DN_ArenaMemFuncs mem_funcs; + DN_ArenaBlock *curr; + DN_ArenaStats stats; + DN_ArenaFlags flags; + DN_Str8 label; + DN_Arena *prev, *next; +}; + +struct DN_ArenaTempMem +{ + DN_Arena *arena; + DN_U64 used_sum; +}; + +struct DN_ArenaTempMemScope +{ + DN_ArenaTempMemScope(DN_Arena *arena); + ~DN_ArenaTempMemScope(); + DN_ArenaTempMem mem; +}; + +DN_USize const DN_ARENA_HEADER_SIZE = DN_AlignUpPowerOfTwo(sizeof(DN_Arena), 64); + +// NOTE: DN_Arena ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Arena DN_Arena_InitFromBuffer (void *buffer, DN_USize size, DN_ArenaFlags flags); +DN_API DN_Arena DN_Arena_InitFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags, DN_ArenaMemFuncs mem_funcs); +DN_API void DN_Arena_Deinit (DN_Arena *arena); +DN_API bool DN_Arena_Commit (DN_Arena *arena, DN_U64 size); +DN_API bool DN_Arena_CommitTo (DN_Arena *arena, DN_U64 pos); +DN_API bool DN_Arena_Grow (DN_Arena *arena, DN_U64 reserve, DN_U64 commit); +DN_API void * DN_Arena_Alloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); +DN_API void * DN_Arena_AllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); +DN_API void * DN_Arena_Copy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); +DN_API void DN_Arena_PopTo (DN_Arena *arena, DN_U64 init_used); +DN_API void DN_Arena_Pop (DN_Arena *arena, DN_U64 amount); +DN_API DN_U64 DN_Arena_Pos (DN_Arena const *arena); +DN_API void DN_Arena_Clear (DN_Arena *arena); +DN_API bool DN_Arena_OwnsPtr (DN_Arena const *arena, void *ptr); +DN_API DN_ArenaStats DN_Arena_SumStatsArray (DN_ArenaStats const *array, DN_USize size); +DN_API DN_ArenaStats DN_Arena_SumStats (DN_ArenaStats lhs, DN_ArenaStats rhs); +DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats (DN_Arena const *array, DN_USize size); +DN_API DN_ArenaTempMem DN_Arena_TempMemBegin (DN_Arena *arena); +DN_API void DN_Arena_TempMemEnd (DN_ArenaTempMem mem); +#define DN_Arena_New_Frame(T, zero_mem) (T *)DN_Arena_Alloc(DN_OS_TLSGet()->frame_arena, sizeof(T), alignof(T), zero_mem) +#define DN_Arena_New(arena, T, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T), alignof(T), zero_mem) +#define DN_Arena_NewArray(arena, T, count, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T) * (count), alignof(T), zero_mem) +#define DN_Arena_NewCopy(arena, T, src) (T *)DN_Arena_Copy (arena, (src), sizeof(T), alignof(T)) +#define DN_Arena_NewArrayCopy(arena, T, src, count) (T *)DN_Arena_Copy (arena, (src), sizeof(T) * (count), alignof(T)) + +#if !defined(DN_POOL_DEFAULT_ALIGN) + #define DN_POOL_DEFAULT_ALIGN 16 +#endif + +struct DN_PoolSlot +{ + void *data; + DN_PoolSlot *next; +}; + +enum DN_PoolSlotSize +{ + DN_PoolSlotSize_32B, + DN_PoolSlotSize_64B, + DN_PoolSlotSize_128B, + DN_PoolSlotSize_256B, + DN_PoolSlotSize_512B, + DN_PoolSlotSize_1KiB, + DN_PoolSlotSize_2KiB, + DN_PoolSlotSize_4KiB, + DN_PoolSlotSize_8KiB, + DN_PoolSlotSize_16KiB, + DN_PoolSlotSize_32KiB, + DN_PoolSlotSize_64KiB, + DN_PoolSlotSize_128KiB, + DN_PoolSlotSize_256KiB, + DN_PoolSlotSize_512KiB, + DN_PoolSlotSize_1MiB, + DN_PoolSlotSize_2MiB, + DN_PoolSlotSize_4MiB, + DN_PoolSlotSize_8MiB, + DN_PoolSlotSize_16MiB, + DN_PoolSlotSize_32MiB, + DN_PoolSlotSize_64MiB, + DN_PoolSlotSize_128MiB, + DN_PoolSlotSize_256MiB, + DN_PoolSlotSize_512MiB, + DN_PoolSlotSize_1GiB, + DN_PoolSlotSize_2GiB, + DN_PoolSlotSize_4GiB, + DN_PoolSlotSize_8GiB, + DN_PoolSlotSize_16GiB, + DN_PoolSlotSize_32GiB, + DN_PoolSlotSize_Count, +}; + +struct DN_Pool +{ + DN_Arena *arena; + DN_PoolSlot *slots[DN_PoolSlotSize_Count]; + uint8_t align; +}; + +// NOTE: DN_Pool /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Pool DN_Pool_Init (DN_Arena *arena, uint8_t align); +DN_API bool DN_Pool_IsValid (DN_Pool const *pool); +DN_API void * DN_Pool_Alloc (DN_Pool *pool, DN_USize size); +DN_API DN_Str8 DN_Pool_AllocStr8FV (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Pool_AllocStr8F (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Pool_AllocStr8Copy (DN_Pool *pool, DN_Str8 string); +DN_API void DN_Pool_Dealloc (DN_Pool *pool, void *ptr); +DN_API void * DN_Pool_Copy (DN_Pool *pool, void const *data, DN_U64 size, uint8_t align); + +#define DN_Pool_New(pool, T) (T *)DN_Pool_Alloc(pool, sizeof(T)) +#define DN_Pool_NewArray(pool, T, count) (T *)DN_Pool_Alloc(pool, count * sizeof(T)) +#define DN_Pool_NewCopy(arena, T, src) (T *)DN_Pool_Copy (arena, (src), sizeof(T), alignof(T)) +#define DN_Pool_NewArrayCopy(arena, T, src, count) (T *)DN_Pool_Copy (arena, (src), sizeof(T) * (count), alignof(T)) + +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +#if defined(DN_LEAK_TRACKING) && !defined(DN_FREESTANDING) +DN_API void DN_Debug_TrackAlloc (void *ptr, DN_USize size, bool alloc_can_leak); +DN_API void DN_Debug_TrackDealloc(void *ptr); +DN_API void DN_Debug_DumpLeaks (); +#else +#define DN_Debug_TrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define DN_Debug_TrackDealloc(ptr) do { (void)ptr; } while (0) +#define DN_Debug_DumpLeaks() do { } while (0) +#endif +#endif // !defined(DN_BASE_MEM_H) diff --git a/Base/dn_base_os.h b/Base/dn_base_os.h new file mode 100644 index 0000000..7a19adf --- /dev/null +++ b/Base/dn_base_os.h @@ -0,0 +1,46 @@ +#if !defined(DN_BASE_OS_H) +#define DN_BASE_OS_H + +// NOTE: OS primitives that the OS layer can provide for the base layer but is optional. + +struct DN_StackTraceFrame +{ + DN_U64 address; + DN_U64 line_number; + DN_Str8 file_name; + DN_Str8 function_name; +}; + +struct DN_StackTraceRawFrame +{ + void *process; + DN_U64 base_addr; +}; + +struct DN_StackTraceWalkResult +{ + void *process; // [Internal] Windows handle to the process + DN_U64 *base_addr; // The addresses of the functions in the stack trace + DN_U16 size; // The number of `base_addr`'s stored from the walk +}; + +struct DN_StackTraceWalkResultIterator +{ + DN_StackTraceRawFrame raw_frame; + DN_U16 index; +}; + +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_StackTraceWalkResult DN_StackTrace_Walk (struct DN_Arena *arena, DN_U16 limit); +DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); +DN_API DN_Str8 DN_StackTrace_WalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); +DN_API DN_Str8 DN_StackTrace_WalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_Slice DN_StackTrace_GetFrames (struct DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); +DN_API void DN_StackTrace_Print (DN_U16 limit); +DN_API void DN_StackTrace_ReloadSymbols (); + + +#endif + diff --git a/Base/dn_base_string.cpp b/Base/dn_base_string.cpp new file mode 100644 index 0000000..6c3609b --- /dev/null +++ b/Base/dn_base_string.cpp @@ -0,0 +1,1183 @@ +#define DN_STRING_CPP + +// NOTE: DN_CStr8 ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_USize DN_CStr8_FSize(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args); + va_end(args); + return result; +} + +DN_API DN_USize DN_CStr8_FVSize(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + DN_USize result = DN_VSNPrintF(nullptr, 0, fmt, args_copy); + va_end(args_copy); + return result; +} + +DN_API DN_USize DN_CStr8_Size(char const *src) +{ + DN_USize result = 0; + while (src && src[0] != 0) { + src++; + result++; + } + return result; +} + +DN_API DN_USize DN_CStr16_Size(wchar_t const *src) +{ + DN_USize result = 0; + while (src && src[0] != 0) { + src++; + result++; + } + + return result; +} + +// NOTE: DN_Str16 ////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator==(DN_Str16 const &lhs, DN_Str16 const &rhs) +{ + bool result = false; + if (lhs.size == rhs.size) + result = DN_Memcmp(lhs.data, rhs.data, lhs.size * sizeof(*lhs.data)) == 0; + return result; +} + +DN_API bool operator!=(DN_Str16 const &lhs, DN_Str16 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_Str8_InitCStr8(char const *src) +{ + DN_USize size = DN_CStr8_Size(src); + DN_Str8 result = DN_Str8_Init(src, size); + return result; +} + +DN_API bool DN_Str8_IsAll(DN_Str8 string, DN_Str8IsAll is_all) +{ + bool result = DN_Str8_HasData(string); + if (!result) + return result; + + switch (is_all) { + case DN_Str8IsAll_Digits: { + for (DN_USize index = 0; result && index < string.size; index++) + result = string.data[index] >= '0' && string.data[index] <= '9'; + } break; + + case DN_Str8IsAll_Hex: { + DN_Str8 trimmed = DN_Str8_TrimPrefix(string, DN_STR8("0x"), DN_Str8EqCase_Insensitive); + for (DN_USize index = 0; result && index < trimmed.size; index++) { + char ch = trimmed.data[index]; + result = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + } break; + } + + return result; +} + +DN_API char *DN_Str8_End(DN_Str8 string) +{ + char *result = string.data + string.size; + return result; +} + +DN_API DN_Str8 DN_Str8_Slice(DN_Str8 string, DN_USize offset, DN_USize size) +{ + DN_Str8 result = DN_Str8_Init(string.data, 0); + if (!DN_Str8_HasData(string)) + return result; + + DN_USize capped_offset = DN_Min(offset, string.size); + DN_USize max_size = string.size - capped_offset; + DN_USize capped_size = DN_Min(size, max_size); + result = DN_Str8_Init(string.data + capped_offset, capped_size); + return result; +} + +DN_API DN_Str8 DN_Str8_Advance(DN_Str8 string, DN_USize amount) +{ + DN_Str8 result = DN_Str8_Slice(string, amount, DN_USIZE_MAX); + return result; +} + +DN_API DN_Str8 DN_Str8_NextLine(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_BinarySplit(string, DN_STR8("\n")).rhs; + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BinarySplitResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return result; + + result.lhs = string; + for (size_t index = 0; !result.rhs.data && index < string.size; index++) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(string_slice, find_item)) { + result.lhs.size = index; + result.rhs.data = string_slice.data + find_item.size; + result.rhs.size = string.size - (index + find_item.size); + break; + } + } + } + + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplit(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = DN_Str8_BinarySplitArray(string, &find, 1); + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) +{ + DN_Str8BinarySplitResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return result; + + result.lhs = string; + for (size_t index = string.size - 1; !result.rhs.data && index < string.size; index--) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(string_slice, find_item)) { + result.lhs.size = index; + result.rhs.data = string_slice.data + find_item.size; + result.rhs.size = string.size - (index + find_item.size); + break; + } + } + } + + return result; +} + +DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLast(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = DN_Str8_BinarySplitLastArray(string, &find, 1); + return result; +} + +DN_API DN_USize DN_Str8_Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_USize result = 0; // The number of splits in the actual string. + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(delimiter) || delimiter.size <= 0) + return result; + + DN_Str8BinarySplitResult split = {}; + DN_Str8 first = string; + do { + split = DN_Str8_BinarySplit(first, delimiter); + if (split.lhs.size || mode == DN_Str8SplitIncludeEmptyStrings_Yes) { + if (splits && result < splits_count) + splits[result] = split.lhs; + result++; + } + first = split.rhs; + } while (first.size); + + return result; +} + +DN_API DN_Slice DN_Str8_SplitAlloc(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = {}; + DN_USize splits_required = DN_Str8_Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); + result.data = DN_Arena_NewArray(arena, DN_Str8, splits_required, DN_ZeroMem_No); + if (result.data) { + result.size = DN_Str8_Split(string, delimiter, result.data, splits_required, mode); + DN_Assert(splits_required == result.size); + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8_FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = {}; + if (!DN_Str8_HasData(string) || !find || find_size == 0) + return result; + + for (DN_USize index = 0; !result.found && index < string.size; index++) { + for (DN_USize find_index = 0; find_index < find_size; find_index++) { + DN_Str8 find_item = find[find_index]; + DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); + if (DN_Str8_Eq(string_slice, find_item, eq_case)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8_Init(string.data, index); + result.match = DN_Str8_Init(string.data + index, find_item.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find_item.size); + break; + } + } + } + return result; +} + +DN_API DN_Str8FindResult DN_Str8_FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case) +{ + DN_Str8FindResult result = DN_Str8_FindStr8Array(string, &find, 1, eq_case); + return result; +} + +DN_API DN_Str8FindResult DN_Str8_Find(DN_Str8 string, uint32_t flags) +{ + DN_Str8FindResult result = {}; + for (size_t index = 0; !result.found && index < string.size; index++) { + result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_Char_IsDigit(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_Char_IsAlphabet(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_Char_IsWhitespace(string.data[index])); + result.found |= ((flags & DN_Str8FindFlag_Plus) && string.data[index] == '+'); + result.found |= ((flags & DN_Str8FindFlag_Minus) && string.data[index] == '-'); + if (result.found) { + result.index = index; + result.match = DN_Str8_Init(string.data + index, 1); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, 1); + } + } + return result; +} + +DN_API DN_Str8 DN_Str8_Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || !DN_Str8_HasData(src)) { + DN_Str8 result = DN_Str8_Copy(arena, src); + return result; + } + + DN_USize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + DN_USize segment_counter = 0; + DN_Str8 result = DN_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); + DN_USize write_index = 0; + DN_ForIndexU(src_index, src.size) { + result.data[write_index++] = src.data[src_index]; + if ((src_index + 1) % segment_size == 0 && segment_counter < segments) { + result.data[write_index++] = segment_char; + segment_counter++; + } + DN_AssertF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index); + } + + DN_AssertF(write_index == result.size, "result.size=%zu, write_index=%zu", result.size, write_index); + return result; +} + +DN_API DN_Str8 DN_Str8_ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) +{ + if (!segment_size || !DN_Str8_HasData(src)) { + DN_Str8 result = DN_Str8_Copy(arena, src); + return result; + } + + DN_USize segments = src.size / segment_size; + if (src.size % segment_size == 0) + segments--; + + DN_USize write_counter = 0; + DN_USize segment_counter = 0; + DN_Str8 result = DN_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); + DN_USize write_index = result.size - 1; + + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (size_t src_index = src.size - 1; src_index < src.size; src_index--) { + DN_MSVC_WARNING_POP + result.data[write_index--] = src.data[src_index]; + if (++write_counter % segment_size == 0 && segment_counter < segments) { + result.data[write_index--] = segment_char; + segment_counter++; + } + } + + DN_Assert(write_index == SIZE_MAX); + return result; +} + +DN_API bool DN_Str8_Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +{ + if (lhs.size != rhs.size) + return false; + + if (lhs.size == 0) + return true; + + if (!lhs.data || !rhs.data) + return false; + + bool result = true; + switch (eq_case) { + case DN_Str8EqCase_Sensitive: { + result = (DN_Memcmp(lhs.data, rhs.data, lhs.size) == 0); + } break; + + case DN_Str8EqCase_Insensitive: { + for (DN_USize index = 0; index < lhs.size && result; index++) + result = (DN_Char_ToLower(lhs.data[index]) == DN_Char_ToLower(rhs.data[index])); + } break; + } + return result; +} + +DN_API bool DN_Str8_EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) +{ + bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 substring = {string.data, DN_Min(prefix.size, string.size)}; + bool result = DN_Str8_Eq(substring, prefix, eq_case); + return result; +} + +DN_API bool DN_Str8_StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + bool result = DN_Str8_StartsWith(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) +{ + DN_Str8 substring = {string.data + string.size - suffix.size, DN_Min(string.size, suffix.size)}; + bool result = DN_Str8_Eq(substring, suffix, eq_case); + return result; +} + +DN_API bool DN_Str8_EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) +{ + bool result = DN_Str8_EndsWith(string, suffix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API bool DN_Str8_HasChar(DN_Str8 string, char ch) +{ + bool result = false; + for (DN_USize index = 0; !result && index < string.size; index++) + result = string.data[index] == ch; + return result; +} + +DN_API DN_Str8 DN_Str8_TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8_StartsWith(string, prefix, eq_case)) { + result.data += prefix.size; + result.size -= prefix.size; + } + return result; +} + +DN_API DN_Str8 DN_Str8_TrimHexPrefix(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_TrimPrefix(string, DN_STR8("0x"), DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) +{ + DN_Str8 result = string; + if (DN_Str8_EndsWith(string, suffix, eq_case)) + result.size -= suffix.size; + return result; +} + +DN_API DN_Str8 DN_Str8_TrimAround(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8_TrimPrefix(string, trim_string); + result = DN_Str8_TrimSuffix(result, trim_string); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimHeadWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(string)) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (start < end && DN_Char_IsWhitespace(start[0])) + start++; + + result = DN_Str8_Init(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimTailWhitespace(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(string)) + return result; + + char const *start = string.data; + char const *end = string.data + string.size; + while (end > start && DN_Char_IsWhitespace(end[-1])) + end--; + + result = DN_Str8_Init(start, end - start); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimWhitespaceAround(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_TrimHeadWhitespace(string); + result = DN_Str8_TrimTailWhitespace(result); + return result; +} + +DN_API DN_Str8 DN_Str8_TrimByteOrderMark(DN_Str8 string) +{ + DN_Str8 result = string; + if (!DN_Str8_HasData(result)) + return result; + + // TODO(dn): This is little endian + DN_Str8 UTF8_BOM = DN_STR8("\xEF\xBB\xBF"); + DN_Str8 UTF16_BOM_BE = DN_STR8("\xEF\xFF"); + DN_Str8 UTF16_BOM_LE = DN_STR8("\xFF\xEF"); + DN_Str8 UTF32_BOM_BE = DN_STR8("\x00\x00\xFE\xFF"); + DN_Str8 UTF32_BOM_LE = DN_STR8("\xFF\xFE\x00\x00"); + + result = DN_Str8_TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8_FileNameFromPath(DN_Str8 path) +{ + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(path, separators, DN_ArrayCountU(separators)); + DN_Str8 result = DN_Str8_HasData(split.rhs) ? split.rhs : split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8_FileNameNoExtension(DN_Str8 path) +{ + DN_Str8 file_name = DN_Str8_FileNameFromPath(path); + DN_Str8 result = DN_Str8_FilePathNoExtension(file_name); + return result; +} + +DN_API DN_Str8 DN_Str8_FilePathNoExtension(DN_Str8 path) +{ + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); + DN_Str8 result = split.lhs; + return result; +} + +DN_API DN_Str8 DN_Str8_FileExtension(DN_Str8 path) +{ + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); + DN_Str8 result = split.rhs; + return result; +} + +DN_API DN_Str8ToU64Result DN_Str8_ToU64(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_Str8ToU64Result result = {}; + if (!DN_Str8_HasData(string)) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + // NOTE: Handle prefix '+' + DN_USize start_index = 0; + if (!DN_Char_IsDigit(trim_string.data[0])) { + if (trim_string.data[0] != '+') + return result; + start_index++; + } + + // NOTE: Convert the string number to the binary number + for (DN_USize index = start_index; index < trim_string.size; index++) { + char ch = trim_string.data[index]; + if (index) { + if (separator != 0 && ch == separator) + continue; + } + + if (!DN_Char_IsDigit(ch)) + return result; + + result.value = DN_Safe_MulU64(result.value, 10); + uint64_t digit = ch - '0'; + result.value = DN_Safe_AddU64(result.value, digit); + } + + result.success = true; + return result; +} + +DN_API DN_Str8ToI64Result DN_Str8_ToI64(DN_Str8 string, char separator) +{ + // NOTE: Argument check + DN_Str8ToI64Result result = {}; + if (!DN_Str8_HasData(string)) { + result.success = true; + return result; + } + + // NOTE: Sanitize input/output + DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); + if (trim_string.size == 0) { + result.success = true; + return result; + } + + bool negative = false; + DN_USize start_index = 0; + if (!DN_Char_IsDigit(trim_string.data[0])) { + negative = (trim_string.data[start_index] == '-'); + if (!negative && trim_string.data[0] != '+') + return result; + start_index++; + } + + // NOTE: Convert the string number to the binary number + for (DN_USize index = start_index; index < trim_string.size; index++) { + char ch = trim_string.data[index]; + if (index) { + if (separator != 0 && ch == separator) + continue; + } + + if (!DN_Char_IsDigit(ch)) + return result; + + result.value = DN_Safe_MulU64(result.value, 10); + uint64_t digit = ch - '0'; + result.value = DN_Safe_AddU64(result.value, digit); + } + + if (negative) + result.value *= -1; + + result.success = true; + return result; +} + +DN_API DN_Str8 DN_Str8_AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(arena, string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFV(DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args) +{ + // TODO: Calculate size and write into one buffer instead of 2 appends + DN_Str8 append = DN_Str8_InitFV(arena, fmt, args); + DN_Str8 result = DN_Str8_Alloc(arena, string.size + append.size, DN_ZeroMem_No); + DN_Memcpy(result.data, string.data, string.size); + DN_Memcpy(result.data + string.size, append.data, append.size); + return result; +} + +DN_API DN_Str8 DN_Str8_FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(arena, count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFV(DN_Arena *arena, DN_USize count, char const *fmt, va_list args) +{ + DN_Str8 fill = DN_Str8_InitFV(arena, fmt, args); + DN_Str8 result = DN_Str8_Alloc(arena, count * fill.size, DN_ZeroMem_No); + for (DN_USize index = 0; index < count; index++) { + void *dest = result.data + (index * fill.size); + DN_Memcpy(dest, fill.data, fill.size); + } + return result; +} + +DN_API void DN_Str8_Remove(DN_Str8 *string, DN_USize offset, DN_USize size) +{ + if (!string || !DN_Str8_HasData(*string)) + return; + + char *end = string->data + string->size; + char *dest = DN_Min(string->data + offset, end); + char *src = DN_Min(string->data + offset + size, end); + DN_USize bytes_to_move = end - src; + DN_Memmove(dest, src, bytes_to_move); + string->size -= bytes_to_move; +} + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddle(DN_Arena *arena, DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = {}; + if (str8.size <= (side_size * 2)) { + result.str8 = DN_Str8_Copy(arena, str8); + return result; + } + + DN_Str8 head = DN_Str8_Slice(str8, 0, side_size); + DN_Str8 tail = DN_Str8_Slice(str8, str8.size - side_size, side_size); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8_InitF' Actual type: 'struct DN_Str8' + result.str8 = DN_Str8_InitF(arena, "%S%S%S", head, truncator, tail); + DN_MSVC_WARNING_POP + result.truncated = true; + return result; +} + +DN_API DN_Str8 DN_Str8_Lower(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(arena, string); + DN_ForIndexU(index, result.size) + result.data[index] = DN_Char_ToLower(result.data[index]); + return result; +} + +DN_API DN_Str8 DN_Str8_Upper(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(arena, string); + DN_ForIndexU(index, result.size) + result.data[index] = DN_Char_ToUpper(result.data[index]); + return result; +} + +#if defined(__cplusplus) +DN_API bool operator==(DN_Str8 const &lhs, DN_Str8 const &rhs) +{ + bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API bool operator!=(DN_Str8 const &lhs, DN_Str8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} +#endif + +DN_API DN_Str8 DN_Str8_InitF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list va; + va_start(va, fmt); + DN_Str8 result = DN_Str8_InitFV(arena, fmt, va); + va_end(va); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFV(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = {}; + if (!fmt) + return result; + + DN_USize size = DN_CStr8_FVSize(fmt, args); + if (size) { + result = DN_Str8_Alloc(arena, size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) + DN_VSNPrintF(result.data, DN_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); + } + return result; +} + +DN_API DN_Str8 DN_Str8_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = {}; + result.data = DN_Arena_NewArray(arena, char, size + 1, zero_mem); + if (result.data) + result.size = size; + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_Str8_Copy(DN_Arena *arena, DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Alloc(arena, string.size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) { + DN_Memcpy(result.data, string.data, string.size); + result.data[string.size] = 0; + } + return result; +} + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8Builder DN_Str8Builder_Init(DN_Arena *arena) +{ + DN_Str8Builder result = {}; + result.arena = arena; + return result; +} + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef(DN_Arena *arena, + DN_Str8 const *strings, + DN_USize size) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendArrayRef(&result, strings, size); + return result; +} + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy(DN_Arena *arena, + DN_Str8 const *strings, + DN_USize size) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendArrayCopy(&result, strings, size); + return result; +} + +DN_API bool DN_Str8Builder_AddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + DN_Str8Link *links = DN_Arena_NewArray(builder->arena, DN_Str8Link, size, DN_ZeroMem_No); + if (!links) + return false; + + if (add == DN_Str8BuilderAdd_Append) { + DN_ForIndexU(index, size) { + DN_Str8 string = strings[index]; + DN_Str8Link *link = links + index; + + link->string = string; + link->next = NULL; + + if (builder->head) + builder->tail->next = link; + else + builder->head = link; + + builder->tail = link; + builder->count++; + builder->string_size += string.size; + } + } else { + DN_Assert(add == DN_Str8BuilderAdd_Prepend); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop + for (DN_USize index = size - 1; index < size; index--) { + DN_MSVC_WARNING_POP + DN_Str8 string = strings[index]; + DN_Str8Link *link = links + index; + link->string = string; + link->next = builder->head; + builder->head = link; + if (!builder->tail) + builder->tail = link; + builder->count++; + builder->string_size += string.size; + } + } + return true; +} + +DN_API bool DN_Str8Builder_AddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) +{ + if (!builder) + return false; + + if (!strings || size <= 0) + return true; + + DN_ArenaTempMem tmp_mem = DN_Arena_TempMemBegin(builder->arena); + bool result = true; + DN_Str8 *strings_copy = DN_Arena_NewArray(builder->arena, DN_Str8, size, DN_ZeroMem_No); + DN_ForIndexU(index, size) + { + strings_copy[index] = DN_Str8_Copy(builder->arena, strings[index]); + if (strings_copy[index].size != strings[index].size) { + result = false; + break; + } + } + + if (result) + result = DN_Str8Builder_AddArrayRef(builder, strings_copy, size, add); + + if (!result) + DN_Arena_TempMemEnd(tmp_mem); + + return result; +} + +DN_API bool DN_Str8Builder_AddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 string = DN_Str8_InitFV(builder->arena, fmt, args); + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(builder->arena); + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, add); + if (!result) + DN_Arena_TempMemEnd(temp_mem); + return result; +} + +DN_API bool DN_Str8Builder_AppendRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8Builder_AppendCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append); + return result; +} + +DN_API bool DN_Str8Builder_AppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8Builder_AppendFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8Builder_AppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8_Init(ptr, size); + bool result = DN_Str8Builder_AppendRef(builder, input); + return result; +} + +DN_API bool DN_Str8Builder_AppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size) +{ + DN_Str8 input = DN_Str8_Init(ptr, size); + bool result = DN_Str8Builder_AppendCopy(builder, input); + return result; +} + +static bool DN_Str8Builder_AppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy) +{ + if (!dest) + return false; + if (!src) + return true; + + DN_Arena_TempMemBegin(dest->arena); + DN_Str8Link *links = DN_Arena_NewArray(dest->arena, DN_Str8Link, src->count, DN_ZeroMem_No); + if (!links) + return false; + + DN_Str8Link *first = nullptr; + DN_Str8Link *last = nullptr; + DN_USize link_index = 0; + bool result = true; + for (DN_Str8Link const *it = src->head; it; it = it->next) { + DN_Str8Link *link = links + link_index++; + link->next = nullptr; + link->string = it->string; + + if (copy) { + link->string = DN_Str8_Copy(dest->arena, it->string); + if (link->string.size != it->string.size) { + result = false; + break; + } + } + + if (last) + last->next = link; + else + first = link; + last = link; + } + + if (result) { + if (dest->head) + dest->tail->next = first; + else + dest->head = first; + dest->tail = last; + dest->count += src->count; + dest->string_size += src->string_size; + } + return true; +} + +DN_API bool DN_Str8Builder_AppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8Builder_AppendBuilder_(dest, src, false); + return result; +} + +DN_API bool DN_Str8Builder_AppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src) +{ + bool result = DN_Str8Builder_AppendBuilder_(dest, src, true); + return result; +} + +DN_API bool DN_Str8Builder_PrependRef(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8Builder_PrependCopy(DN_Str8Builder *builder, DN_Str8 string) +{ + bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend); + return result; +} + +DN_API bool DN_Str8Builder_PrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_Str8Builder_PrependFV(builder, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_Str8Builder_Erase(DN_Str8Builder *builder, DN_Str8 string) +{ + for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { + if ((*it)->string == string) { + *it = (*it)->next; + builder->string_size -= string.size; + builder->count -= 1; + return true; + } + } + return false; +} + +DN_API DN_Str8Builder DN_Str8Builder_Copy(DN_Arena *arena, DN_Str8Builder const *builder) +{ + DN_Str8Builder result = DN_Str8Builder_Init(arena); + DN_Str8Builder_AppendBuilderCopy(&result, builder); + return result; +} + +DN_API DN_Str8 DN_Str8Builder_Build(DN_Str8Builder const *builder, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Builder_BuildDelimited(builder, DN_STR8(""), arena); + return result; +} + +DN_API DN_Str8 DN_Str8Builder_BuildDelimited(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena) +{ + DN_Str8 result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + DN_USize size_for_delimiter = DN_Str8_HasData(delimiter) ? ((builder->count - 1) * delimiter.size) : 0; + result.data = DN_Arena_NewArray(arena, + char, + builder->string_size + size_for_delimiter + 1 /*null terminator*/, + DN_ZeroMem_No); + if (!result.data) + return result; + + for (DN_Str8Link *link = builder->head; link; link = link->next) { + DN_Memcpy(result.data + result.size, link->string.data, link->string.size); + result.size += link->string.size; + if (link->next && DN_Str8_HasData(delimiter)) { + DN_Memcpy(result.data + result.size, delimiter.data, delimiter.size); + result.size += delimiter.size; + } + } + + result.data[result.size] = 0; + DN_Assert(result.size == builder->string_size + size_for_delimiter); + return result; +} + +DN_API DN_Slice DN_Str8Builder_BuildSlice(DN_Str8Builder const *builder, DN_Arena *arena) +{ + DN_Slice result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result = DN_Slice_Alloc(arena, builder->count, DN_ZeroMem_No); + if (!result.data) + return result; + + DN_USize slice_index = 0; + for (DN_Str8Link *link = builder->head; link; link = link->next) + result.data[slice_index++] = DN_Str8_Copy(arena, link->string); + + DN_Assert(slice_index == builder->count); + return result; +} + +// NOTE: DN_Char /////////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_Char_IsAlphabet(char ch) +{ + bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); + return result; +} + +DN_API bool DN_Char_IsDigit(char ch) +{ + bool result = (ch >= '0' && ch <= '9'); + return result; +} + +DN_API bool DN_Char_IsAlphaNum(char ch) +{ + bool result = DN_Char_IsAlphabet(ch) || DN_Char_IsDigit(ch); + return result; +} + +DN_API bool DN_Char_IsWhitespace(char ch) +{ + bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); + return result; +} + +DN_API bool DN_Char_IsHex(char ch) +{ + bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9')); + return result; +} + +DN_API DN_CharHexToU8 DN_Char_HexToU8(char ch) +{ + DN_CharHexToU8 result = {}; + result.success = true; + if (ch >= 'a' && ch <= 'f') + result.value = ch - 'a' + 10; + else if (ch >= 'A' && ch <= 'F') + result.value = ch - 'A' + 10; + else if (ch >= '0' && ch <= '9') + result.value = ch - '0'; + else + result.success = false; + return result; +} + +static char constexpr DN_HEX_LUT[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +DN_API char DN_Char_ToHex(char ch) +{ + char result = DN_CAST(char) - 1; + if (ch < 16) + result = DN_HEX_LUT[DN_CAST(uint8_t) ch]; + return result; +} + +DN_API char DN_Char_ToHexUnchecked(char ch) +{ + char result = DN_HEX_LUT[DN_CAST(uint8_t) ch]; + return result; +} + +DN_API char DN_Char_ToLower(char ch) +{ + char result = ch; + if (result >= 'A' && result <= 'Z') + result += 'a' - 'A'; + return result; +} + +DN_API char DN_Char_ToUpper(char ch) +{ + char result = ch; + if (result >= 'a' && result <= 'z') + result -= 'a' - 'A'; + return result; +} + +// NOTE: DN_UTF //////////////////////////////////////////////////////////////////////////////////// +DN_API int DN_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+----------------------------+--------------------+ + // UTF-8 (binary) | Code point (binary) | Range | + // ----------------------------------------+----------------------------+--------------------+ + // 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F | + // 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF | + // 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF | + // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | + // ----------------------------------------+----------------------------+--------------------+ + + if (codepoint <= 0b0111'1111) { + utf8[0] = DN_CAST(uint8_t) codepoint; + return 1; + } + + if (codepoint <= 0b0111'1111'1111) { + utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y + return 2; + } + + if (codepoint <= 0b1111'1111'1111'1111) { + utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z + return 3; + } + + if (codepoint <= 0b1'1111'1111'1111'1111'1111) { + utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z + utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w + return 4; + } + + return 0; +} + +DN_API int DN_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+------------------------------------+------------------+ + // UTF-16 (binary) | Code point (binary) | Range | + // ----------------------------------------+------------------------------------+------------------+ + // xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000???U+FFFF | + // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000???U+10FFFF | + // ----------------------------------------+------------------------------------+------------------+ + + if (codepoint <= 0b1111'1111'1111'1111) { + utf16[0] = DN_CAST(uint16_t) codepoint; + return 1; + } + + if (codepoint <= 0b1111'1111'1111'1111'1111) { + uint32_t surrogate_codepoint = codepoint + 0x10000; + utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x + utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y + return 2; + } + + return 0; +} diff --git a/dqn_string.h b/Base/dn_base_string.h similarity index 54% rename from dqn_string.h rename to Base/dn_base_string.h index 2307e3c..a251545 100644 --- a/dqn_string.h +++ b/Base/dn_base_string.h @@ -1,5 +1,30 @@ -#pragma once -#include "dqn.h" +#if !defined(DN_BASE_STRING_H) +#define DN_BASE_STRING_H + +#if defined(DN_USE_STD_PRINTF) + #include + #define DN_SPrintF(...) sprintf(__VA_ARGS__) + #define DN_SNPrintF(...) snprintf(__VA_ARGS__) + #define DN_VSPrintF(...) vsprintf(__VA_ARGS__) + #define DN_VSNPrintF(...) vsnprintf(__VA_ARGS__) +#else + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) + #define STB_SPRINTF_IMPLEMENTATION + #define STB_SPRINTF_STATIC + #endif + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4505) // Unused function warning + DN_GCC_WARNING_PUSH + DN_GCC_WARNING_DISABLE(-Wunused-function) + #include "../External/stb_sprintf.h" + DN_GCC_WARNING_POP + DN_MSVC_WARNING_POP + + #define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) + #define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) + #define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) + #define DN_VSNPrintF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) +#endif /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,133 +38,112 @@ // \$$$$$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |\$$$$$$ | // \______/ \__| \__| \__|\______|\__| \__| \______/ // -// dqn_string.h -- UTF8/16 string manipulation -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$CSTR] DN_CStr8 -- C-string helpers -// [$STR8] DN_Str8 -- Pointer and length strings -// [$STRB] DN_Str8Builder -- Construct strings dynamically -// [$FSTR] DN_FStr8 -- Fixed-size strings -// [$CHAR] DN_Char -- Character ascii/digit.. helpers -// [$UTFX] DN_UTF -- Unicode helpers +// dn_base_string.h // //////////////////////////////////////////////////////////////////////////////////////////////////// */ -// NOTE: [$STR8] DN_Str8 ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Str8 ////////////////////////////////////////////////////////////////////////////////// struct DN_Str8Link { - DN_Str8 string; // The string - DN_Str8Link *next; // The next string in the linked list - DN_Str8Link *prev; // The prev string in the linked list -}; - -struct DN_Str16 // A pointer and length style string that holds slices to UTF16 bytes. -{ - wchar_t *data; // The UTF16 bytes of the string - DN_USize size; // The number of characters in the string - - #if defined(__cplusplus) - wchar_t const *begin() const { return data; } // Const begin iterator for range-for loops - wchar_t const *end () const { return data + size; } // Const end iterator for range-for loops - wchar_t *begin() { return data; } // Begin iterator for range-for loops - wchar_t *end () { return data + size; } // End iterator for range-for loops - #endif + DN_Str8 string; // The string + DN_Str8Link *next; // The next string in the linked list + DN_Str8Link *prev; // The prev string in the linked list }; struct DN_Str8BinarySplitResult { - DN_Str8 lhs; - DN_Str8 rhs; + DN_Str8 lhs; + DN_Str8 rhs; }; struct DN_Str8FindResult { - bool found; // True if string was found. If false, the subsequent fields below are not set. - DN_USize index; // Index in the buffer where the found string starts - DN_Str8 match; // Matching string in the buffer that was searched - DN_Str8 match_to_end_of_buffer; // Substring containing the found string to the end of the buffer - DN_Str8 after_match_to_end_of_buffer; // Substring starting after the found string to the end of the buffer - DN_Str8 start_to_before_match; // Substring from the start of the buffer up until the found string, not including it + bool found; // True if string was found. If false, the subsequent fields below are not set. + DN_USize index; // Index in the buffer where the found string starts + DN_Str8 match; // Matching string in the buffer that was searched + DN_Str8 match_to_end_of_buffer; // Substring containing the found string to the end of the buffer + DN_Str8 after_match_to_end_of_buffer; // Substring starting after the found string to the end of the buffer + DN_Str8 start_to_before_match; // Substring from the start of the buffer up until the found string, not including it }; enum DN_Str8IsAll { - DN_Str8IsAll_Digits, - DN_Str8IsAll_Hex, + DN_Str8IsAll_Digits, + DN_Str8IsAll_Hex, }; enum DN_Str8EqCase { - DN_Str8EqCase_Sensitive, - DN_Str8EqCase_Insensitive, + DN_Str8EqCase_Sensitive, + DN_Str8EqCase_Insensitive, }; enum DN_Str8FindFlag { - DN_Str8FindFlag_Digit = 1 << 0, // 0-9 - DN_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' - DN_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z - DN_Str8FindFlag_Plus = 1 << 3, // + - DN_Str8FindFlag_Minus = 1 << 4, // - - DN_Str8FindFlag_AlphaNum = DN_Str8FindFlag_Alphabet | DN_Str8FindFlag_Digit, + DN_Str8FindFlag_Digit = 1 << 0, // 0-9 + DN_Str8FindFlag_Whitespace = 1 << 1, // '\r', '\t', '\n', ' ' + DN_Str8FindFlag_Alphabet = 1 << 2, // A-Z, a-z + DN_Str8FindFlag_Plus = 1 << 3, // + + DN_Str8FindFlag_Minus = 1 << 4, // - + DN_Str8FindFlag_AlphaNum = DN_Str8FindFlag_Alphabet | DN_Str8FindFlag_Digit, }; enum DN_Str8SplitIncludeEmptyStrings { - DN_Str8SplitIncludeEmptyStrings_No, - DN_Str8SplitIncludeEmptyStrings_Yes, + DN_Str8SplitIncludeEmptyStrings_No, + DN_Str8SplitIncludeEmptyStrings_Yes, }; struct DN_Str8ToU64Result { - bool success; - uint64_t value; + bool success; + uint64_t value; }; struct DN_Str8ToI64Result { - bool success; - int64_t value; + bool success; + int64_t value; }; struct DN_Str8DotTruncateResult { - bool truncated; + bool truncated; DN_Str8 str8; }; -// NOTE: [$FSTR] DN_FStr8 ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// #if !defined(DN_NO_FSTR8) -template struct DN_FStr8 +template +struct DN_FStr8 { - char data[N+1]; - DN_USize size; + char data[N + 1]; + DN_USize size; - char *begin() { return data; } - char *end () { return data + size; } - char const *begin() const { return data; } - char const *end () const { return data + size; } + char *begin() { return data; } + char *end() { return data + size; } + char const *begin() const { return data; } + char const *end() const { return data + size; } }; #endif // !defined(DN_NO_FSTR8) struct DN_Str8Builder { - DN_Arena *arena; // Allocator to use to back the string list - DN_Str8Link *head; // First string in the linked list of strings - DN_Str8Link *tail; // Last string in the linked list of strings - DN_USize string_size; // The size in bytes necessary to construct the current string - DN_USize count; // The number of links in the linked list of strings + DN_Arena *arena; // Allocator to use to back the string list + DN_Str8Link *head; // First string in the linked list of strings + DN_Str8Link *tail; // Last string in the linked list of strings + DN_USize string_size; // The size in bytes necessary to construct the current string + DN_USize count; // The number of links in the linked list of strings }; enum DN_Str8BuilderAdd { - DN_Str8BuilderAdd_Append, - DN_Str8BuilderAdd_Prepend, + DN_Str8BuilderAdd_Append, + DN_Str8BuilderAdd_Prepend, }; -// NOTE: [$CSTR] DN_CStr8 ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_CStr8 ////////////////////////////////////////////////////////////////////////////////// template constexpr DN_USize DN_CStr8_ArrayUCount (char const (&literal)[N]) { (void)literal; return N - 1; } template constexpr DN_USize DN_CStr8_ArrayICount (char const (&literal)[N]) { (void)literal; return N - 1; } DN_API DN_USize DN_CStr8_FSize (DN_FMT_ATTRIB char const *fmt, ...); @@ -147,7 +151,7 @@ DN_API DN_USize DN_CStr8_FVSize DN_API DN_USize DN_CStr8_Size (char const *a); DN_API DN_USize DN_CStr16_Size (wchar_t const *a); -// NOTE: [$STR6] DN_Str16 ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_Str16 ////////////////////////////////////////////////////////////////////////////////// #define DN_STR16(string) DN_Str16{(wchar_t *)(string), sizeof(string)/sizeof(string[0]) - 1} #define DN_Str16_HasData(string) ((string).data && (string).size) @@ -156,7 +160,7 @@ DN_API bool operator== DN_API bool operator!= (DN_Str16 const &lhs, DN_Str16 const &rhs); #endif -// NOTE: [$STR8] DN_Str8 ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// #define DN_STR8(string) DN_Str8{(char *)(string), (sizeof(string) - 1)} #define DN_STR_FMT(string) (int)((string).size), (string).data #define DN_Str8_Init(data, size) DN_Str8{(char *)(data), (size_t)(size)} @@ -166,17 +170,9 @@ DN_API DN_Str8 DN_Str8_InitCStr8 DN_API bool DN_Str8_IsAll (DN_Str8 string, DN_Str8IsAll is_all); DN_API DN_Str8 DN_Str8_InitF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_Str8_InitF_TLS(...) DN_Str8_InitF(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_Str8_InitF_Frame(...) DN_Str8_InitF(DN_TLS_FrameArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8_InitFV (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args); -#define DN_Str8_InitFV_TLS(...) DN_Str8_InitFV(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8_Alloc (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); -#define DN_Str8_Alloc_TLS(...) DN_Str8_Alloc(DN_TLS_TopArena(), ##__VA_ARGS__) -DN_API DN_Str8 DN_Str8_CopyCString (DN_Arena *arena, char const *string, DN_USize size); -#define DN_Str8_CopyCString_TLS(...) DN_Str8_CopyCString(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8_Copy (DN_Arena *arena, DN_Str8 string); -#define DN_Str8_Copy_TLS(...) DN_Str8_Copy(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_Str8_Copy_Frame(...) DN_Str8_Copy(DN_TLS_FrameArena(), ##__VA_ARGS__) DN_API char * DN_Str8_End (DN_Str8 string); DN_API DN_Str8 DN_Str8_Slice (DN_Str8 string, DN_USize offset, DN_USize size); @@ -188,15 +184,12 @@ DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitL DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLast (DN_Str8 string, DN_Str8 find); DN_API DN_USize DN_Str8_Split (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode); DN_API DN_Slice DN_Str8_SplitAlloc (DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); -#define DN_Str8_SplitAlloc_TLS(...) DN_Str8_SplitAlloc(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8FindResult DN_Str8_FindStr8Array (DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case); DN_API DN_Str8FindResult DN_Str8_FindStr8 (DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case); DN_API DN_Str8FindResult DN_Str8_Find (DN_Str8 string, uint32_t flags); DN_API DN_Str8 DN_Str8_Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -#define DN_Str8_Segment_TLS(...) DN_Str8_Segment(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8_ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -#define DN_Str8_ReverseSegment_TLS(...) DN_Str8_ReverseSegment(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API bool DN_Str8_Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); DN_API bool DN_Str8_EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); @@ -210,6 +203,8 @@ DN_API DN_Str8 DN_Str8_TrimPrefix DN_API DN_Str8 DN_Str8_TrimHexPrefix (DN_Str8 string); DN_API DN_Str8 DN_Str8_TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); DN_API DN_Str8 DN_Str8_TrimAround (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8_TrimHeadWhitespace (DN_Str8 string); +DN_API DN_Str8 DN_Str8_TrimTailWhitespace (DN_Str8 string); DN_API DN_Str8 DN_Str8_TrimWhitespaceAround (DN_Str8 string); DN_API DN_Str8 DN_Str8_TrimByteOrderMark (DN_Str8 string); @@ -221,46 +216,30 @@ DN_API DN_Str8 DN_Str8_FileExtensio DN_API DN_Str8ToU64Result DN_Str8_ToU64 (DN_Str8 string, char separator); DN_API DN_Str8ToI64Result DN_Str8_ToI64 (DN_Str8 string, char separator); -#define DN_Str8_AppendF_TLS(...) DN_Str8_AppendF(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8_AppendF (DN_Arena *arena, DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_AppendFV (DN_Arena *arena, DN_Str8 string, char const *fmt, va_list args); + DN_API DN_Str8 DN_Str8_FillF (DN_Arena *arena, DN_USize count, char const *fmt, ...); -#define DN_Str8_FillF_TLS(count, ...) DN_Str8_FillF(DN_TLS_TopArena(), count, ##__VA_ARGS__) -#define DN_Str8_FillF_Frame(count, ...) DN_Str8_FillF(DN_TLS_FrameArena(), count, ##__VA_ARGS__) -DN_API DN_Str8 DN_Str8_Replace (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API DN_Str8 DN_Str8_ReplaceInsensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); +DN_API DN_Str8 DN_Str8_FillFV (DN_Arena *arena, DN_USize count, char const *fmt, va_list args); + DN_API void DN_Str8_Remove (DN_Str8 *string, DN_USize offset, DN_USize size); + DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddle (DN_Arena *arena, DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); -#define DN_Str8_DotTruncateMiddle_Frame(...) DN_Str8_DotTruncateMiddle(DN_TLS_FrameArena(), ## __VA_ARGS__) -DN_API DN_Str8 DN_Str8_PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad); -#define DN_Str8_PadNewLines_TLS(src, pad) DN_Str8_PadNewLines(DN_TLS_TopArena(), src, pad) - -#define DN_Str8_Lower_TLS(...) DN_Str8_Lower(DN_TLS_TopArena(), __VA_ARGS__) DN_API DN_Str8 DN_Str8_Lower (DN_Arena *arena, DN_Str8 string); -#define DN_Str8_Upper_TLS(...) DN_Str8_Upper(DN_TLS_TopArena(), __VA_ARGS__) DN_API DN_Str8 DN_Str8_Upper (DN_Arena *arena, DN_Str8 string); #if defined(__cplusplus) -DN_API bool operator== (DN_Str8 const &lhs, DN_Str8 const &rhs); -DN_API bool operator!= (DN_Str8 const &lhs, DN_Str8 const &rhs); +DN_API bool operator== (DN_Str8 const &lhs, DN_Str8 const &rhs); +DN_API bool operator!= (DN_Str8 const &lhs, DN_Str8 const &rhs); #endif -// NOTE: [$STRB] DN_Str8Builder /////////////////////////////////////////////////////////////////// +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// DN_API DN_Str8Builder DN_Str8Builder_Init (DN_Arena *arena); -DN_API DN_Str8Builder DN_Str8Builder_Init_Frame () { return DN_Str8Builder_Init(DN_TLS_Get()->frame_arena); } -#define DN_Str8Builder_Init_TLS() DN_Str8Builder_Init(DN_TLS_TopArena()) DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); -DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef_Frame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayRef(DN_TLS_Get()->frame_arena, strings, size); } -#define DN_Str8Builder_InitArrayRef_TLS(...) DN_Str8Builder_InitArrayRef(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy (DN_Arena *arena, DN_Str8 const *strings, DN_USize size); -DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy_Frame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayCopy(DN_TLS_Get()->frame_arena, strings, size); } -#define DN_Str8Builder_InitArrayCopy_TLS(...) DN_Str8Builder_InitArrayCopy(DN_TLS_TopArena(), ##__VA_ARGS__) template DN_Str8Builder DN_Str8Builder_InitCArrayRef (DN_Arena *arena, DN_Str8 const (&array)[N]); -template DN_Str8Builder DN_Str8Builder_InitCArrayRef_Frame (DN_Str8 const (&array)[N]) { return DN_Str8Builder_InitCArrayRef(DN_TLS_Get()->frame_arena, array); } -#define DN_Str8Builder_InitCArrayRef_TLS(...) DN_Str8Builder_InitCArrayRef(DN_TLS_TopArena(), ##__VA_ARGS__) template DN_Str8Builder DN_Str8Builder_InitCArrayCopy (DN_Arena *arena, DN_Str8 const (&array)[N]); -template DN_Str8Builder DN_Str8Builder_InitCArrayCopy_Frame (DN_Str8 const (&array)[N]) { return DN_Str8Builder_InitCArrayCopy(DN_TLS_Get()->frame_arena, array); } -#define DN_Str8Builder_InitCArrayCopy_TLS(...) DN_Str8Builder_InitCArrayCopy(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API bool DN_Str8Builder_AddArrayRef (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); DN_API bool DN_Str8Builder_AddArrayCopy (DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add); @@ -290,19 +269,11 @@ DN_API bool DN_Str8Builder_Prepe DN_API bool DN_Str8Builder_Erase (DN_Str8Builder *builder, DN_Str8 string); DN_API DN_Str8Builder DN_Str8Builder_Copy (DN_Arena *arena, DN_Str8Builder const *builder); -#define DN_Str8Builder_Copy_TLS(...) DN_Str8Builder_Copy(DN_TLS_TopArena(), ##__VA_ARGS__) DN_API DN_Str8 DN_Str8Builder_Build (DN_Str8Builder const *builder, DN_Arena *arena); -#define DN_Str8Builder_Build_TLS(...) DN_Str8Builder_Build(__VA_ARGS__, DN_TLS_TopArena()) -#define DN_Str8Builder_Build_Frame(...) DN_Str8Builder_Build(__VA_ARGS__, DN_TLS_FrameArena()) DN_API DN_Str8 DN_Str8Builder_BuildDelimited (DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena); -#define DN_Str8Builder_BuildDelimited_TLS(...) DN_Str8Builder_BuildDelimited(__VA_ARGS__, DN_TLS_TopArena()) -DN_API DN_Str8 DN_Str8Builder_BuildCRT (DN_Str8Builder const *builder); DN_API DN_Slice DN_Str8Builder_BuildSlice (DN_Str8Builder const *builder, DN_Arena *arena); -#define DN_Str8Builder_BuildSlice_TLS(...) DN_Str8Builder_BuildSlice(__VA_ARGS__, DN_TLS_TopArena()) -DN_API void DN_Str8Builder_Print (DN_Str8Builder const *builder); -DN_API void DN_Str8Builder_PrintLn (DN_Str8Builder const *builder); -// NOTE: [$FSTR] DN_FStr8 ////////////////////////////////////////////////////////////////////// +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// #if !defined(DN_NO_FSTR8) template DN_FStr8 DN_FStr8_InitF (DN_FMT_ATTRIB char const *fmt, ...); template DN_FStr8 DN_FStr8_InitFV (char const *fmt, va_list args); @@ -317,15 +288,15 @@ template bool DN_FStr8_Eq template bool DN_FStr8_EqStr8 (DN_FStr8 const *lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); template bool DN_FStr8_EqInsensitive (DN_FStr8 const *lhs, DN_FStr8 const *rhs); template bool DN_FStr8_EqStr8Insensitive (DN_FStr8 const *lhs, DN_Str8 rhs); -template bool DN_FStr8_EqFStr8 (DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case); -template bool DN_FStr8_EqFStr8Insensitive (DN_FStr8 const *lhs, DN_FStr8 const *rhs); -template bool operator== (DN_FStr8 const &lhs, DN_FStr8 const &rhs); -template bool operator!= (DN_FStr8 const &lhs, DN_FStr8 const &rhs); -template bool operator== (DN_FStr8 const &lhs, DN_Str8 const &rhs); -template bool operator!= (DN_FStr8 const &lhs, DN_Str8 const &rhs); +template bool DN_FStr8_EqFStr8 (DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case); +template bool DN_FStr8_EqFStr8Insensitive (DN_FStr8 const *lhs, DN_FStr8 const *rhs); +template bool operator== (DN_FStr8 const &lhs, DN_FStr8 const &rhs); +template bool operator!= (DN_FStr8 const &lhs, DN_FStr8 const &rhs); +template bool operator== (DN_FStr8 const &lhs, DN_Str8 const &rhs); +template bool operator!= (DN_FStr8 const &lhs, DN_Str8 const &rhs); #endif // !defined(DN_NO_FSTR8) -// NOTE: [$CHAR] DN_Char ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Char /////////////////////////////////////////////////////////////////////////////////// struct DN_CharHexToU8 { bool success; @@ -343,195 +314,219 @@ DN_API char DN_Char_ToHexUncheck DN_API char DN_Char_ToLower (char ch); DN_API char DN_Char_ToUpper (char ch); -// NOTE: [$UTFX] DN_UTF /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_UTF //////////////////////////////////////////////////////////////////////////////////// DN_API int DN_UTF8_EncodeCodepoint (uint8_t utf8[4], uint32_t codepoint); DN_API int DN_UTF16_EncodeCodepoint (uint16_t utf16[2], uint32_t codepoint); -// NOTE: [$STRB] DN_Str8Builder /////////////////////////////////////////////////////////////////// -template DN_Str8Builder DN_Str8Builder_InitCArrayRef(DN_Arena *arena, DN_Str8 const (&array)[N]) +// NOTE: DN_Str8Builder /////////////////////////////////////////////////////////////////////////// +template +DN_Str8Builder DN_Str8Builder_InitCArrayRef(DN_Arena *arena, DN_Str8 const (&array)[N]) { - DN_Str8Builder result = DN_Str8Builder_InitArrayRef(arena, array, N); - return result; + DN_Str8Builder result = DN_Str8Builder_InitArrayRef(arena, array, N); + return result; } -template DN_Str8Builder DN_Str8Builder_InitCArrayCopy(DN_Arena *arena, DN_Str8 const (&array)[N]) +template +DN_Str8Builder DN_Str8Builder_InitCArrayCopy(DN_Arena *arena, DN_Str8 const (&array)[N]) { - DN_Str8Builder result = DN_Str8Builder_InitArrayCopy(arena, array, N); - return result; + DN_Str8Builder result = DN_Str8Builder_InitArrayCopy(arena, array, N); + return result; } -template bool DN_Str8Builder_AddCArrayRef(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) +template +bool DN_Str8Builder_AddCArrayRef(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) { - bool result = DN_Str8Builder_AddArrayRef(builder, array, N, add); - return result; + bool result = DN_Str8Builder_AddArrayRef(builder, array, N, add); + return result; } -template bool DN_Str8Builder_AddCArrayCopy(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) +template +bool DN_Str8Builder_AddCArrayCopy(DN_Str8Builder *builder, DN_Str8 const (&array)[N], DN_Str8BuilderAdd add) { - bool result = DN_Str8Builder_AddArrayCopy(builder, array, N, add); - return result; + bool result = DN_Str8Builder_AddArrayCopy(builder, array, N, add); + return result; } #if !defined(DN_NO_FSTR8) -// NOTE: [$FSTR] DN_FStr8 ///////////////////////////////////////////////////////////////////////// -template DN_FStr8 DN_FStr8_InitF(DN_FMT_ATTRIB char const *fmt, ...) +// NOTE: DN_FStr8 ////////////////////////////////////////////////////////////////////////////////// +template +DN_FStr8 DN_FStr8_InitF(DN_FMT_ATTRIB char const *fmt, ...) { - DN_FStr8 result = {}; - if (fmt) { - va_list args; - va_start(args, fmt); - DN_FStr8_AddFV(&result, fmt, args); - va_end(args); - } - return result; -} - -template DN_FStr8 DN_FStr8_InitFV(char const *fmt, va_list args) -{ - DN_FStr8 result = {}; - DN_FStr8_AddFV(&result, fmt, args); - return result; -} - -template DN_USize DN_FStr8_Max(DN_FStr8 const *) -{ - DN_USize result = N; - return result; -} - -template void DN_FStr8_Clear(DN_FStr8 *string) -{ - *string = {}; -} - -template bool DN_FStr8_AddFV(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - bool result = false; - if (!string || !fmt) - return result; - - DN_USize require = DN_CStr8_FVSize(fmt, args) + 1 /*null_terminate*/; - DN_USize space = (N + 1) - string->size; - result = require <= space; - string->size += DN_VSNPRINTF(string->data + string->size, DN_CAST(int)space, fmt, args); - - // NOTE: snprintf returns the required size of the format string - // irrespective of if there's space or not. - string->size = DN_MIN(string->size, N); - return result; -} - -template bool DN_FStr8_AddF(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, ...) -{ - bool result = false; - if (!string || !fmt) - return result; + DN_FStr8 result = {}; + if (fmt) { va_list args; va_start(args, fmt); - result = DN_FStr8_AddFV(string, fmt, args); + DN_FStr8_AddFV(&result, fmt, args); va_end(args); - return result; + } + return result; } -template bool DN_FStr8_AddCStr8(DN_FStr8 *string, char const *src, DN_USize size) +template +DN_FStr8 DN_FStr8_InitFV(char const *fmt, va_list args) { - DN_ASSERT(string->size <= N); - bool result = false; - if (!string || !src || size == 0 || string->size >= N) - return result; - - DN_USize space = N - string->size; - result = size <= space; - DN_MEMCPY(string->data + string->size, src, DN_MIN(space, size)); - string->size = DN_MIN(string->size + size, N); - string->data[string->size] = 0; - return result; + DN_FStr8 result = {}; + DN_FStr8_AddFV(&result, fmt, args); + return result; } -template bool DN_FStr8_Add(DN_FStr8 *string, DN_Str8 src) +template +DN_USize DN_FStr8_Max(DN_FStr8 const *) { - bool result = DN_FStr8_AddCStr8(string, src.data, src.size); - return result; + DN_USize result = N; + return result; } -template DN_Str8 DN_FStr8_ToStr8(DN_FStr8 const *string) +template +void DN_FStr8_Clear(DN_FStr8 *string) { - DN_Str8 result = {}; - if (!string || string->size <= 0) - return result; - - result.data = DN_CAST(char *)string->data; - result.size = string->size; - return result; + *string = {}; } -template bool DN_FStr8_Eq(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) +template +bool DN_FStr8_AddFV(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, va_list args) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); - bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + bool result = false; + if (!string || !fmt) return result; + + DN_USize require = DN_CStr8_FVSize(fmt, args) + 1 /*null_terminate*/; + DN_USize space = (N + 1) - string->size; + result = require <= space; + string->size += DN_VSNPrintF(string->data + string->size, DN_CAST(int) space, fmt, args); + + // NOTE: snprintf returns the required size of the format string + // irrespective of if there's space or not. + string->size = DN_Min(string->size, N); + return result; } -template bool DN_FStr8_EqStr8(DN_FStr8 const *lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) +template +bool DN_FStr8_AddF(DN_FStr8 *string, DN_FMT_ATTRIB char const *fmt, ...) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - bool result = DN_Str8_Eq(lhs_s8, rhs, eq_case); + bool result = false; + if (!string || !fmt) return result; + va_list args; + va_start(args, fmt); + result = DN_FStr8_AddFV(string, fmt, args); + va_end(args); + return result; } -template bool DN_FStr8_EqInsensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) +template +bool DN_FStr8_AddCStr8(DN_FStr8 *string, char const *src, DN_USize size) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); - bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); + DN_Assert(string->size <= N); + bool result = false; + if (!string || !src || size == 0 || string->size >= N) return result; + + DN_USize space = N - string->size; + result = size <= space; + DN_Memcpy(string->data + string->size, src, DN_Min(space, size)); + string->size = DN_Min(string->size + size, N); + string->data[string->size] = 0; + return result; } -template bool DN_FStr8_EqStr8Insensitive(DN_FStr8 const *lhs, DN_Str8 rhs) +template +bool DN_FStr8_Add(DN_FStr8 *string, DN_Str8 src) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - bool result = DN_Str8_Eq(lhs_s8, rhs, DN_Str8EqCase_Insensitive); - return result; + bool result = DN_FStr8_AddCStr8(string, src.data, src.size); + return result; } -template bool DN_FStr8_EqFStr8(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) +template +DN_Str8 DN_FStr8_ToStr8(DN_FStr8 const *string) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); - bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + DN_Str8 result = {}; + if (!string || string->size <= 0) return result; + + result.data = DN_CAST(char *) string->data; + result.size = string->size; + return result; } -template bool DN_FStr8_EqFStr8Insensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) +template +bool DN_FStr8_Eq(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) { - DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); - DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); - bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); - return result; + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; } -template bool operator==(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +template +bool DN_FStr8_EqStr8(DN_FStr8 const *lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) { - bool result = DN_FStr8_Eq(&lhs, &rhs, DN_Str8EqCase_Sensitive); - return result; + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + bool result = DN_Str8_Eq(lhs_s8, rhs, eq_case); + return result; } -template bool operator!=(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +template +bool DN_FStr8_EqInsensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) { - bool result = !(lhs == rhs); - return result; + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); + return result; } -template bool operator==(DN_FStr8 const &lhs, DN_Str8 const &rhs) +template +bool DN_FStr8_EqStr8Insensitive(DN_FStr8 const *lhs, DN_Str8 rhs) { - bool result = DN_Str8_Eq(DN_FStr8_ToStr8(&lhs), rhs, DN_Str8EqCase_Insensitive); - return result; + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + bool result = DN_Str8_Eq(lhs_s8, rhs, DN_Str8EqCase_Insensitive); + return result; } -template bool operator!=(DN_FStr8 const &lhs, DN_Str8 const &rhs) +template +bool DN_FStr8_EqFStr8(DN_FStr8 const *lhs, DN_FStr8 const *rhs, DN_Str8EqCase eq_case) { - bool result = !(lhs == rhs); - return result; + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, eq_case); + return result; +} + +template +bool DN_FStr8_EqFStr8Insensitive(DN_FStr8 const *lhs, DN_FStr8 const *rhs) +{ + DN_Str8 lhs_s8 = DN_FStr8_ToStr8(lhs); + DN_Str8 rhs_s8 = DN_FStr8_ToStr8(rhs); + bool result = DN_Str8_Eq(lhs_s8, rhs_s8, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool operator==(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +{ + bool result = DN_FStr8_Eq(&lhs, &rhs, DN_Str8EqCase_Sensitive); + return result; +} + +template +bool operator!=(DN_FStr8 const &lhs, DN_FStr8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +template +bool operator==(DN_FStr8 const &lhs, DN_Str8 const &rhs) +{ + bool result = DN_Str8_Eq(DN_FStr8_ToStr8(&lhs), rhs, DN_Str8EqCase_Insensitive); + return result; +} + +template +bool operator!=(DN_FStr8 const &lhs, DN_Str8 const &rhs) +{ + bool result = !(lhs == rhs); + return result; } #endif // !defined(DN_NO_FSTR8) +#endif // !defined(DN_BASE_STRING_H) diff --git a/Core/dn_core.cpp b/Core/dn_core.cpp new file mode 100644 index 0000000..dd4f4ab --- /dev/null +++ b/Core/dn_core.cpp @@ -0,0 +1,91 @@ +static DN_Core *g_dn_core; + +DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) +{ + DN_Assert(g_dn_os_core_); + g_dn_core = core; + + // NOTE Initialise fields ////////////////////////////////////////////////////////////////////// + #if !defined(DN_NO_PROFILER) + core->profiler = &core->profiler_default_instance; + #endif + + #if defined(DN_LEAK_TRACKING) + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + core->alloc_table_arena = DN_Arena_InitFromOSVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); + core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); + #endif + + // NOTE: Print out init features /////////////////////////////////////////////////////////////// + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + if (on_init & DN_CoreOnInit_LogLibFeatures) { + DN_Str8Builder_AppendRef(&builder, DN_STR8("DN initialised:\n")); + + DN_F64 page_size_kib = g_dn_os_core_->page_size / 1024.0; + DN_F64 alloc_granularity_kib = g_dn_os_core_->alloc_granularity / 1024.0; + DN_Str8Builder_AppendF( + &builder, " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n", page_size_kib, alloc_granularity_kib); + + #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (DN_ASAN_POISON) { + DN_Str8Builder_AppendF( + &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + DN_Str8Builder_AppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); + } + #endif + + #if defined(DN_LEAK_TRACKING) + DN_Str8Builder_AppendRef(&builder, DN_STR8(" Allocation leak tracing\n")); + #endif + + #if !defined(DN_NO_PROFILER) + DN_Str8Builder_AppendRef(&builder, DN_STR8(" TSC profiler available\n")); + #endif + // TODO(doyle): Add stacktrace feature log + } + + if (on_init & DN_CoreOnInit_LogCPUFeatures) { + DN_CPUReport const *report = &g_dn_os_core_->cpu_report; + DN_Str8 brand = DN_Str8_TrimWhitespaceAround(DN_Str8_Init(report->brand, sizeof(report->brand) - 1)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'. + DN_Str8Builder_AppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); + DN_MSVC_WARNING_POP + + DN_USize longest_feature_name = 0; + DN_ForIndexU(feature_index, DN_CPUFeature_Count) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); + } + + DN_ForIndexU(feature_index, DN_CPUFeature_Count) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + bool has_feature = DN_CPU_HasFeature(report, feature_decl.value); + DN_Str8Builder_AppendF(&builder, + " %.*s:%*s%s\n", + DN_STR_FMT(feature_decl.label), + DN_CAST(int)(longest_feature_name - feature_decl.label.size), + "", + has_feature ? "available" : "not available"); + } + } + + DN_Str8 info_log = DN_Str8Builder_Build(&builder, tmem.arena); + if (DN_Str8_HasData(info_log)) + DN_LOG_DebugF("%.*s", DN_STR_FMT(info_log)); +} + +DN_API void DN_Core_BeginFrame() +{ + DN_Atomic_SetValue64(&g_dn_os_core_->mem_allocs_frame, 0); +} + +#if !defined(DN_NO_PROFILER) +DN_API void DN_Core_SetProfiler(DN_Profiler *profiler) +{ + if (profiler) + g_dn_core->profiler = profiler; +} +#endif diff --git a/Core/dn_core.h b/Core/dn_core.h new file mode 100644 index 0000000..20aad31 --- /dev/null +++ b/Core/dn_core.h @@ -0,0 +1,37 @@ +#if !defined(DN_CORE_H) +#define DN_CORE_H + +// NOTE: DN_Core /////////////////////////////////////////////////////////////////////////////////// +// Book-keeping data for the library and allow customisation of certain features +// provided. +struct DN_Core +{ + // NOTE: Leak Tracing ////////////////////////////////////////////////////////////////////////// + #if defined(DN_LEAK_TRACKING) + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_Arena alloc_table_arena; + #endif + DN_U64 alloc_table_bytes_allocated_for_stack_traces; + + // NOTE: Profiler ////////////////////////////////////////////////////////////////////////////// + #if !defined(DN_NO_PROFILER) + DN_Profiler * profiler; + DN_Profiler profiler_default_instance; + #endif +}; + +enum DN_CoreOnInit +{ + DN_CoreOnInit_Nil = 0, + DN_CoreOnInit_LogLibFeatures = 1 << 0, + DN_CoreOnInit_LogCPUFeatures = 1 << 1, + DN_CoreOnInit_LogAllFeatures = DN_CoreOnInit_LogLibFeatures | DN_CoreOnInit_LogCPUFeatures, +}; + +DN_API void DN_Core_Init (DN_Core *core, DN_CoreOnInit on_init); +DN_API void DN_Core_BeginFrame (); +#if !defined(DN_NO_PROFILER) +DN_API void DN_Core_SetProfiler (DN_Profiler *profiler); +#endif +#endif // !defined(DN_CORE_H) diff --git a/Core/dn_core_debug.cpp b/Core/dn_core_debug.cpp new file mode 100644 index 0000000..ba5c86c --- /dev/null +++ b/Core/dn_core_debug.cpp @@ -0,0 +1,442 @@ +#define DN_CORE_DEBUG_CPP + +DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limit) +{ + DN_StackTraceWalkResult result = {}; +#if defined(DN_OS_WIN32) + if (!arena) + return result; + + static DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + + HANDLE thread = GetCurrentThread(); + result.process = GetCurrentProcess(); + + if (!g_dn_os_core_->win32_sym_initialised) { + g_dn_os_core_->win32_sym_initialised = true; + SymSetOptions(SYMOPT_LOAD_LINES); + if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_WinError error = DN_Win_LastError(tmem.arena); + DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_STR_FMT(error.msg)); + } + } + + CONTEXT context; + RtlCaptureContext(&context); + + STACKFRAME64 frame = {}; + frame.AddrPC.Offset = context.Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Rsp; + frame.AddrStack.Mode = AddrModeFlat; + + DN_FArray raw_frames = {}; + while (raw_frames.size < limit) { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, + result.process, + thread, + &frame, + &context, + nullptr /*ReadMemoryRoutine*/, + SymFunctionTableAccess64, + SymGetModuleBase64, + nullptr /*TranslateAddress*/)) + break; + + // NOTE: It might be useful one day to use frame.AddrReturn.Offset. + // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. + DN_FArray_Add(&raw_frames, frame.AddrPC.Offset); + } + DN_TicketMutex_End(&mutex); + + result.base_addr = DN_Arena_NewArray(arena, uint64_t, raw_frames.size, DN_ZeroMem_No); + result.size = DN_CAST(uint16_t) raw_frames.size; + DN_Memcpy(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); +#else + (void)limit; + (void)arena; +#endif + return result; +} + +static void DN_StackTrace_AddWalkToStr8Builder_(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) +{ + DN_StackTraceRawFrame raw_frame = {}; + raw_frame.process = walk->process; + for (DN_USize index = skip; index < walk->size; index++) { + raw_frame.base_addr = walk->base_addr[index]; + DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder->arena, raw_frame); + DN_Str8Builder_AppendF(builder, "%.*s(%zu): %.*s%s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name), (DN_CAST(int) index == walk->size - 1) ? "" : "\n"); + } +} + +DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) +{ + bool result = false; + if (!it || !walk || !walk->base_addr || !walk->process) + return result; + + if (it->index >= walk->size) + return false; + + result = true; + it->raw_frame.process = walk->process; + it->raw_frame.base_addr = walk->base_addr[it->index++]; + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) +{ + DN_Str8 result{}; + if (!walk || !arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + DN_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); + DN_Str8 result = DN_StackTrace_WalkResultToStr8(arena, &walk, skip); + return result; +} + +DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap(uint16_t limit, uint16_t skip) +{ + // NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which + // does not use the OS heap. + DN_Arena arena = DN_Arena_InitFromOSHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); + DN_Str8Builder builder = DN_Str8Builder_Init(&arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(&arena, limit); + DN_StackTrace_AddWalkToStr8Builder_(&walk, &builder, skip); + DN_Str8 result = DN_Str8Builder_BuildFromOSHeap(&builder); + DN_Arena_Deinit(&arena); + return result; +} + +DN_API DN_Slice DN_StackTrace_GetFrames(DN_Arena *arena, uint16_t limit) +{ + DN_Slice result = {}; + if (!arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); + if (!walk.size) + return result; + + DN_USize slice_index = 0; + result = DN_Slice_Alloc(arena, walk.size, DN_ZeroMem_No); + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { + result.data[slice_index++] = DN_StackTrace_RawFrameToFrame(arena, it.raw_frame); + } + return result; +} + +DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) +{ + #if defined(DN_OS_WIN32) + // NOTE: Get line+filename ///////////////////////////////////////////////////////////////////// + + // TODO: Why does zero-initialising this with `line = {};` cause + // SymGetLineFromAddr64 function to fail once we are at + // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The + // line and file number are still valid in the result which we use, so, + // we silently ignore this error. + IMAGEHLP_LINEW64 line; + line.SizeOfStruct = sizeof(line); + DWORD line_displacement = 0; + if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) { + line = {}; + } + + // NOTE: Get function name ///////////////////////////////////////////////////////////////////// + + alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; + SYMBOL_INFOW *symbol = DN_CAST(SYMBOL_INFOW *)buffer; + symbol->SizeOfStruct = sizeof(*symbol); + symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); + + uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address + SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); + + // NOTE: Construct result ////////////////////////////////////////////////////////////////////// + + DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16_Size(line.FileName)}; + DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; + + DN_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = DN_Win_Str16ToStr8(arena, file_name16); + result.function_name = DN_Win_Str16ToStr8(arena, function_name16); + + if (!DN_Str8_HasData(result.function_name)) + result.function_name = DN_STR8(""); + if (!DN_Str8_HasData(result.file_name)) + result.file_name = DN_STR8(""); + #else + DN_StackTraceFrame result = {}; + #endif + return result; +} + +DN_API void DN_StackTrace_Print(uint16_t limit) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Slice stack_trace = DN_StackTrace_GetFrames(tmem.arena, limit); + for (DN_StackTraceFrame &frame : stack_trace) + DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); +} + +DN_API void DN_StackTrace_ReloadSymbols() +{ + #if defined(DN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); + #endif +} + +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +#if defined(DN_LEAK_TRACKING) +DN_API void DN_Debug_TrackAlloc(void *ptr, DN_USize size, bool leak_permitted) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); + }; + + // NOTE: If the entry was not added, we are reusing a pointer that has been freed. + // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it + // already existed. + DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &g_dn_core->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_CAST(uint64_t) ptr); + DN_DebugAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & DN_DebugAllocFlag_Freed) == 0) { + DN_Str8 alloc_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, alloc->size, DN_CVTU64ByteSizeType_Auto); + DN_Str8 new_alloc_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, size, DN_CVTU64ByteSizeType_Auto); + DN_HardAssertF( + alloc->flags & DN_DebugAllocFlag_Freed, + "This pointer is already in the leak tracker, however it has not been freed yet. This " + "same pointer is being ask to be tracked twice in the allocation table, e.g. one if its " + "previous free calls has not being marked freed with an equivalent call to " + "DN_Debug_TrackDealloc()\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is allocating %.*s again at:\n" + "\n" + "%.*s\n", + ptr, + DN_STR_FMT(alloc_size), + DN_STR_FMT(alloc->stack_trace), + DN_STR_FMT(new_alloc_size), + DN_STR_FMT(stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; + g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; + + DN_OS_MemDealloc(alloc->stack_trace.data); + DN_OS_MemDealloc(alloc->freed_stack_trace.data); + *alloc = {}; + } + + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace; + alloc->flags |= leak_permitted ? DN_DebugAllocFlag_LeakPermitted : 0; + g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; +} + +DN_API void DN_Debug_TrackDealloc(void *ptr) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); + DN_DEFER { DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); }; + + DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &g_dn_core->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_CAST(uintptr_t) ptr); + DN_HardAssertF(alloc_entry.found, + "Allocated pointer can not be removed as it does not exist in the " + "allocation table. When this memory was allocated, the pointer was " + "not added to the allocation table [ptr=%p]", + ptr); + + DN_DebugAlloc *alloc = alloc_entry.value; + if (alloc->flags & DN_DebugAllocFlag_Freed) { + DN_Str8 freed_size = DN_CVT_U64ToByteSizeStr8(alloc_table->arena, alloc->freed_size, DN_CVTU64ByteSizeType_Auto); + DN_HardAssertF((alloc->flags & DN_DebugAllocFlag_Freed) == 0, + "Double free detected, pointer to free was already marked " + "as freed. Either the pointer was reallocated but not " + "traced, or, the pointer was freed twice.\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer was freed at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is being freed again at:\n" + "\n" + "%.*s\n" + , + ptr, DN_STR_FMT(freed_size), + DN_STR_FMT(alloc->stack_trace), + DN_STR_FMT(alloc->freed_stack_trace), + DN_STR_FMT(stack_trace)); + } + + DN_Assert(!DN_Str8_HasData(alloc->freed_stack_trace)); + alloc->flags |= DN_DebugAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; + g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; +} + +DN_API void DN_Debug_DumpLeaks() +{ + uint64_t leak_count = 0; + uint64_t leaked_bytes = 0; + for (DN_USize index = 1; index < g_dn_core->alloc_table.occupied; index++) { + DN_DSMapSlot *slot = g_dn_core->alloc_table.slots + index; + DN_DebugAlloc *alloc = &slot->value; + bool alloc_leaked = (alloc->flags & DN_DebugAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & DN_DebugAllocFlag_LeakPermitted); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + DN_Str8 alloc_size = DN_CVT_U64ToByteSizeStr8(g_dn_core->alloc_table.arena, alloc->size, DN_CVTU64ByteSizeType_Auto); + DN_LOG_WarningF("Pointer (0x%p) leaked %.*s at:\n" + "%.*s", + alloc->ptr, DN_STR_FMT(alloc_size), + DN_STR_FMT(alloc->stack_trace)); + } + } + + if (leak_count) { + char buffer[512]; + DN_Arena arena = DN_Arena_InitFromBuffer(buffer, sizeof(buffer), DN_ArenaFlags_Nil); + DN_Str8 leak_size = DN_CVT_U64ToByteSizeStr8(&arena, leaked_bytes, DN_CVTU64ByteSizeType_Auto); + DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_STR_FMT(leak_size)); + } +} +#endif // DN_LEAK_TRACKING + +#if !defined(DN_NO_PROFILER) +// NOTE: DN_Profiler /////////////////////////////////////////////////////////////////////////////// +DN_API DN_ProfilerZoneScope::DN_ProfilerZoneScope(DN_Str8 name, uint16_t anchor_index) +{ + zone = DN_Profiler_BeginZoneAtIndex(name, anchor_index); +} + +DN_API DN_ProfilerZoneScope::~DN_ProfilerZoneScope() +{ + DN_Profiler_EndZone(zone); +} + +DN_API DN_ProfilerAnchor *DN_Profiler_ReadBuffer() +{ + uint8_t mask = DN_ArrayCountU(g_dn_core->profiler->anchors) - 1; + DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer - 1) & mask]; + return result; +} + +DN_API DN_ProfilerAnchor *DN_Profiler_WriteBuffer() +{ + uint8_t mask = DN_ArrayCountU(g_dn_core->profiler->anchors) - 1; + DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer + 0) & mask]; + return result; +} + +DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex(DN_Str8 name, uint16_t anchor_index) +{ + DN_ProfilerAnchor *anchor = DN_Profiler_WriteBuffer() + anchor_index; + // TODO: We need per-thread-local-storage profiler so that we can use these apis + // across threads. For now, we let them overwrite each other but this is not tenable. + #if 0 + if (DN_Str8_HasData(anchor->name) && anchor->name != name) + DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_STR_FMT(anchor->name), DN_STR_FMT(name)); + #endif + anchor->name = name; + DN_ProfilerZone result = {}; + result.begin_tsc = DN_CPU_TSC(); + result.anchor_index = anchor_index; + result.parent_zone = g_dn_core->profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + g_dn_core->profiler->parent_zone = anchor_index; + return result; +} + +DN_API void DN_Profiler_EndZone(DN_ProfilerZone zone) +{ + uint64_t elapsed_tsc = DN_CPU_TSC() - zone.begin_tsc; + DN_ProfilerAnchor *anchor_buffer = DN_Profiler_WriteBuffer(); + DN_ProfilerAnchor *anchor = anchor_buffer + zone.anchor_index; + + anchor->hit_count++; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + + DN_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + g_dn_core->profiler->parent_zone = zone.parent_zone; +} + +DN_API void DN_Profiler_SwapAnchorBuffer() +{ + g_dn_core->profiler->active_anchor_buffer++; + g_dn_core->profiler->parent_zone = 0; + DN_ProfilerAnchor *anchors = DN_Profiler_WriteBuffer(); + DN_Memset(anchors, + 0, + DN_ArrayCountU(g_dn_core->profiler->anchors[0]) * sizeof(g_dn_core->profiler->anchors[0][0])); +} + +DN_API void DN_Profiler_Dump(uint64_t tsc_per_second) +{ + DN_ProfilerAnchor *anchors = DN_Profiler_ReadBuffer(); + for (size_t anchor_index = 1; anchor_index < DN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) { + DN_ProfilerAnchor const *anchor = anchors + anchor_index; + if (!anchor->hit_count) + continue; + + uint64_t tsc_exclusive = anchor->tsc_exclusive; + uint64_t tsc_inclusive = anchor->tsc_inclusive; + DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; + if (tsc_exclusive == tsc_inclusive) { + DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_STR_FMT(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); + } else { + DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; + DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", + DN_STR_FMT(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds); + } + } +} +#endif // !defined(DN_NO_PROFILER) + diff --git a/Core/dn_core_debug.h b/Core/dn_core_debug.h new file mode 100644 index 0000000..b83f21e --- /dev/null +++ b/Core/dn_core_debug.h @@ -0,0 +1,98 @@ +#if !defined(DN_CORE_DEBUG_H) +#define DN_CORE_DEBUG_H + +// NOTE: DN_StackTrace ///////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// +enum DN_DebugAllocFlag +{ + DN_DebugAllocFlag_Freed = 1 << 0, + DN_DebugAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_DebugAlloc +{ + void *ptr; // 8 Pointer to the allocation being tracked + DN_USize size; // 16 Size of the allocation + DN_USize freed_size; // 24 Store the size of the allocation when it is freed + DN_Str8 stack_trace; // 40 Stack trace at the point of allocation + DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + DN_U16 flags; // 72 Bit flags from `DN_DebugAllocFlag` +}; + +static_assert(sizeof(DN_DebugAlloc) == 64 || sizeof(DN_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively + "We aim to keep the allocation record as light as possible as " + "memory tracking can get expensive. Enforce that there is no " + "unexpected padding."); + +// NOTE: DN_Profiler /////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_PROFILER) +#if !defined(DN_PROFILER_ANCHOR_BUFFER_SIZE) + #define DN_PROFILER_ANCHOR_BUFFER_SIZE 256 +#endif + +struct DN_ProfilerAnchor +{ + // Inclusive refers to the time spent to complete the function call + // including all children functions. + // + // Exclusive refers to the time spent in the function, not including any + // time spent in children functions that we call that are also being + // profiled. If we recursively call into ourselves, the time we spent in + // our function is accumulated. + DN_U64 tsc_inclusive; + DN_U64 tsc_exclusive; + DN_U16 hit_count; + DN_Str8 name; +}; + +struct DN_ProfilerZone +{ + DN_U16 anchor_index; + DN_U64 begin_tsc; + DN_U16 parent_zone; + DN_U64 elapsed_tsc_at_zone_start; +}; + +#if defined(__cplusplus) +struct DN_ProfilerZoneScope +{ + DN_ProfilerZoneScope(DN_Str8 name, DN_U16 anchor_index); + ~DN_ProfilerZoneScope(); + DN_ProfilerZone zone; +}; + +#define DN_Profiler_ZoneScopeAtIndex(name, anchor_index) auto DN_UniqueName(profile_zone_) = DN_ProfilerZoneScope(DN_STR8(name), anchor_index) +#define DN_Profiler_ZoneScope(name) DN_Profiler_ZoneScopeAtIndex(name, __COUNTER__ + 1) +#endif + +#define DN_Profiler_ZoneBlockIndex(name, index) \ + for (DN_ProfilerZone DN_UniqueName(profile_zone__) = DN_Profiler_BeginZoneAtIndex(name, index), DN_UniqueName(dummy__) = {}; \ + DN_UniqueName(dummy__).begin_tsc == 0; \ + DN_Profiler_EndZone(DN_UniqueName(profile_zone__)), DN_UniqueName(dummy__).begin_tsc = 1) + +#define DN_Profiler_ZoneBlock(name) DN_Profiler_ZoneBlockIndex(DN_STR8(name), __COUNTER__ + 1) + +enum DN_ProfilerAnchorBuffer +{ + DN_ProfilerAnchorBuffer_Back, + DN_ProfilerAnchorBuffer_Front, +}; + +struct DN_Profiler +{ + DN_ProfilerAnchor anchors[2][DN_PROFILER_ANCHOR_BUFFER_SIZE]; + DN_U8 active_anchor_buffer; + DN_U16 parent_zone; +}; + +DN_API DN_ProfilerAnchor * DN_Profiler_ReadBuffer (); +DN_API DN_ProfilerAnchor * DN_Profiler_WriteBuffer (); +#define DN_Profiler_BeginZone(name) DN_Profiler_BeginZoneAtIndex(DN_STR8(name), __COUNTER__ + 1) +DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex (DN_Str8 name, DN_U16 anchor_index); +DN_API void DN_Profiler_EndZone (DN_ProfilerZone zone); +DN_API DN_ProfilerAnchor * DN_Profiler_AnchorBuffer (DN_ProfilerAnchorBuffer buffer); +DN_API void DN_Profiler_SwapAnchorBuffer (); +DN_API void DN_Profiler_Dump (DN_U64 tsc_per_second); + +#endif // !defined(DN_NO_PROFILER) +#endif // DN_CORE_DEBUG_H diff --git a/Core/dn_core_demo.cpp b/Core/dn_core_demo.cpp new file mode 100644 index 0000000..e09fe33 --- /dev/null +++ b/Core/dn_core_demo.cpp @@ -0,0 +1,1218 @@ +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ +// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| +// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ +// $$ | $$ |$$ | $$ |$$ | \____$$\ +// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | +// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | +// \_______/ \______/ \______/ \______/ +// +// dn_docs.cpp -- Library documentation via real code examples +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Use this file for documentation and examples of the various APIs in this +// library. Normally docs are written as inline comments in header files, +// however, these quickly go out of date as APIs change. Instead, I provide +// some example code that compiles here that serves to also document the API. +// +// The library header files then become a very minimal reference of exactly the +// function prototypes and definitions instead of massive reams of inline +// comments that visually space out the functions and hinders discoverability +// and/or conciseness of being able to learn the breadth of the APIs. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4702) // unreachable code + +void DN_Docs_Demo() +{ +// NOTE: Before using anything in the library, DN_Core_Init() must be +// called, for example: +#if 0 + DN_Core core = {}; + DN_Core_Init(&core, DN_CoreOnInit_Nil); +#endif + + // NOTE: DN_Atomic_SetValue64 ///////////////////////////////////////////////////////////////// + // NOTE: DN_Atomic_SetValue32 ///////////////////////////////////////////////////////////////// + // Atomically set the value into the target using an atomic compare and swap + // idiom. The return value of the function is the value that was last stored + // in the target. + { + uint64_t target = 8; + uint64_t value_to_set = 0xCAFE; + if (DN_Atomic_SetValue64(&target, value_to_set) == 8) { + // Atomic swap was successful, e.g. the last value that this thread + // observed was '8' which is the value we initialised with e.g. no + // other thread has modified the value. + } + } + + // NOTE: DN_CVT_BytesToHex //////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + unsigned char bytes[2] = {0xFA, 0xCE}; + DN_Str8 hex = DN_CVT_BytesToHex(tmem.arena, bytes, sizeof(bytes)); + DN_Assert(hex == DN_STR8("face")); // NOTE: Guaranteed to be null-terminated + } + + // NOTE: DN_Check ///////////////////////////////////////////////////////////////////////////// + // + // Check the expression trapping in debug, whilst in release- trapping is + // removed and the expression is evaluated as if it were a normal 'if' branch. + // + // This allows handling of the condition gracefully when compiled out but + // traps to notify the developer in builds when it's compiled in. + { + bool flag = true; + if (DN_CheckF(flag, "Flag was false!")) { + /// This branch will execute! + } + } + + // NOTE: DN_CPUID ///////////////////////////////////////////////////////////////////////////// + // Execute the 'CPUID' instruction which lets you query the capabilities of + // the current CPU. + + // NOTE: DN_DEFER + // + // A macro that expands to a C++ lambda that executes arbitrary code on + // scope exit. + { + int x = 0; + DN_DEFER + { + x = 3; + }; + x = 1; + // On scope exit, DN_DEFER object executes and assigns x = 3 + } + + // NOTE: DN_DSMap ///////////////////////////////////////////////////////////////////////////// + // + // A hash table configured using the presets recommended by Demitri Spanos + // from the Handmade Network (HMN), + // + // - power of two capacity + // - grow by 2x on load >= 75% + // - open-addressing with linear probing + // - separate large values (esp. variable length values) into a separate table + // - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) + // - chain-repair on delete (rehash items in the probe chain after delete) + // - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) + // + // Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 + // + // This hash-table stores slots (values) separate from the hash mapping. + // Hashes are mapped to slots using the hash-to-slot array which is an array + // of slot indexes. This array intentionally only stores indexes to maximise + // usage of the cache line. Linear probing on collision will only cost a + // couple of cycles to fetch from L1 cache the next slot index to attempt. + // + // The slots array stores values contiguously, non-sorted allowing iteration + // of the map. On element erase, the last element is swapped into the + // deleted element causing the non-sorted property of this table. + // + // The 0th slot (DN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved + // for a sentinel value, e.g. all zeros value. After map initialisation the + // 'occupied' value of the array will be set to 1 to exclude the sentinel + // from the capacity of the table. Skip the first value if you are iterating + // the hash table! + // + // This hash-table accept either a U64 or a buffer (ptr + len) as the key. + // In practice this covers a majority of use cases (with string, buffer and + // number keys). It also allows us to minimise our C++ templates to only + // require 1 variable which is the Value part of the hash-table simplifying + // interface complexity and cruft brought by C++. + // + // Keys are value-copied into the hash-table. If the key uses a pointer to a + // buffer, this buffer must be valid throughout the lifetime of the hash + // table! + { + // NOTE: DN_DSMap_Init ////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Deinit ////////////////////////////////////////////////////////////////// + // + // Initialise a hash table where the table size *must* be a + // power-of-two, otherwise an assert will be triggered. If + // initialisation fails (e.g. memory allocation failure) the table is + // returned zero-initialised where a call to 'IsValid' will return + // false. + // + // The map takes ownership of the arena. This means in practice that if the + // map needs to resize (e.g. because the load threshold of the table is + // exceeded), the arena associated with it will be released and the memory + // will be reallocated with the larger capacity and reassigned to the arena. + // + // In simple terms, when the map resizes it invalidates all memory that was + // previously allocated with the given arena! + // + // A 'Deinit' of the map will similarly deallocate the passed in arena (as + // the map takes ownership of the arena). + DN_Arena arena = DN_Arena_InitFromOSVMem(0, 0, DN_ArenaFlags_Nil); + DN_DSMap map = DN_DSMap_Init(&arena, /*size*/ 1024, DN_DSMapFlags_Nil); // Size must be PoT! + DN_Assert(DN_DSMap_IsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure) + + // NOTE: DN_DSMap_KeyCStringLit /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyU64 /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyU64NoHash /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyBuffer /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyStr8 /////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_KeyStr8Copy /////////////////////////////////////////////////////////// + // Create a hash-table key where: + // + // KeyCStringLit: Uses a Hash(cstring literal) + // KeyU64: Uses a Hash(U64) + // KeyU64NoHash: Uses a U64 (where it's truncated to 4 bytes) + // KeyBuffer: Uses a Hash(ptr+len) slice of bytes + // KeyStr8: Uses a Hash(string) + // KeyStr8Copy: Uses a Hash(string) that is copied first using the arena + // + // Buffer-based keys memory must persist throughout lifetime of the map. + // Keys are valued copied into the map, alternatively, copy the + // key/buffer before constructing the key. + // + // You *can't* use the map's arena to allocate keys because on resize it + // will deallocate then reallocate the entire arena. + // + // KeyU64NoHash may be useful if you have a source of data that is + // already sufficiently uniformly distributed already (e.g. using 8 + // bytes taken from a SHA256 hash as the key) and the first 4 bytes + // will be used verbatim. + DN_DSMapKey key = DN_DSMap_KeyStr8(&map, DN_STR8("Sample Key")); + + // NOTE: DN_DSMap_Find //////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Make //////////////////////////////////////////////////////////////////// + // NOTE: DN_DSMap_Set //////////////////////////////////////////////////////////////////// + // + // Query or commit key-value pair to the table, where: + // + // Find: does a key-lookup on the table and returns the hash table slot's value + // Make: assigns the key to the table and returns the hash table slot's value + // Set: assigns the key-value to the table and returns the hash table slot's value + // + // A find query will set 'found' to false if it does not exist. + // + // For 'Make' and 'Set', 'found' can be set to 'true' if the item already + // existed in the map prior to the call. If it's the first time the + // key-value pair is being inserted 'found' will be set to 'false'. + // + // If by adding the key-value pair to the table puts the table over 75% load, + // the table will be grown to 2x the current the size before insertion + // completes. + { + DN_DSMapResult set_result = DN_DSMap_Set(&map, key, 0xCAFE); + DN_Assert(!set_result.found); // First time we are setting the key-value pair, it wasn't previously in the table + DN_Assert(map.occupied == 2); // Sentinel + new element == 2 + } + + // Iterating elements in the array, note that index '0' is the sentinel + // slot! You typically don't care about it! + for (DN_USize index = 1; index < map.occupied; index++) { + DN_DSMapSlot *it = map.slots + index; + DN_DSMapKey it_key = it->key; + int *it_value = &it->value; + DN_Assert(*it_value == 0xCAFE); + + DN_Assert(DN_Str8_Init(it_key.buffer_data, it_key.buffer_size) == DN_STR8("Sample Key")); + } + + // NOTE: DN_DSMap_Erase /////////////////////////////////////////////////////////////////// + // + // Remove the key-value pair from the table. If by erasing the key-value + // pair from the table puts the table under 25% load, the table will be + // shrunk by 1/2 the current size after erasing. The table will not shrink + // below the initial size that the table was initialised as. + { + bool erased = DN_DSMap_Erase(&map, key); + DN_Assert(erased); + DN_Assert(map.occupied == 1); // Sentinel element + } + + DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); // Deallocates the 'arena' for us! + } + +// NOTE: DN_DSMap_Hash //////////////////////////////////////////////////////////////////////// +// +// Hash the input key using the custom hash function if it's set on the map, +// otherwise uses the default hashing function (32bit Murmur3). + +// NOTE: DN_DSMap_HashToSlotIndex ///////////////////////////////////////////////////////////// +// +// Calculate the index into the map's 'slots' array from the given hash. + +// NOTE: DN_DSMap_Resize ////////////////////////////////////////////////////////////////////// +// +// Resize the table and move all elements to the new map, note that the new +// size must be a power of two. This function wil fail on memory allocation +// failure, or the requested size is smaller than the current number of +// elements in the map to resize. + +// NOTE: DN_OSErrSink ///////////////////////////////////////////////////////////////////////// +// +// Error sinks are a way of accumulating errors from API calls related or +// unrelated into 1 unified error handling pattern. The implemenation of a +// sink requires 2 fundamental design constraints on the APIs supporting +// this pattern. +// +// 1. Pipelining of errors +// Errors emitted over the course of several API calls are accumulated +// into a sink which save the error code and message of the first error +// encountered and can be checked later. +// +// 2. Error proof APIs +// Functions that produce errors must return objects/handles that are +// marked to trigger no-ops used in subsequent functions dependent on it. +// +// Consider the following example demonstrating a conventional error +// handling approach (error values by return/sentinel values) and error +// handling using error-proof and pipelining. + +// (A) Conventional error checking patterns using return/sentinel values +#if 0 + DN_OSFile *file = DN_OS_FileOpen("/path/to/file", ...); + if (file) { + if (!DN_OS_FileWrite(file, "abc")) { + // Error handling! + } + Dnq_OS_FileClose(file); + } else { + // Error handling! + } +#endif + + // (B) Error handling using pipelining and and error proof APIs. APIs that + // produce errors take in the error sink as a parameter. + if (0) { + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); + DN_OS_FileWrite(&file, DN_STR8("abc"), error); + DN_OS_FileClose(&file); + if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { + // Do error handling! + } + } + + // Pipeling and error-proof APIs lets you write sequence of instructions and + // defer error checking until it is convenient or necessary. Functions are + // *guaranteed* to return an object that is usable. There are no hidden + // exceptions to be thrown. Functions may opt to still return error values + // by way of return values thereby *not* precluding the ability to check + // every API call either. + // + // Ultimately, this error handling approach gives more flexibility on the + // manner in how errors are handled with less code. + // + // Error sinks can nest begin and end statements. This will open a new scope + // whereby the current captured error pushed onto a stack and the sink will + // be populated by the first error encountered in that scope. + + if (0) { + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); + DN_OS_FileWrite(&file, DN_STR8("abc"), error); + DN_OS_FileClose(&file); + + { + // NOTE: My error sinks are thread-local, so the returned 'error' is + // the same as the 'error' value above. + DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OS_WriteAll(DN_STR8("/path/to/another/file"), DN_STR8("123"), error); + DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to another file"); + } + + if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { + // Do error handling! + } + } + + // NOTE: DN_FStr8_Max ///////////////////////////////////////////////////////////////////////// + // + // Return the maximum capacity of the string, e.g. the 'N' template + // parameter of FStr8 + + // NOTE: DN_FStr8_ToStr8 ////////////////////////////////////////////////////////////////////// + // + // Create a slice of the string into a pointer and length string (DN_Str8). + // The lifetime of the slice is bound to the lifetime of the FStr8 and is + // invalidated when the FStr8 is. + + // NOTE: DN_CVT_HexToBytes //////////////////////////////////////////////////////////////////////// + { + unsigned char bytes[2]; + DN_USize bytes_written = DN_CVT_HexToBytesPtr(DN_STR8("0xFACE"), bytes, sizeof(bytes)); + DN_Assert(bytes_written == 2); + DN_Assert(bytes[0] == 0xFA); + DN_Assert(bytes[1] == 0xCE); + } + + // NOTE: DN_JSONBuilder_Build ///////////////////////////////////////////////////////////////// + // + // Convert the internal JSON buffer in the builder into a string. + + // NOTE: DN_JSONBuilder_KeyValue, DN_JSONBuilder_KeyValueF + // + // Add a JSON key value pair untyped. The value is emitted directly without + // checking the contents of value. + // + // All other functions internally call into this function which is the main + // workhorse of the builder. + + // NOTE: DN_JSON_Builder_ObjectEnd + // + // End a JSON object in the builder, generates internally a '}' string + + // NOTE: DN_JSON_Builder_ArrayEnd + // + // End a JSON array in the builder, generates internally a ']' string + + // NOTE: DN_JSONBuilder_LiteralNamed + // + // Add a named JSON key-value object whose value is directly written to + // the following '"": ' (e.g. useful for emitting the 'null' + // value) + + // NOTE: DN_JSONBuilder_U64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_U64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_I64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_I64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_F64 ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_F64Named ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_Bool ///////////////////////////////////////////////////////////// + // NOTE: DN_JSONBuilder_BoolNamed ///////////////////////////////////////////////////////////// + // + // Add the named JSON data type as a key-value object. The named variants + // generates internally the key-value pair, e.g. + // + // "": + // + // And the non-named version emit just the 'value' portion + + // NOTE: DN_List_Iterate ////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_List list = DN_List_Init(/*chunk_size*/ 128); + for (DN_ListIterator it = {}; DN_List_Iterate(&list, &it, 0);) { + int *item = it.data; + (void)item; + } + } + + // NOTE: DN_LogProc /////////////////////////////////////////////////////////////////////////// + // + // Function prototype of the logging interface exposed by this library. Logs + // emitted using the DN_Log_* family of functions are routed through this + // routine. + + // NOTE: DN_FNV1A ///////////////////////////////////////////////////////////////////////////// +#if 0 + { + // Using the default hash as defined by DN_FNV1A32_SEED and + // DN_FNV1A64_SEED for 32/64bit hashes respectively + uint32_t buffer1 = 0xCAFE0000; + uint32_t buffer2 = 0xDEAD0000; + { + uint64_t hash = DN_FNV1A64_Hash(&buffer1, sizeof(buffer1)); + hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); // Chained hashing + (void)hash; + } + + // You can use a custom seed by skipping the 'Hash' call and instead + // calling 'Iterate' immediately. + { + uint64_t custom_seed = 0xABCDEF12; + uint64_t hash = DN_FNV1A64_Iterate(&buffer1, sizeof(buffer1), custom_seed); + hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); + (void)hash; + } + } +#endif + + // NOTE: DN_FmtBuffer3DotTruncate ////////////////////////////////////////////////////////////// + { + char buffer[8] = {}; + int buffer_chars_written = DN_CVT_FmtBuffer3DotTruncate(buffer, sizeof(buffer), "This string is longer than %d characters", DN_CAST(int)(sizeof(buffer) - 1)); + if (0) // Prints "This ..." which is exactly 8 characters long + printf("%.*s", buffer_chars_written, buffer); + } + + // NOTE: DN_MurmurHash3 /////////////////////////////////////////////////////////////////////// + // MurmurHash3 was written by Austin Appleby, and is placed in the public + // domain. The author (Austin Appleby) hereby disclaims copyright to this source + // code. + // + // Note - The x86 and x64 versions do _not_ produce the same results, as the + // algorithms are optimized for their respective platforms. You can still + // compile and run any of them on any platform, but your performance with the + // non-native version will be less than optimal. + + // NOTE: DN_OS_DateUnixTime + // + // Produce the time elapsed since the unix epoch + { + uint64_t now = DN_OS_DateUnixTimeS(); + (void)now; + } + + // NOTE: DN_OS_DirIterate ///////////////////////////////////////////////////////////////////// + // + // Iterate the files within the passed in folder + for (DN_OSDirIterator it = {}; DN_OS_DirIterate(DN_STR8("."), &it);) { + // printf("%.*s\n", DN_STR_FMT(it.file_name)); + } + + // NOTE: DN_OS_FileDelete + // + // This function can only delete files and it can *only* delete directories + // if it is empty otherwise this function fails. + + // NOTE: DN_OS_WriteAllSafe + // Writes the file at the path first by appending '.tmp' to the 'path' to + // write to. If the temporary file is written successfully then the file is + // copied into 'path', for example: + // + // path: C:/Home/my.txt + // tmp_path: C:/Home/my.txt.tmp + // + // If 'tmp_path' is written to successfuly, the file will be copied over into + // 'path'. + if (0) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OS_WriteAllSafe(/*path*/ DN_STR8("C:/Home/my.txt"), /*buffer*/ DN_STR8("Hello world"), error); + DN_OS_ErrSinkEndAndLogErrorF(error, ""); + } + + // NOTE: DN_OS_EstimateTSCPerSecond /////////////////////////////////////////////////////////// + // + // Estimate how many timestamp count's (TSC) there are per second. TSC + // is evaluated by calling __rdtsc() or the equivalent on the platform. This + // value can be used to convert TSC durations into seconds. + // + // The 'duration_ms_to_gauge_tsc_frequency' parameter specifies how many + // milliseconds to spend measuring the TSC rate of the current machine. + // 100ms is sufficient to produce a fairly accurate result with minimal + // blocking in applications if calculated on startup.. + // + // This may return 0 if querying the CPU timestamp counter is not supported + // on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). + + // NOTE: DN_OS_EXEDir ///////////////////////////////////////////////////////////////////////// + // + // Retrieve the executable directory without the trailing '/' or ('\' for + // windows). If this fails an empty string is returned. + + // NOTE: DN_OS_PerfCounterFrequency /////////////////////////////////////////////////////////// + // + // Get the number of ticks in the performance counter per second for the + // operating system you're running on. This value can be used to calculate + // duration from OS performance counter ticks. + + // NOTE: DN_OS_Path* ////////////////////////////////////////////////////////////////////////// + // Construct paths ensuring the native OS path separators are used in the + // string. In 99% of cases you can use 'PathConvertF' which converts the + // given path in one shot ensuring native path separators in the string. + // + // path: C:\Home/My/Folder + // converted: C:/Home/My/Folder (On Unix) + // C:\Home\My\Folder (On Windows) + // + // If you need to construct a path dynamically you can use the builder-esque + // interface to build a path's step-by-step using the 'OSPath' data structure. + // With this API you can append paths piece-meal to build the path after all + // pieces are appended. + // + // You may append a singular or nested path to the builder. In the builder, + // the string is scanned and separated into path separated chunks and stored + // in the builder, e.g. these are all valid to pass into 'PathAdd', + // 'PathAddRef' ... e.t.c + // + // "path/to/your/desired/folder" is valid + // "path" is valid + // "path/to\your/desired\folder" is valid + // + // 'PathPop' removes the last appended path from the current path stored in + // the 'OSPath': + // + // path: path/to/your/desired/folder + // popped_path: path/to/your/desired + + // NOTE: DN_OS_SecureRNGBytes ///////////////////////////////////////////////////////////////// + // + // Generate cryptographically secure bytes + +#if 0 + // NOTE: DN_PCG32 ///////////////////////////////////////////////////////////////////////////// + // + // Random number generator of the PCG family. Implementation taken from + // Martins Mmozeiko from Handmade Network. + // https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 + { + DN_PCG32 rng = DN_PCG32_Init(0xb917'a66c'1d9b'3bd8); + + // NOTE: DN_PCG32_Range /////////////////////////////////////////////////////////////////// + // + // Generate a value in the [low, high) interval + uint32_t u32_value = DN_PCG32_Range(&rng, 32, 64); + DN_Assert(u32_value >= 32 && u32_value < 64); + + // NOTE: DN_PCG32_NextF32 ///////////////////////////////////////////////////////////////// + // NOTE: DN_PCG32_NextF64 ///////////////////////////////////////////////////////////////// + // + // Generate a float/double in the [0, 1) interval + DN_F64 f64_value = DN_PCG32_NextF64(&rng); + DN_Assert(f64_value >= 0.f && f64_value < 1.f); + + // NOTE: DN_PCG32_Advance ///////////////////////////////////////////////////////////////// + // + // Step the random number generator by 'delta' steps + DN_PCG32_Advance(&rng, /*delta*/ 5); + } +#endif + +#if 0 +#if !defined(DN_NO_PROFILER) + // NOTE: DN_Profiler ///////////////////////////////////////////////////////////////////////////// + // + // A profiler based off Casey Muratori's Computer Enhance course, Performance + // Aware Programming. This profiler measures function elapsed time using the + // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count + // that can be converted into a duration. + // + // This profiler uses a double buffer scheme for storing profiling markers. + // After an application's typical update/frame cycle you can swap the + // profiler's buffer whereby the front buffer contains the previous frames + // profiling metrics and the back buffer will be populated with the new + // frame's profiling metrics. + { + enum Zone + { + Zone_MainLoop, + Zone_Count + }; + + DN_ProfilerZone profiler_zone_main_update = DN_Profiler_BeginZone(Zone_MainLoop); + + // NOTE: DN_Profiler_AnchorBuffer ///////////////////////////////////////////////////// + // + // Retrieve the requested buffer from the profiler for + // writing/reading profiling metrics. Pass in the enum to specify + // which buffer to grab from the profiler. + // + // The front buffer contains the previous frame's profiling metrics + // and the back buffer is where the profiler is currently writing + // to. + // + // For end user intents and purposes, you likely only need to read + // the front buffer which contain the metrics that you can visualise + // regarding the most profiling metrics recorded. + + // NOTE: DN_Profiler_ReadBuffer /////////////////////////////////////////////////////////// + // + // Retrieve the buffer of anchors of which there are + // `DN_PROFILER_ANCHOR_BUFFER_SIZE` anchors from the most recent run + // of the profiler after you have called `SwapAnchorBuffer` to trigger + // the double buffer + DN_ProfilerAnchor *read_anchors = DN_Profiler_ReadBuffer(); + for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { + DN_ProfilerAnchor *anchor = read_anchors + index; + if (DN_Str8_HasData(anchor->name)) { + // ... + } + } + + // NOTE: DN_Profiler_WriteBuffer ////////////////////////////////////////////////////////// + // + // Same as `ReadBuffer` however we return the buffer that the profiler + // is currently writing anchors into. + DN_ProfilerAnchor *write_anchors = DN_Profiler_WriteBuffer(); + for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { + DN_ProfilerAnchor *anchor = write_anchors + index; + if (DN_Str8_HasData(anchor->name)) { + // ... + } + } + + DN_Profiler_EndZone(profiler_zone_main_update); + DN_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! + DN_Memset(g_dn_core->profiler, 0, sizeof(*g_dn_core->profiler)); + } +#endif // !defined(DN_NO_PROFILER) +#endif + + // NOTE: DN_Raycast_LineIntersectV2 /////////////////////////////////////////////////////////// + // Calculate the intersection point of 2 rays returning a `t` value + // which is how much along the direction of the 'ray' did the intersection + // occur. + // + // The arguments passed in do not need to be normalised for the function to + // work. + + // NOTE: DN_Safe_* //////////////////////////////////////////////////////////////////////////// + // + // Performs the arithmetic operation and uses DN_Check on the operation to + // check if it overflows. If it overflows the MAX value of the integer is + // returned in add and multiply operations, and, the minimum is returned in + // subtraction and division. + + // NOTE: DN_Safe_SaturateCast* //////////////////////////////////////////////////////////////// + // + // Truncate the passed in value to the return type clamping the resulting + // value to the max value of the desired data type. It DN_Check's the + // truncation. + // + // The following sentinel values are returned when saturated, + // USize -> Int: INT_MAX + // USize -> I8: INT8_MAX + // USize -> I16: INT16_MAX + // USize -> I32: INT32_MAX + // USize -> I64: INT64_MAX + // + // U64 -> UInt: UINT_MAX + // U64 -> U8: UINT8_MAX + // U64 -> U16: UINT16_MAX + // U64 -> U32: UINT32_MAX + // + // USize -> U8: UINT8_MAX + // USize -> U16: UINT16_MAX + // USize -> U32: UINT32_MAX + // USize -> U64: UINT64_MAX + // + // ISize -> Int: INT_MIN or INT_MAX + // ISize -> I8: INT8_MIN or INT8_MAX + // ISize -> I16: INT16_MIN or INT16_MAX + // ISize -> I32: INT32_MIN or INT32_MAX + // ISize -> I64: INT64_MIN or INT64_MAX + // + // ISize -> UInt: 0 or UINT_MAX + // ISize -> U8: 0 or UINT8_MAX + // ISize -> U16: 0 or UINT16_MAX + // ISize -> U32: 0 or UINT32_MAX + // ISize -> U64: 0 or UINT64_MAX + // + // I64 -> ISize: DN_ISIZE_MIN or DN_ISIZE_MAX + // I64 -> I8: INT8_MIN or INT8_MAX + // I64 -> I16: INT16_MIN or INT16_MAX + // I64 -> I32: INT32_MIN or INT32_MAX + // + // Int -> I8: INT8_MIN or INT8_MAX + // Int -> I16: INT16_MIN or INT16_MAX + // Int -> U8: 0 or UINT8_MAX + // Int -> U16: 0 or UINT16_MAX + // Int -> U32: 0 or UINT32_MAX + // Int -> U64: 0 or UINT64_MAX + + // NOTE: DN_StackTrace //////////////////////////////////////////////////////////////////////// + // Emit stack traces at the calling site that these functions are invoked + // from. + // + // For some applications, it may be viable to generate raw stack traces and + // store just the base addresses of the call stack from the 'Walk' + // functions. This reduces the memory overhead and required to hold onto + // stack traces and resolve the addresses on-demand when required. + // + // However if your application is loading and/or unloading shared libraries, + // on Windows it may be impossible for the application to resolve raw base + // addresses if they become invalid over time. In these applications you + // must convert the raw stack traces before the unloading occurs, and when + // loading new shared libraries, 'ReloadSymbols' must be called to ensure + // the debug APIs are aware of how to resolve the new addresses imported + // into the address space. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + + // NOTE: DN_StackTrace_Walk /////////////////////////////////////////////////////////////// + // + // Generate a stack trace as a series of addresses to the base of the + // functions on the call-stack at the current instruction pointer. The + // addresses are stored in order from the current executing function + // first to the most ancestor function last in the walk. + DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, /*depth limit*/ 128); + + // Loop over the addresses produced in the stack trace + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk);) { + // NOTE: DN_StackTrace_RawFrameToFrame //////////////////////////////////////////////// + // + // Converts the base address into a human readable stack trace + // entry (e.g. address, line number, file and function name). + DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(tmem.arena, it.raw_frame); + + // You may then print out the frame like so + if (0) + printf("%.*s(%" PRIu64 "): %.*s\n", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); + } + + // If you load new shared-libraries into the address space it maybe + // necessary to call into 'ReloadSymbols' to ensure that the OS is able + // to resolve the new addresses. + DN_StackTrace_ReloadSymbols(); + + // NOTE: DN_StackTrace_GetFrames ////////////////////////////////////////////////////////// + // + // Helper function to create a stack trace and automatically convert the + // raw frames into human readable frames. This function effectively + // calls 'Walk' followed by 'RawFrameToFrame'. + DN_Slice frames = DN_StackTrace_GetFrames(tmem.arena, /*depth limit*/ 128); + (void)frames; + } + + // NOTE: DN_Str8_Alloc //////////////////////////////////////////////////////////////////////// + // + // Allocates a string with the requested 'size'. An additional byte is + // always requested from the allocator to null-terminate the buffer. This + // allows the string to be used with C-style string APIs. + // + // The returned string's 'size' member variable does *not* include this + // additional null-terminating byte. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Alloc(tmem.arena, /*size*/ 1, DN_ZeroMem_Yes); + DN_Assert(string.size == 1); + DN_Assert(string.data[string.size] == 0); // It is null-terminated! + } + + // NOTE: DN_Str8_BinarySplit ////////////////////////////////////////////////////////////////// + // + // Splits a string into 2 substrings occuring prior and after the first + // occurence of the delimiter. Neither strings include the matched + // delimiter. If no delimiter is found, the 'rhs' of the split will be + // empty. + { + DN_Str8BinarySplitResult dot_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8(".")); + DN_Str8BinarySplitResult slash_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8("/")); + DN_Assert(dot_split.lhs == DN_STR8("abc") && dot_split.rhs == DN_STR8("def.ghi")); + DN_Assert(slash_split.lhs == DN_STR8("abc.def.ghi") && slash_split.rhs == DN_STR8("")); + + // Loop that walks the string and produces ("abc", "def", "ghi") + for (DN_Str8 it = DN_STR8("abc.def.ghi"); it.size;) { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(it, DN_STR8(".")); + DN_Str8 chunk = split.lhs; // "abc", "def", ... + it = split.rhs; + (void)chunk; + } + } + + // NOTE: DN_Str8_FileNameFromPath ///////////////////////////////////////////////////////////// + // + // Takes a slice to the file name from a file path. The file name is + // evaluated by searching from the end of the string backwards to the first + // occurring path separator '/' or '\'. If no path separator is found, the + // original string is returned. This function preserves the file extension + // if there were any. + { + { + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/item.txt")); + DN_Assert(string == DN_STR8("item.txt")); + } + { + // TODO(doyle): Intuitively this seems incorrect. Empty string instead? + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/")); + DN_Assert(string == DN_STR8("C:/Folder")); + } + { + DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder")); + DN_Assert(string == DN_STR8("Folder")); + } + } + + // NOTE: DN_Str8_FilePathNoExtension ////////////////////////////////////////////////////////// + // + // This function preserves the original string if no extension was found. + // An extension is defined as the substring after the last '.' encountered + // in the string. + { + DN_Str8 string = DN_Str8_FilePathNoExtension(DN_STR8("C:/Folder/item.txt.bak")); + DN_Assert(string == DN_STR8("C:/Folder/item.txt")); + } + + // NOTE: DN_Str8_FileNameNoExtension ////////////////////////////////////////////////////////// + // + // This function is the same as calling 'FileNameFromPath' followed by + // 'FilePathNoExtension' + { + DN_Str8 string = DN_Str8_FileNameNoExtension(DN_STR8("C:/Folder/item.txt.bak")); + DN_Assert(string == DN_STR8("item.txt")); + } + + // NOTE: DN_Str8_Replace /////////////////////////////////////////////////////////// + // NOTE: DN_Str8_ReplaceInsensitive /////////////////////////////////////////////////////////// + // + // Replace any matching substring 'find' with 'replace' in the passed in + // 'string'. The 'start_index' may be specified to offset which index the + // string will start doing replacements from. + // + // String replacements are not done inline and the returned string will + // always be a newly allocated copy, irrespective of if any replacements + // were done or not. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Replace(/*string*/ DN_STR8("Foo Foo Bar"), + /*find*/ DN_STR8("Foo"), + /*replace*/ DN_STR8("Moo"), + /*start_index*/ 1, + /*arena*/ tmem.arena, + /*eq_case*/ DN_Str8EqCase_Sensitive); + DN_Assert(string == DN_STR8("Foo Moo Bar")); + } + + // NOTE: DN_Str8_Segment ////////////////////////////////////////////////////////////////////// + // + // Add a delimiting 'segment_char' every 'segment_size' number of characters + // in the string. + // + // Reverse segment delimits the string counting 'segment_size' from the back + // of the string. + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Segment(tmem.arena, /*string*/ DN_STR8("123456789"), /*segment_size*/ 3, /*segment_char*/ ','); + DN_Assert(string == DN_STR8("123,456,789")); + } + + // NOTE: DN_Str8_Split //////////////////////////////////////////////////////////////////////// + { + // Splits the string at each delimiter into substrings occuring prior and + // after until the next delimiter. + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + { + DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, + /*string*/ DN_STR8("192.168.8.1"), + /*delimiter*/ DN_STR8("."), + /*mode*/ DN_Str8SplitIncludeEmptyStrings_No); + DN_Assert(splits.size == 4); + DN_Assert(splits.data[0] == DN_STR8("192") && splits.data[1] == DN_STR8("168") && splits.data[2] == DN_STR8("8") && splits.data[3] == DN_STR8("1")); + } + + // You can include empty strings that occur when splitting by setting + // the split mode to include empty strings. + { + DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, + /*string*/ DN_STR8("a--b"), + /*delimiter*/ DN_STR8("-"), + /*mode*/ DN_Str8SplitIncludeEmptyStrings_Yes); + DN_Assert(splits.size == 3); + DN_Assert(splits.data[0] == DN_STR8("a") && splits.data[1] == DN_STR8("") && splits.data[2] == DN_STR8("b")); + } + } + + // NOTE: DN_Str8_ToI64 //////////////////////////////////////////////////////////////////////// + // NOTE: DN_Str8_ToU64 //////////////////////////////////////////////////////////////////////// + // + // Convert a number represented as a string to a signed 64 bit number. + // + // The 'separator' is an optional digit separator for example, if + // 'separator' is set to ',' then '1,234' will successfully be parsed to + // '1234'. If no separator is desired, you may pass in '0' in which + // '1,234' will *not* be succesfully parsed. + // + // Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, + // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of + // digits, the seperator or the permitted prefixes as previously mentioned + // otherwise this function will return false, i.e. "1234 dog" will cause the + // function to return false, however, the output is greedily converted and + // will be evaluated to "1234". + // + // 'ToU64' only '+' prefix is permitted + // 'ToI64' either '+' or '-' prefix is permitted + { + { + DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ ','); + DN_Assert(result.success && result.value == -1234); + } + { + DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ 0); + DN_Assert(!result.success && result.value == 1); // 1 because it's a greedy conversion + } + } + + // NOTE: DN_Str8_TrimByteOrderMark //////////////////////////////////////////////////////////// + // + // Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the + // string if it's present. + + // NOTE: DN_STR_FMT /////////////////////////////////////////////////////////////////////////// + // + // Unpacks a string struct that has the fields {.data, .size} for printing a + // pointer and length style string using the printf format specifier "%.*s" + // + // printf("%.*s\n", DN_STR_FMT(DN_STR8("Hello world"))); + + // NOTE: DN_Str8Builder_AppendF //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendFV //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendRef //////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_AppendCopy //////////////////////////////////////////////////////////// + // + // - Appends a string to the string builder as follows + // + // AppendRef: Stores the string slice by value + // AppendCopy: Stores the string slice by copy (with builder's arena) + // AppendF/V: Constructs a format string and calls 'AppendRef' + + // NOTE: DN_Str8Builder_Build /////////////////////////////////////////////////////////// + // NOTE: DN_Str8Builder_BuildCRT /////////////////////////////////////////////////////////// + // + // Constructs the final string by merging all the appended strings into + // one merged string. + // + // The CRT variant calls into 'malloc' and the string *must* be released + // using 'free'. + + // NOTE: DN_Str8Builder_BuildSlice /////////////////////////////////////////////////////////// + // + // Constructs the final string into an array of strings (e.g. a slice) + + // NOTE: DN_TicketMutex /////////////////////////////////////////////////////////////////////// + // + // A mutex implemented using an atomic compare and swap on tickets handed + // out for each critical section. + // + // This mutex serves ticket in order and will block all other threads until + // the tickets are returned in order. The thread with the oldest ticket that + // has not been returned has right of way to execute, all other threads will + // be blocked in an atomic compare and swap loop. block execution by going + // into an atomic + // + // When a thread is blocked by this mutex, a spinlock intrinsic '_mm_pause' is + // used to yield the CPU and reduce spinlock on the thread. This mutex is not + // ideal for long blocking operations. This mutex does not issue any syscalls + // and relies entirely on atomic instructions. + { + DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); // Simple procedural mutual exclusion lock + DN_TicketMutex_End(&mutex); + + // NOTE: DN_TicketMutex_MakeTicket //////////////////////////////////////////////////////// + // + // Request the next available ticket for locking from the mutex. + DN_UInt ticket = DN_TicketMutex_MakeTicket(&mutex); + + if (DN_TicketMutex_CanLock(&mutex, ticket)) { + // NOTE: DN_TicketMutex_BeginTicket /////////////////////////////////////////////////// + // + // Locks the mutex using the given ticket if possible. If it's not + // the next ticket to be locked the executing thread will block + // until the mutex can lock the ticket, i.e. All prior tickets are + // returned, in sequence, to the mutex. + DN_TicketMutex_BeginTicket(&mutex, ticket); + DN_TicketMutex_End(&mutex); + } + } + + // NOTE: DN_ThreadContext ///////////////////////////////////////////////////////////////////// + // + // Each thread is assigned in their thread-local storage (TLS) tmem and + // permanent arena allocators. These can be used for allocations with a + // lifetime scoped to the lexical scope or for storing data permanently + // using the arena paradigm. + // + // TLS in this implementation is implemented using the `thread_local` C/C++ + // keyword. + // + // 99% of the time you will want DN_OS_TLSTMem(...) which returns you a + // temporary arena for function lifetime allocations. On scope exit, the + // arena is cleared out. + // + // This library's paradigm revolves heavily around arenas including tmem + // arenas into child functions for temporary calculations. If an arena is + // passed into a function, this poses a problem sometimes known as + // 'arena aliasing'. + // + // If an arena aliases another arena (e.g. the arena passed in) is the same + // as the tmem arena requested in the function, we risk the tmem arena + // on scope exit deallocating memory belonging to the caller. + // + // To avoid this we the 'DN_OS_TLSTMem(...)' API takes in a list of arenas + // to ensure that we provide a tmem arena that *won't* alias with the + // caller's arena. If arena aliasing occurs, with ASAN on, generally + // the library will trap and report use-after-poison once violated. + { + DN_OSTLSTMem tmem_a = DN_OS_TLSTMem(nullptr); + + // Now imagine we call a function where we pass tmem_a.arena down + // into it .. If we call tmem again, we need to pass in the arena + // to prevent aliasing. + DN_OSTLSTMem tmem_b = DN_OS_TLSTMem(tmem_a.arena); + DN_Assert(tmem_a.arena != tmem_b.arena); + } + + // @proc DN_Thread_GetTMem + // @desc Retrieve the per-thread temporary arena allocator that is reset on scope + // exit. + + // The tmem arena must be deconflicted with any existing arenas in the + // function to avoid trampling over each other's memory. Consider the situation + // where the tmem arena is passed into the function. Inside the function, if + // the same arena is reused then, if both arenas allocate, when the inner arena + // is reset, this will undo the passed in arena's allocations in the function. + + // @param[in] conflict_arena A pointer to the arena currently being used in the + // function + + // NOTE: DN_CVT_U64ToStr8 ///////////////////////////////////////////////////////////////////////// + { + DN_CVTU64Str8 string = DN_CVT_U64ToStr8(123123, ','); + if (0) // Prints "123,123" + printf("%.*s", DN_STR_FMT(string)); + } + + // NOTE: DN_CVT_U64ToAge ////////////////////////////////////////////////////////////////////////// + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_CVT_U64ToAge(tmem.arena, DN_HoursToSec(2) + DN_MinutesToSec(30), DN_CVTU64AgeUnit_All); + DN_Assert(DN_Str8_Eq(string, DN_STR8("2h 30m"))); + } + + // NOTE: DN_VArray //////////////////////////////////////////////////////////////////////////// + // + // An array that is backed by virtual memory by reserving addressing space + // and comitting pages as items are allocated in the array. This array never + // reallocs, instead you should reserve the upper bound of the memory you + // will possibly ever need (e.g. 16GB) and let the array commit physical + // pages on demand. + // + // On 64 bit operating systems you are given 48 bits of addressable space + // giving you 256 TB of reservable memory. This gives you practically + // an unlimited array capacity that avoids reallocs and only consumes memory + // that is actually occupied by the array. + // + // Each page that is committed into the array will be at page/allocation + // granularity which are always cache aligned. This array essentially retains + // all the benefits of normal arrays, + // + // - contiguous memory + // - O(1) random access + // - O(N) iterate + // + // In addition to no realloc on expansion or shrinking. + // + { + // NOTE: DN_VArray_Init /////////////////////////////////////////////////////////// + // NOTE: DN_VArray_InitByteSize /////////////////////////////////////////////////////////// + // + // Initialise an array with the requested byte size or item capacity + // respectively. The returned array may have a higher capacity than the + // requested amount since requested memory from the OS may have a certain + // alignment requirement (e.g. on Windows reserve/commit are 64k/4k + // aligned). + DN_VArray array = DN_VArray_Init(1024); + DN_Assert(array.size == 0 && array.max >= 1024); + + // NOTE: DN_VArray_Make ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_Add ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_MakeArray ////////////////////////////////////////////////////////////// + // NOTE: DN_VArray_AddArray ////////////////////////////////////////////////////////////// + // + // Allocate items from the array where: + // + // Make: creates a zero-init item from the array + // Add: creates a zero-init item and memcpy passed in data into the item + // + // If the array has run out of capacity or was never initialised, a null + // pointer is returned. + int *item = DN_VArray_Add(&array, 0xCAFE); + DN_Assert(*item == 0xCAFE && array.size == 1); + + // NOTE: DN_VArray_AddCArray ///////////////////////////////////////////////////////////// + DN_VArray_AddCArray(&array, {1, 2, 3}); + DN_Assert(array.size == 4); + +// TODO(doyle): There's a bug here with the negative erase! +// Loop over the array items and erase 1 item. +#if 0 + for (DN_USize index = 0; index < array.size; index++) { + if (index != 1) + continue; + + // NOTE: DN_VArray_EraseRange ///////////////////////////////////////////////////////// + // + // Erase the next 'count' items at 'begin_index' in the array. + // 'count' can be positive or negative which dictates the if we + // erase forward from the 'begin_index' or in reverse. + // + // This operation will invalidate all pointers to the array! + // + // A stable erase will shift all elements after the erase ranged + // into the range preserving the order of prior elements. Unstable + // erase will move the tail elements into the range being erased. + // + // Erase range returns a result that contains the next iterator + // index that can be used to update the your for loop index if you + // are trying to iterate over the array. + + // TODO(doyle): There's a bug here! This doesn't work. + // Erase index 0 with the negative count! + DN_ArrayEraseResult erase_result = DN_VArray_EraseRange(&array, + /*begin_index*/ index, + /*count*/ -1, + /*erase*/ DN_ArrayErase_Stable); + DN_Assert(erase_result.items_erased == 1); + + // Use the index returned to continue linearly iterating the array + index = erase_result.it_index; + DN_Assert(array.data[index + 1] == 2); // Next loop iteration will process item '2' + } + + DN_Assert(array.size == 3 && + array.data[0] == 1 && + array.data[1] == 2 && + array.data[2] == 3); +#endif + + // NOTE: DN_VArray_Reserve //////////////////////////////////////////////////////////////////// + // + // Ensure that the requested number of items are backed by physical pages + // from the OS. Calling this pre-emptively will minimise syscalls into the + // kernel to request memory. The requested items will be rounded up to the + // in bytes to the allocation granularity of OS allocation APIs hence the + // reserved space may be greater than the requested amount (e.g. this is 4k + // on Windows). + DN_VArray_Reserve(&array, /*count*/ 8); + + DN_VArray_Deinit(&array); + } + + // NOTE: DN_Win_LastError ///////////////////////////////////////////////////////////// + // NOTE: DN_Win_ErrorCodeToMsg ///////////////////////////////////////////////////////////// + #if defined(DN_PLATFORM_WIN32) + if (0) { + // Generate the error string for the last Win32 API called that return + // an error value. + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_WinError get_last_error = DN_Win_LastError(tmem.arena); + printf("Error (%lu): %.*s", get_last_error.code, DN_STR_FMT(get_last_error.msg)); + + // Alternatively, pass in the error code directly + DN_WinError error_msg_for_code = DN_Win_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); + printf("Error (%lu): %.*s", error_msg_for_code.code, DN_STR_FMT(error_msg_for_code.msg)); + } + + // NOTE: DN_Win_MakeProcessDPIAware /////////////////////////////////////////////////////////// + // + // Call once at application start-up to ensure that the application is DPI + // aware on Windows and ensure that application UI is scaled up + // appropriately for the monitor. + + // NOTE: DN_Win_Str8ToStr16 ///////////////////////////////////////////////////////////// + // NOTE: DN_Win_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// + // NOTE: DN_Win_Str16ToStr8 ///////////////////////////////////////////////////////////// + // NOTE: DN_Win_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// + // + // Convert a UTF8 <-> UTF16 string. + // + // The exact size buffer required for this function can be determined by + // calling this function with the 'dest' set to null and 'dest_size' set to + // 0, the return size is the size required for conversion not-including + // space for the null-terminator. This function *always* null-terminates the + // input buffer. + // + // Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) + // written/required for conversion. 0 if there was a conversion error and can be + // queried using 'DN_Win_LastError' + #endif +} + +DN_MSVC_WARNING_POP diff --git a/dqn_external.cpp b/External/stb_sprintf.h similarity index 84% rename from dqn_external.cpp rename to External/stb_sprintf.h index a3d6528..e4b0323 100644 --- a/dqn_external.cpp +++ b/External/stb_sprintf.h @@ -1,23 +1,229 @@ +// NOTE: Additional *unofficial* changes +// - Adding STBSP__ASAN to STBSP__PUBLICDEC so that MSVC's ASAN does not trigger (https://github.com/nothings/stb/pull/1350) +// - Adding __attribute__((no_sanitize("undefined"))) to STBSP__ASAN for CLANG so that UBSAN does not trigger (https://github.com/nothings/stb/pull/1477) +// - Adding '%S' for DN_Str8 + +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + /* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ -// $$ _____|$$ | $$ |\__$$ __|$$ _____|$$ __$$\ $$$\ $$ |$$ __$$\ $$ | -// $$ | \$$\ $$ | $$ | $$ | $$ | $$ |$$$$\ $$ |$$ / $$ |$$ | -// $$$$$\ \$$$$ / $$ | $$$$$\ $$$$$$$ |$$ $$\$$ |$$$$$$$$ |$$ | -// $$ __| $$ $$< $$ | $$ __| $$ __$$< $$ \$$$$ |$$ __$$ |$$ | -// $$ | $$ /\$$\ $$ | $$ | $$ | $$ |$$ |\$$$ |$$ | $$ |$$ | -// $$$$$$$$\ $$ / $$ | $$ | $$$$$$$$\ $$ | $$ |$$ | \$$ |$$ | $$ |$$$$$$$$\ -// \________|\__| \__| \__| \________|\__| \__|\__| \__|\__| \__|\________| -// -// dqn_external.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) */ -#if !defined(DN_USE_STD_PRINTF) && !defined(DN_STB_SPRINTF_HEADER_ONLY) -// NOTE: [$STBS] stb_sprintf /////////////////////////////////////////////////////////////////////// -#define STB_SPRINTF_IMPLEMENTATION +#if defined(__clang__) + #if defined(__has_feature) && defined(__has_attribute) + #if __has_feature(address_sanitizer) + #if __has_attribute(__no_sanitize__) + #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) + #elif __has_attribute(__no_sanitize_address__) + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #elif __has_attribute(__no_address_safety_analysis__) + #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) + #endif + #endif + #endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) + #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ + #define STBSP__ASAN __attribute__((__no_sanitize_address__)) + #endif +#elif defined(_MSC_VER) + #if defined(__SANITIZE_ADDRESS__) + #define STBSP__ASAN __declspec(no_sanitize_address) + #endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static STBSP__ASAN +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" STBSP__ASAN +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern STBSP__ASAN +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) + #if __has_attribute(format) + #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) + #endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + #ifdef STB_SPRINTF_IMPLEMENTATION #define stbsp__uint32 unsigned int @@ -149,9 +355,6 @@ static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uin return (stbsp__uint32)(sn - s); } -#if defined(__clang__) - __attribute__((no_sanitize("undefined"))) -#endif STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) { static char hex[] = "0123456789abcdefxp"; @@ -406,6 +609,18 @@ STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, // copy the string in goto scopy; + case 'S': + { + DN_Str8 str8 = va_arg(va, DN_Str8); + s = (char *)str8.data; + l = (uint32_t)str8.size; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + }goto scopy; + case 'c': // char // get the character s = num + STBSP__NUMSZ - 1; @@ -1176,7 +1391,7 @@ done: #undef stbsp__flush_cb #undef stbsp__cb_buf_clamp -// //////////////////////////////////////////////////////////////////////////// +// ============================================================================ // wrapper functions STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) @@ -1280,7 +1495,7 @@ STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); } -// /////////////////////////////////////////////////////////////////////// +// ======================================================================= // low level float utility functions #ifndef STB_SPRINTF_NOFLOAT @@ -1710,4 +1925,3 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ -#endif // !defined(DN_USE_STD_PRINTF) && !defined(DN_STB_SPRINTF_HEADER_ONLY) diff --git a/Extra/dn_bin_pack.cpp b/Extra/dn_bin_pack.cpp new file mode 100644 index 0000000..9b27da6 --- /dev/null +++ b/Extra/dn_bin_pack.cpp @@ -0,0 +1,170 @@ +DN_API void DN_BinPack_U64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item) +{ + DN_U64 const VALUE_MASK = 0b0111'1111; + DN_U8 const CONTINUE_BIT = 0b1000'0000; + + if (mode == DN_BinPackMode_Serialise) { + DN_U64 it = *item; + do { + DN_U8 write_value = DN_CAST(DN_U8)(it & VALUE_MASK); + it >>= 7; + if (it) + write_value |= CONTINUE_BIT; + DN_Str8Builder_AppendBytesCopy(&pack->writer, &write_value, sizeof(write_value)); + } while (it); + } else { + *item = 0; + DN_USize bits_read = 0; + for (DN_U8 src = CONTINUE_BIT; (src & CONTINUE_BIT) && bits_read < 64; bits_read += 7) { + src = pack->read.data[pack->read_index++]; + DN_U8 masked_src = src & VALUE_MASK; + *item |= (DN_CAST(DN_U64) masked_src << bits_read); + } + } +} + +DN_API void DN_BinPack_VarInt_(DN_BinPack *pack, DN_BinPackMode mode, void *item, DN_USize size) +{ + DN_U64 value = 0; + DN_AssertF(size <= sizeof(value), + "An item larger than 64 bits (%zu) is trying to be packed as a variable integer which is not supported", + size * 8); + + if (mode == DN_BinPackMode_Serialise) // Read `item` into U64 `value` + DN_Memcpy(&value, item, size); + + DN_BinPack_U64(pack, mode, &value); + + if (mode == DN_BinPackMode_Deserialise) // Write U64 `value` into `item` + DN_Memcpy(item, &value, size); +} + +DN_API void DN_BinPack_U32(DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_U16(DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_U8(DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_I64(DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_I32(DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_I16(DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_I8(DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_F64(DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_F32(DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +#if defined(DN_MATH_H) +DN_API void DN_BinPack_V2(DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item) +{ + DN_BinPack_F32(pack, mode, &item->x); + DN_BinPack_F32(pack, mode, &item->y); +} + +DN_API void DN_BinPack_V4(DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item) +{ + DN_BinPack_F32(pack, mode, &item->x); + DN_BinPack_F32(pack, mode, &item->y); + DN_BinPack_F32(pack, mode, &item->z); + DN_BinPack_F32(pack, mode, &item->w); +} +#endif + +DN_API void DN_BinPack_Bool(DN_BinPack *pack, DN_BinPackMode mode, bool *item) +{ + DN_BinPack_VarInt_(pack, mode, item, sizeof(*item)); +} + +DN_API void DN_BinPack_Str8(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string) +{ + DN_BinPack_VarInt_(pack, mode, &string->size, sizeof(string->size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8Builder_AppendBytesCopy(&pack->writer, string->data, string->size); + } else { + DN_Str8 src = DN_Str8_Slice(pack->read, pack->read_index, string->size); + *string = DN_Str8_Copy(arena, src); + pack->read_index += src.size; + } +} + +DN_API void DN_BinPack_Str8Pool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string) +{ + DN_BinPack_VarInt_(pack, mode, &string->size, sizeof(string->size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8Builder_AppendBytesCopy(&pack->writer, string->data, string->size); + } else { + DN_Str8 src = DN_Str8_Slice(pack->read, pack->read_index, string->size); + *string = DN_Pool_AllocStr8Copy(pool, src); + pack->read_index += src.size; + } +} + +template +DN_API void DN_BinPack_FStr8(DN_BinPack *pack, DN_BinPackMode mode, DN_FStr8 *string) +{ + DN_BinPack_VarInt_(pack, mode, &string->size, sizeof(string->size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8Builder_AppendBytesCopy(&pack->writer, string->data, string->size); + } else { + DN_Str8 src = DN_Str8_Slice(pack->read, pack->read_index, string->size); + *string = DN_FStr8_InitF("%.*s", DN_STR_FMT(src)); + pack->read_index += src.size; + } +} + +DN_API void DN_BinPack_Bytes(DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size) +{ + DN_Str8 string = DN_Str8_Init(*ptr, *size); + DN_BinPack_Str8(pack, arena, mode, &string); + *ptr = string.data; + *size = string.size; +} + +DN_API void DN_BinPack_CArray(DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size) +{ + DN_BinPack_VarInt_(pack, mode, &size, sizeof(size)); + if (mode == DN_BinPackMode_Serialise) { + DN_Str8Builder_AppendBytesCopy(&pack->writer, ptr, size); + } else { + DN_Str8 src = DN_Str8_Slice(pack->read, pack->read_index, size); + DN_Assert(src.size == size); + DN_Memcpy(ptr, src.data, DN_Min(src.size, size)); + pack->read_index += src.size; + } +} + +DN_API DN_Str8 DN_BinPack_Build(DN_BinPack const *pack, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Builder_Build(&pack->writer, arena); + return result; +} diff --git a/Extra/dn_bin_pack.h b/Extra/dn_bin_pack.h new file mode 100644 index 0000000..b14f83c --- /dev/null +++ b/Extra/dn_bin_pack.h @@ -0,0 +1,43 @@ +#if !defined(DN_BIN_PACK_H) +#define DN_BIN_PACK_H + +#if !defined(DN_BASE_INC_H) + #error dn_base_inc.h must be included before this +#endif + +enum DN_BinPackMode +{ + DN_BinPackMode_Serialise, + DN_BinPackMode_Deserialise, +}; + +struct DN_BinPack +{ + DN_Str8Builder writer; + DN_Str8 read; + DN_USize read_index; +}; + +DN_API void DN_BinPack_U64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); +DN_API void DN_BinPack_U32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); +DN_API void DN_BinPack_U16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); +DN_API void DN_BinPack_U8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); +DN_API void DN_BinPack_I64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); +DN_API void DN_BinPack_I32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); +DN_API void DN_BinPack_I16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); +DN_API void DN_BinPack_I8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); +DN_API void DN_BinPack_F64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); +DN_API void DN_BinPack_F32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); +#if defined(DN_MATH_H) +DN_API void DN_BinPack_V2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); +DN_API void DN_BinPack_V4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); +#endif +DN_API void DN_BinPack_Bool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); +DN_API void DN_BinPack_Str8 (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); +DN_API void DN_BinPack_Str8Pool(DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); +template DN_API void DN_BinPack_FStr8 (DN_BinPack *pack, DN_BinPackMode mode, DN_FStr8 *string); +DN_API void DN_BinPack_Bytes (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPack_CArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); +DN_API DN_Str8 DN_BinPack_Build (DN_BinPack const *pack, DN_Arena *arena); + +#endif // !defined(DN_BIN_PACK_H) diff --git a/Extra/dn_cgen.cpp b/Extra/dn_cgen.cpp new file mode 100644 index 0000000..db23ba9 --- /dev/null +++ b/Extra/dn_cgen.cpp @@ -0,0 +1,1288 @@ +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$$$$$$$\ $$\ $$\ +// $$ __$$\ $$ __$$\ $$ _____|$$$\ $$ | +// $$ / \__|$$ / \__|$$ | $$$$\ $$ | +// $$ | $$ |$$$$\ $$$$$\ $$ $$\$$ | +// $$ | $$ |\_$$ |$$ __| $$ \$$$$ | +// $$ | $$\ $$ | $$ |$$ | $$ |\$$$ | +// \$$$$$$ |\$$$$$$ |$$$$$$$$\ $$ | \$$ | +// \______/ \______/ \________|\__| \__| +// +// dn_cgen.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +DN_CGenMapNodeToEnum const DN_CGEN_TABLE_KEY_LIST[] = { + {DN_CGenTableKeyType_Name, DN_STR8("name")}, + {DN_CGenTableKeyType_Type, DN_STR8("type")}, +}; + +DN_CGenMapNodeToEnum const DN_CGEN_TABLE_TYPE_LIST[] = { + {DN_CGenTableType_Data, DN_STR8("data") }, + {DN_CGenTableType_CodeGenBuiltinTypes, DN_STR8("code_gen_builtin_types")}, + {DN_CGenTableType_CodeGenStruct, DN_STR8("code_gen_struct") }, + {DN_CGenTableType_CodeGenEnum, DN_STR8("code_gen_enum") }, +}; + +DN_CGenMapNodeToEnum const DN_CGEN_TABLE_ROW_TAG_LIST[] = { + {DN_CGenTableRowTagType_CommentDivider, DN_STR8("comment_divider")}, + {DN_CGenTableRowTagType_EmptyLine, DN_STR8("empty_line") }, +}; + +DN_CGenMapNodeToEnum const DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST[] = { + {DN_CGenTableRowTagCommentDivider_Label, DN_STR8("label")}, +}; + +DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_STRUCT_HEADER_LIST[] = { + DN_CGenTableHeaderType_Name, + DN_CGenTableHeaderType_Table, + DN_CGenTableHeaderType_CppType, + DN_CGenTableHeaderType_CppName, + DN_CGenTableHeaderType_CppIsPtr, + DN_CGenTableHeaderType_CppOpEquals, + DN_CGenTableHeaderType_CppArraySize, + DN_CGenTableHeaderType_CppArraySizeField, + DN_CGenTableHeaderType_CppLabel, + DN_CGenTableHeaderType_GenTypeInfo, +}; + +DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_ENUM_HEADER_LIST[] = { + DN_CGenTableHeaderType_Name, + DN_CGenTableHeaderType_Table, + DN_CGenTableHeaderType_CppName, + DN_CGenTableHeaderType_CppValue, + DN_CGenTableHeaderType_CppLabel, + DN_CGenTableHeaderType_GenTypeInfo, + DN_CGenTableHeaderType_GenEnumCount, +}; + +DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_BUILTIN_TYPES_HEADER_LIST[] = { + DN_CGenTableHeaderType_Name, +}; + +static bool DN_CGen_GatherTables_(DN_CGen *cgen, DN_OSErrSink *err) +{ + bool result = false; + if (!cgen || !cgen->file_list || !cgen->arena) + return result; + + // NOTE: Gather the tables ///////////////////////////////////////////////////////////////////// + for (MD_EachNode(ref, cgen->file_list->first_child)) { + MD_Node *root = MD_ResolveNodeFromReference(ref); + for (MD_EachNode(node, root->first_child)) { + MD_Node *table_tag = MD_TagFromString(node, MD_S8Lit("table"), 0); + if (MD_NodeIsNil(table_tag)) + continue; + + DN_CGenTable *table = MD_PushArray(cgen->arena, DN_CGenTable, 1); + table->node = node; + table->name = DN_CGen_MDToDNStr8(table_tag->first_child->first_child->string); + MD_MapInsert(cgen->arena, &cgen->table_map, MD_MapKeyStr(DN_CGen_DNToMDStr8(table->name)), table); + MD_QueuePush(cgen->first_table, cgen->last_table, table); + + for (MD_EachNode(key, table_tag->first_child)) { + DN_CGenMapNodeToEnum key_mapping = DN_CGen_MapNodeToEnumOrExit(key, + DN_CGEN_TABLE_KEY_LIST, + DN_ArrayCountU(DN_CGEN_TABLE_KEY_LIST), + "Table specified invalid key"); + switch (DN_CAST(DN_CGenTableKeyType) key_mapping.enum_val) { + case DN_CGenTableKeyType_Nil: DN_InvalidCodePath; + + case DN_CGenTableKeyType_Name: { + table->name = DN_CGen_MDToDNStr8(key->first_child->string); + } break; + + case DN_CGenTableKeyType_Type: { + MD_Node *table_type_value = key->first_child; + DN_CGenMapNodeToEnum table_type_validator = DN_CGen_MapNodeToEnumOrExit(table_type_value, + DN_CGEN_TABLE_TYPE_LIST, + DN_ArrayCountU(DN_CGEN_TABLE_TYPE_LIST), + "Table 'type' specified invalid value"); + table->type = DN_CAST(DN_CGenTableType) table_type_validator.enum_val; + + DN_Assert(table->type <= DN_CGenTableType_Count); + cgen->table_counts[table->type]++; + } break; + } + } + } + } + + // NOTE: Parse the tables ////////////////////////////////////////////////////////////////////// + DN_USize const BEGIN_COLUMN_INDEX = 1; /*Reserve 0th slot for nil-entry*/ + for (DN_CGenTable *table = cgen->first_table; table; table = table->next) { + table->column_count = BEGIN_COLUMN_INDEX; + table->headers_node = table->node->first_child; + for (MD_EachNode(column_node, table->headers_node->first_child)) + table->column_count++; + + if (table->column_count == BEGIN_COLUMN_INDEX) + continue; + + MD_Node *row_it = table->headers_node->next; + for (MD_EachNode(row_node, row_it)) + table->row_count++; + + table->rows = MD_PushArray(cgen->arena, DN_CGenTableRow, table->row_count); + table->headers = MD_PushArray(cgen->arena, DN_CGenTableHeader, table->column_count); + for (DN_USize index = 0; index < table->row_count; index++) { + DN_CGenTableRow *row = table->rows + index; + row->columns = MD_PushArray(cgen->arena, DN_CGenTableColumn, table->column_count); + } + + // NOTE: Collect table headers ///////////////////////////////////////////////////////////// + table->headers_map = MD_MapMake(cgen->arena); + DN_USize column_index = BEGIN_COLUMN_INDEX; + for (MD_EachNode(header_column, table->headers_node->first_child)) { + DN_Assert(column_index < table->column_count); + + // NOTE: Detect builtin headers and cache the index for that table ///////////////////// + for (DN_USize enum_index = 0; enum_index < DN_CGenTableHeaderType_Count; enum_index++) { + DN_Str8 decl_str8 = DN_CGen_TableHeaderTypeToDeclStr8(DN_CAST(DN_CGenTableHeaderType) enum_index); + if (decl_str8 != DN_Str8_Init(header_column->string.str, header_column->string.size)) + continue; + table->column_indexes[enum_index] = column_index; + break; + } + + MD_MapInsert(cgen->arena, &table->headers_map, MD_MapKeyStr(header_column->string), DN_CAST(void *) column_index); + table->headers[column_index++].name = header_column->string; + } + + // NOTE: Validate table headers //////////////////////////////////////////////////////////// + switch (table->type) { + case DN_CGenTableType_Nil: DN_InvalidCodePath; + case DN_CGenTableType_Count: DN_InvalidCodePath; + + case DN_CGenTableType_Data: { + } break; + + case DN_CGenTableType_CodeGenStruct: { + for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_STRUCT_HEADER_LIST) { + if (table->column_indexes[enum_val] == 0) { + DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); + DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Struct code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); + return false; + } + } + } break; + + case DN_CGenTableType_CodeGenEnum: { + for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_ENUM_HEADER_LIST) { + if (table->column_indexes[enum_val] == 0) { + DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); + DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Enum code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); + return false; + } + } + } break; + + case DN_CGenTableType_CodeGenBuiltinTypes: { + for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_BUILTIN_TYPES_HEADER_LIST) { + if (table->column_indexes[enum_val] == 0) { + DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); + DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Enum code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); + return false; + } + } + } break; + } + + // NOTE: Parse each row in table /////////////////////////////////////////////////////////// + DN_USize row_index = 0; + for (MD_EachNode(row_node, row_it)) { + column_index = BEGIN_COLUMN_INDEX; + DN_Assert(row_index < table->row_count); + + // NOTE: Parse any tags set on the row ///////////////////////////////////////////////// + DN_CGenTableRow *row = table->rows + row_index++; + for (MD_EachNode(row_tag, row_node->first_tag)) { + DN_CGenMapNodeToEnum row_mapping = DN_CGen_MapNodeToEnumOrExit(row_tag, + DN_CGEN_TABLE_ROW_TAG_LIST, + DN_ArrayCountU(DN_CGEN_TABLE_ROW_TAG_LIST), + "Table specified invalid row tag"); + DN_CGenTableRowTag *tag = MD_PushArray(cgen->arena, DN_CGenTableRowTag, 1); + tag->type = DN_CAST(DN_CGenTableRowTagType) row_mapping.enum_val; + MD_QueuePush(row->first_tag, row->last_tag, tag); + + switch (tag->type) { + case DN_CGenTableRowTagType_Nil: DN_InvalidCodePath; + + case DN_CGenTableRowTagType_CommentDivider: { + for (MD_EachNode(tag_key, row_tag->first_child)) { + DN_CGenMapNodeToEnum tag_mapping = DN_CGen_MapNodeToEnumOrExit(tag_key, + DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST, + DN_ArrayCountU(DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST), + "Table specified invalid row tag"); + switch (DN_CAST(DN_CGenTableRowTagCommentDivider) tag_mapping.enum_val) { + case DN_CGenTableRowTagCommentDivider_Nil: DN_InvalidCodePath; + case DN_CGenTableRowTagCommentDivider_Label: { + tag->comment = tag_key->first_child->string; + } break; + } + } + } break; + + case DN_CGenTableRowTagType_EmptyLine: break; + } + } + + for (MD_EachNode(column_node, row_node->first_child)) { + table->headers[column_index].longest_string = DN_Max(table->headers[column_index].longest_string, DN_CAST(int) column_node->string.size); + row->columns[column_index].string = DN_CGen_MDToDNStr8(column_node->string); + row->columns[column_index].node = column_node; + column_index++; + } + } + } + + // NOTE: Validate codegen ////////////////////////////////////////////////////////////////////// + DN_CGenTableHeaderType const CHECK_COLUMN_LINKS[] = { + DN_CGenTableHeaderType_CppName, + DN_CGenTableHeaderType_CppType, + DN_CGenTableHeaderType_CppValue, + DN_CGenTableHeaderType_CppIsPtr, + DN_CGenTableHeaderType_CppArraySize, + DN_CGenTableHeaderType_CppArraySizeField, + }; + + result = true; + for (DN_CGenTable *table = cgen->first_table; table; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + for (DN_CGenTableHeaderType check_column : CHECK_COLUMN_LINKS) { + DN_CGenTableColumn column = it.cgen_table_row->columns[table->column_indexes[check_column]]; + if (column.string.size == 0) { + // NOTE: The code generation table did not bind a code generation parameter to + // a column in the target table. We skip it. + continue; + } + + // NOTE: Check if the column to bind to exists in the target table. + if (!MD_MapLookup(&it.table->headers_map, MD_MapKeyStr(DN_CGen_DNToMDStr8(column.string)))) { + result = false; + DN_Str8 header_type_str8 = DN_CGen_TableHeaderTypeToDeclStr8(check_column); + DN_CGen_LogF(MD_MessageKind_Error, column.node, err, + "Code generation table binds '%.*s' to '%.*s', but the column '%.*s' does not exist in table '%.*s'\n" + "NOTE: If you want '%.*s' to omit the column '%.*s' you can bind to the empty string `` to skip it, otherwise, please ensure the table '%.*s' has the column '%.*s'", + DN_STR_FMT(column.string), + DN_STR_FMT(header_type_str8), + DN_STR_FMT(column.string), + DN_STR_FMT(it.table->name), + DN_STR_FMT(it.table->name), + DN_STR_FMT(header_type_str8), + DN_STR_FMT(it.table->name), + DN_STR_FMT(header_type_str8)); + } + } + } + } + + return result; +} + +DN_API DN_CGen DN_CGen_InitFilesArgV(int argc, char const **argv, DN_OSErrSink *err) +{ + DN_CGen result = {}; + result.arena = MD_ArenaAlloc(); + result.file_list = MD_MakeList(result.arena); + result.table_map = MD_MapMake(result.arena); + + bool has_error = false; + for (DN_ISize arg_index = 0; arg_index < argc; arg_index++) { + MD_String8 file_name = MD_S8CString(DN_CAST(char *) argv[arg_index]); + MD_ParseResult parse_result = MD_ParseWholeFile(result.arena, file_name); + for (MD_Message *message = parse_result.errors.first; message != 0; message = message->next) { + has_error = true; + DN_CGen_LogF(message->kind, message->node, err, "%.*s", MD_S8VArg(message->string)); + } + MD_PushNewReference(result.arena, result.file_list, parse_result.node); + } + + if (!has_error) + DN_CGen_GatherTables_(&result, err); + return result; +} + +DN_API DN_Str8 DN_CGen_TableHeaderTypeToDeclStr8(DN_CGenTableHeaderType type) +{ + DN_Str8 result = {}; + switch (type) { + case DN_CGenTableHeaderType_Name: result = DN_STR8("name"); break; + case DN_CGenTableHeaderType_Table: result = DN_STR8("table"); break; + case DN_CGenTableHeaderType_CppType: result = DN_STR8("cpp_type"); break; + case DN_CGenTableHeaderType_CppName: result = DN_STR8("cpp_name"); break; + case DN_CGenTableHeaderType_CppValue: result = DN_STR8("cpp_value"); break; + case DN_CGenTableHeaderType_CppIsPtr: result = DN_STR8("cpp_is_ptr"); break; + case DN_CGenTableHeaderType_CppOpEquals: result = DN_STR8("cpp_op_equals"); break; + case DN_CGenTableHeaderType_CppArraySize: result = DN_STR8("cpp_array_size"); break; + case DN_CGenTableHeaderType_CppArraySizeField: result = DN_STR8("cpp_array_size_field"); break; + case DN_CGenTableHeaderType_CppLabel: result = DN_STR8("cpp_label"); break; + case DN_CGenTableHeaderType_GenTypeInfo: result = DN_STR8("gen_type_info"); break; + case DN_CGenTableHeaderType_GenEnumCount: result = DN_STR8("gen_enum_count"); break; + case DN_CGenTableHeaderType_Count: result = DN_STR8("XX BAD ENUM VALUE XX"); break; + default: result = DN_STR8("XX INVALID ENUM VALUE XX"); break; + } + return result; +} + +DN_API DN_CGenMapNodeToEnum DN_CGen_MapNodeToEnumOrExit(MD_Node const *node, DN_CGenMapNodeToEnum const *valid_keys, DN_USize valid_keys_size, char const *fmt, ...) +{ + DN_CGenMapNodeToEnum result = {}; + for (DN_USize index = 0; index < valid_keys_size; index++) { + DN_CGenMapNodeToEnum const *validator = valid_keys + index; + if (DN_Str8_Init(node->string.str, node->string.size) == validator->node_string) { + result = *validator; + break; + } + } + + if (result.enum_val == 0) { + MD_CodeLoc loc = MD_CodeLocFromNode(DN_CAST(MD_Node *) node); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + va_list args; + va_start(args, fmt); + DN_Str8 user_msg = DN_Str8_InitFV(tmem.arena, fmt, args); + va_end(args); + + DN_Str8Builder builder = {}; + builder.arena = tmem.arena; + + DN_Str8Builder_AppendF(&builder, "%.*s: '%.*s' is not recognised, the supported values are ", DN_STR_FMT(user_msg), MD_S8VArg(node->string)); + for (DN_USize index = 0; index < valid_keys_size; index++) { + DN_CGenMapNodeToEnum const *validator = valid_keys + index; + DN_Str8Builder_AppendF(&builder, DN_CAST(char *) "%s'%.*s'", index ? ", " : "", DN_STR_FMT(validator->node_string)); + } + + DN_Str8 error_msg = DN_Str8Builder_Build(&builder, tmem.arena); + MD_PrintMessageFmt(stderr, loc, MD_MessageKind_Error, DN_CAST(char *) "%.*s", DN_STR_FMT(error_msg)); + DN_OS_Exit(DN_CAST(uint32_t) - 1); + } + return result; +} + +DN_API DN_USize DN_CGen_NodeChildrenCount(MD_Node const *node) +{ + DN_USize result = 0; + for (MD_EachNode(item, node->first_child)) + result++; + return result; +} + +DN_API void DN_CGen_LogF(MD_MessageKind kind, MD_Node *node, DN_OSErrSink *err, char const *fmt, ...) +{ + if (!err) + return; + + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + + MD_String8 kind_string = MD_StringFromMessageKind(kind); + MD_CodeLoc loc = MD_CodeLocFromNode(node); + DN_Str8Builder_AppendF(&builder, "" MD_FmtCodeLoc " %.*s: ", MD_CodeLocVArg(loc), MD_S8VArg(kind_string)); + + va_list args; + va_start(args, fmt); + DN_Str8Builder_AppendFV(&builder, fmt, args); + va_end(args); + + DN_Str8 msg = DN_Str8Builder_Build(&builder, tmem.arena); + DN_OS_ErrSinkAppendF(err, DN_CAST(uint32_t) - 1, "%.*s", DN_STR_FMT(msg)); +} + +DN_API bool DN_CGen_TableHasHeaders(DN_CGenTable const *table, DN_Str8 const *headers, DN_USize header_count, DN_OSErrSink *err) +{ + bool result = true; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8Builder builder = {}; + builder.arena = tmem.arena; + + for (DN_USize index = 0; index < header_count; index++) { + DN_Str8 header = headers[index]; + MD_String8 header_md = {DN_CAST(MD_u8 *) header.data, header.size}; + MD_MapSlot *slot = MD_MapLookup(DN_CAST(MD_Map *) & table->headers_map, MD_MapKeyStr(header_md)); + if (!slot) { + result = false; + DN_Str8Builder_AppendF(&builder, "%s%.*s", builder.count ? ", " : "", DN_STR_FMT(header)); + } + } + + if (!result) { + DN_Str8 missing_headers = DN_Str8Builder_Build(&builder, tmem.arena); + DN_CGen_LogF(MD_MessageKind_Error, + table->headers_node, + err, + "Table '%.*s' is missing the header(s): %.*s", + DN_STR_FMT(table->name), + DN_STR_FMT(missing_headers)); + } + + return result; +} + +DN_API DN_CGenLookupColumnAtHeader DN_CGen_LookupColumnAtHeader(DN_CGenTable *table, DN_Str8 header, DN_CGenTableRow const *row) +{ + DN_CGenLookupColumnAtHeader result = {}; + if (!table || !row) + return result; + + MD_String8 header_md = {DN_CAST(MD_u8 *) header.data, header.size}; + MD_MapSlot *slot = MD_MapLookup(&table->headers_map, MD_MapKeyStr(header_md)); + if (!slot) + return result; + + DN_USize column_index = DN_CAST(DN_USize) slot->val; + DN_Assert(column_index < table->column_count); + { + DN_USize begin = DN_CAST(uintptr_t)(table->rows); + DN_USize end = DN_CAST(uintptr_t)(table->rows + table->row_count); + DN_USize ptr = DN_CAST(uintptr_t) row; + DN_AssertF(ptr >= begin && ptr <= end, "The row to lookup does not belong to the table passed in"); + } + + result.index = column_index; + result.column = row->columns[column_index]; + result.header = table->headers[column_index]; + return result; +} + +DN_API bool DN_CGen_LookupNextTableInCodeGenTable(DN_CGen *cgen, DN_CGenTable *cgen_table, DN_CGenLookupTableIterator *it) +{ + if (!cgen_table) + return false; + + if (it->row_index >= cgen_table->row_count) + return false; + + if (cgen_table->type != DN_CGenTableType_CodeGenEnum && cgen_table->type != DN_CGenTableType_CodeGenStruct && cgen_table->type != DN_CGenTableType_CodeGenBuiltinTypes) + return false; + + // NOTE: Lookup the table in this row that we will code generate from. Not + // applicable when we are doing builtin types as the types are just put + // in-line into the code generation table itself. + it->cgen_table = cgen_table; + it->cgen_table_row = cgen_table->rows + it->row_index++; + if (cgen_table->type != DN_CGenTableType_CodeGenBuiltinTypes) { + DN_CGenTableColumn cgen_table_column = it->cgen_table_row->columns[cgen_table->column_indexes[DN_CGenTableHeaderType_Table]]; + MD_String8 key = {DN_CAST(MD_u8 *) cgen_table_column.string.data, cgen_table_column.string.size}; + MD_MapSlot *table_slot = MD_MapLookup(&cgen->table_map, MD_MapKeyStr(key)); + if (!table_slot) { + MD_CodeLoc loc = MD_CodeLocFromNode(cgen_table_column.node); + MD_PrintMessageFmt(stderr, + loc, + MD_MessageKind_Warning, + DN_CAST(char *) "Code generation table references non-existent table '%.*s'", + DN_STR_FMT(cgen_table_column.string)); + return false; + } + it->table = DN_CAST(DN_CGenTable *) table_slot->val; + } + + for (DN_USize type = 0; type < DN_CGenTableHeaderType_Count; type++) + it->cgen_table_column[type] = it->cgen_table_row->columns[cgen_table->column_indexes[type]]; + return true; +} + +DN_API bool DN_CGen_WillCodeGenTypeName(DN_CGen const *cgen, DN_Str8 name) +{ + if (!DN_Str8_HasData(name)) + return false; + + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenStruct && table->type != DN_CGenTableType_CodeGenEnum) + continue; + + for (DN_USize row_index = 0; row_index < table->row_count; row_index++) { + DN_CGenTableRow const *row = table->rows + row_index; + DN_CGenTableColumn const *column = row->columns + table->column_indexes[DN_CGenTableHeaderType_Name]; + if (column->string == name) + return true; + } + } + + return false; +} + +static void DN_CGen_EmitRowWhitespace_(DN_CGenTableRow const *row, DN_CppFile *cpp) +{ + for (DN_CGenTableRowTag *tag = row->first_tag; tag; tag = tag->next) { + switch (tag->type) { + case DN_CGenTableRowTagType_Nil: DN_InvalidCodePath; + + case DN_CGenTableRowTagType_CommentDivider: { + if (tag->comment.size <= 0) + break; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 prefix = DN_Str8_InitF(tmem.arena, "// NOTE: %.*s ", MD_S8VArg(tag->comment)); + int line_padding = DN_Max(100 - (DN_CAST(int) prefix.size + (DN_CppSpacePerIndent(cpp) * cpp->indent)), 0); + DN_CppPrint(cpp, "%.*s", DN_STR_FMT(prefix)); + for (int index = 0; index < line_padding; index++) + DN_CppAppend(cpp, "/"); + DN_CppAppend(cpp, "\n"); + } break; + + case DN_CGenTableRowTagType_EmptyLine: { + DN_CppAppend(cpp, "\n"); + } break; + } + } +} + +DN_Str8 DN_CGen_ConvertTemplatesToEmittableLiterals_(DN_Arena *arena, DN_Str8 type) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8 result = DN_Str8_TrimWhitespaceAround(type); + result = DN_Str8_Replace(result, /*find*/ DN_STR8("<"), /*replace*/ DN_STR8("_"), /*start_index*/ 0, arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_Replace(result, /*find*/ DN_STR8(">"), /*replace*/ DN_STR8(""), /*start_index*/ 0, arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimWhitespaceAround(result); + return result; +} + +DN_Str8 DN_CGen_StripQualifiersOnCppType_(DN_Arena *arena, DN_Str8 type) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8 result = DN_Str8_TrimWhitespaceAround(type); + result = DN_Str8_Replace(result, /*find*/ DN_STR8("*"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_Replace(result, /*find*/ DN_STR8("constexpr"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_Replace(result, /*find*/ DN_STR8("const"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_Replace(result, /*find*/ DN_STR8("static"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_Replace(result, /*find*/ DN_STR8(" "), /*replace*/ DN_STR8(""), /*start_index*/ 0, arena, DN_Str8EqCase_Sensitive); + result = DN_Str8_TrimWhitespaceAround(result); + return result; +} + +DN_API void DN_CGen_EmitCodeForTables(DN_CGen *cgen, DN_CGenEmit emit, DN_CppFile *cpp, DN_Str8 emit_prefix) +{ + if (emit & DN_CGenEmit_Prototypes) { + // NOTE: Generate type info enums ////////////////////////////////////////////////////////////// + DN_CppEnumBlock(cpp, "%.*sType", DN_STR_FMT(emit_prefix)) + { + DN_CppLine(cpp, "%.*sType_Nil,", DN_STR_FMT(emit_prefix)); + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 enum_name = DN_CGen_ConvertTemplatesToEmittableLiterals_(tmem.arena, it.cgen_table_column[DN_CGenTableHeaderType_Name].string); + DN_CppLine(cpp, "%.*sType_%.*s,", DN_STR_FMT(emit_prefix), DN_STR_FMT(enum_name)); + } + DN_CppLine(cpp, "%.*sType_Count,", DN_STR_FMT(emit_prefix)); + } + DN_CppNewLine(cpp); + + // NOTE: Generate structs + enums ////////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + switch (table->type) { + case DN_CGenTableType_Nil: DN_InvalidCodePath; + case DN_CGenTableType_Count: DN_InvalidCodePath; + case DN_CGenTableType_CodeGenBuiltinTypes: continue; + case DN_CGenTableType_Data: continue; + + case DN_CGenTableType_CodeGenStruct: { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + // TODO(doyle): Verify the codegen table has the headers from the table it references + int longest_type_name = 0; + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); + + DN_USize length = cpp_type.column.string.size; + DN_Str8 find_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); + if (DN_CGen_WillCodeGenTypeName(cgen, find_name)) + length += emit_prefix.size; + + longest_type_name = DN_Max(longest_type_name, DN_CAST(int) length); + } + + DN_CppStructBlock(cpp, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string)) + { + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); + DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); + if (cpp_name.column.string.size <= 0 || cpp_type.column.string.size <= 0) + continue; + + // NOTE: Generate cpp array size /////////////////////////////////// + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 array_size = {}; + if (cpp_array_size.column.string.size) + array_size = DN_Str8_InitF(tmem.arena, "[%.*s]", DN_STR_FMT(cpp_array_size.column.string)); + + // NOTE: Check if we're referencing a code generated type. If we + // are, append the `emit_prefix` + DN_Str8 emit_cpp_type = cpp_type.column.string; + { + DN_Str8 find_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, emit_cpp_type); + if (DN_CGen_WillCodeGenTypeName(cgen, find_name)) + emit_cpp_type = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(cpp_type.column.string)); + } + + int name_to_type_padding = 1 + longest_type_name - DN_CAST(int) emit_cpp_type.size; + + // NOTE: Emit decl ///////////////////////////////////////////////// + DN_CGen_EmitRowWhitespace_(row, cpp); + DN_CppLine(cpp, + "%.*s%*s%.*s%.*s;", + DN_STR_FMT(emit_cpp_type), + name_to_type_padding, + "", + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(array_size)); + } + } + DN_CppNewLine(cpp); + DN_CppNewLine(cpp); + } + + } break; + + case DN_CGenTableType_CodeGenEnum: { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_CppEnumBlock(cpp, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string)) + { + DN_USize enum_count = 0; + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + DN_CGenLookupColumnAtHeader cpp_value = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppValue].string, row); + + if (cpp_name.column.string.size <= 0) + continue; + + DN_CGen_EmitRowWhitespace_(row, cpp); + if (cpp_value.column.string.size) + DN_CppLine(cpp, + "%.*s%.*s_%.*s = %.*s,", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_value.column.string)); + else + DN_CppLine(cpp, + "%.*s%.*s_%.*s = %zu,", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), + DN_STR_FMT(cpp_name.column.string), + row_index); + enum_count++; + } + + DN_CGenTableColumn gen_enum_count_column = it.cgen_table_column[DN_CGenTableHeaderType_GenEnumCount]; + if (gen_enum_count_column.string.size) + DN_CppLine(cpp, + "%.*s%.*s_%.*s = %zu,", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), + DN_STR_FMT(gen_enum_count_column.string), + enum_count); + } + DN_CppNewLine(cpp); + } + } break; + } + } + + // NOTE: Generate enums for struct fields ////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + switch (table->type) { + case DN_CGenTableType_Nil: DN_InvalidCodePath; + case DN_CGenTableType_Count: DN_InvalidCodePath; + case DN_CGenTableType_Data: continue; + case DN_CGenTableType_CodeGenBuiltinTypes: continue; + case DN_CGenTableType_CodeGenEnum: continue; + + case DN_CGenTableType_CodeGenStruct: { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 struct_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppEnumBlock(cpp, "%.*s%.*sTypeField", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name)) + { + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + if (cpp_name.column.string.size <= 0) + continue; + DN_CGen_EmitRowWhitespace_(row, cpp); + DN_CppLine(cpp, "%.*s%.*sTypeField_%.*s,", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name), DN_STR_FMT(cpp_name.column.string)); + } + DN_CppLine(cpp, "%.*s%.*sTypeField_Count,", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name)); + } + DN_CppNewLine(cpp); + } + } break; + } + } + + // NOTE: Str8 to enum conversion /////////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenEnum) + continue; + + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppStructBlock(cpp, "%.*s%.*sStr8ToEnumResult", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppLine(cpp, "bool success;"); + DN_CppLine(cpp, "%.*s%.*s value;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + } + DN_CppNewLine(cpp); + } + } + + // NOTE: Type to DN_TypeField function + DN_CppLine(cpp, + "DN_TypeInfo const *%.*sType_Info(%.*sType type);", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(emit_prefix)); + + // NOTE: Str8 <-> Enum conversion functions + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenEnum) + continue; + + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppLine(cpp, + "%.*s%.*sStr8ToEnumResult %.*s%.*s_NameStr8ToEnum(DN_Str8 string);", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name), + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name)); + DN_CppLine(cpp, + "%.*s%.*sStr8ToEnumResult %.*s%.*s_LabelStr8ToEnum(DN_Str8 string);", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name), + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name)); + } + } + + + // NOTE: Operator == and != //////////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenStruct) + continue; + + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 cpp_op_equals = it.cgen_table_column[DN_CGenTableHeaderType_CppOpEquals].string; + if (cpp_op_equals != DN_STR8("true")) + continue; + + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppLine(cpp, + "bool operator==(%.*s%.*s const &lhs, %.*s%.*s const &rhs);", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name), + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name)); + DN_CppLine(cpp, + "bool operator!=(%.*s%.*s const &lhs, %.*s%.*s const &rhs);", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name), + DN_STR_FMT(emit_prefix), + DN_STR_FMT(type_name)); + } + } + } + + if (emit & DN_CGenEmit_Implementation) { + // NOTE: Generate type info //////////////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + if (table->type == DN_CGenTableType_CodeGenBuiltinTypes) + continue; + + DN_Str8 struct_or_enum_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppBlock(cpp, ";\n\n", "DN_TypeField const g_%.*s%.*s_type_fields[] =", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_or_enum_name)) + { + if (table->type == DN_CGenTableType_CodeGenStruct) { + // NOTE: Construct the cpp type string first. We will prepend `emit_prefix` + // for types that are declared in the same mdesk file. We will also + // calculate the longest type name that we will generate for whitespace + // padding purposes. + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_USize longest_cpp_type_name = 0; + auto cpp_type_list = DN_SArray_Init(tmem.arena, it.table->row_count, DN_ZeroMem_Yes); + + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); + + // NOTE: CHeck the length of the string after turning it into emittable code + DN_Str8 cpp_type_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); + if (DN_CGen_WillCodeGenTypeName(cgen, cpp_type_name)) + cpp_type_name = DN_Str8_InitFFromTLS("%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(cpp_type_name)); + + DN_Str8 cpp_type_name_no_templates = DN_CGen_ConvertTemplatesToEmittableLiterals_(tmem.arena, cpp_type_name); + longest_cpp_type_name = DN_Max(longest_cpp_type_name, cpp_type_name_no_templates.size); + + DN_SArray_Add(&cpp_type_list, cpp_type_name); + } + + // NOTE: Iterate each row and emit the C++ declarations //////////////////// + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); + DN_CGenLookupColumnAtHeader cpp_is_ptr = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppIsPtr].string, row); + DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); + DN_CGenLookupColumnAtHeader cpp_array_size_field = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySizeField].string, row); + DN_CGenLookupColumnAtHeader cpp_label = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppLabel].string, row); + + bool cpp_is_ptr_b32 = cpp_is_ptr.column.string == DN_STR8("true"); + DN_Str8 cpp_array_size_str8 = DN_Str8_HasData(cpp_array_size.column.string) ? cpp_array_size.column.string : DN_STR8("0"); + + DN_CGenTableColumn struct_name = it.cgen_table_row->columns[table->column_indexes[DN_CGenTableHeaderType_Name]]; + DN_Str8 cpp_array_size_field_str8 = DN_STR8("NULL"); + if (cpp_array_size_field.column.string.size) { + // TODO(doyle): Check that array_size_field references a valid field in the table + // NOTE: We use a raw index for the reference because the struct might + // not have type info being generated so we can't rely on the enum. + DN_USize index_the_field_references = 0; + for (DN_USize sub_row_index = 0; sub_row_index < it.table->row_count; sub_row_index++) { + DN_CGenTableRow const *sub_row = it.table->rows + sub_row_index; + DN_CGenTableColumn sub_cpp_name = sub_row->columns[cpp_name.index]; + if (sub_cpp_name.string == cpp_array_size_field.column.string) + index_the_field_references = sub_row_index; + } + cpp_array_size_field_str8 = + DN_Str8_InitFFromTLS("&g_%.*s%.*s_type_fields[%zu]", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(struct_name.string), + index_the_field_references); + } + + DN_Str8 cpp_type_name = cpp_type_list.data[row_index]; + DN_Str8 orig_cpp_type = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); + DN_Str8 orig_cpp_type_no_templates = DN_CGen_ConvertTemplatesToEmittableLiterals_(tmem.arena, orig_cpp_type); + + DN_USize cpp_name_padding = 1 + it.table->headers[cpp_name.index].longest_string - cpp_name.column.string.size; + DN_USize cpp_type_padding = 1 + longest_cpp_type_name - cpp_type_name.size; + + DN_Str8 cpp_type_enum = DN_Str8_InitFFromTLS("%.*sType_%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(orig_cpp_type_no_templates)); + DN_USize cpp_type_enum_padding = cpp_type_padding + (orig_cpp_type.size - cpp_type_name.size); + + DN_Str8 cpp_label_str8 = cpp_name.column.string; + DN_USize cpp_label_str8_padding = cpp_name_padding; + if (cpp_label.column.string.size) { + cpp_label_str8 = cpp_label.column.string; + cpp_label_str8_padding = 1 + it.table->headers[cpp_label.index].longest_string - cpp_label.column.string.size; + } + + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + + // NOTE: row + DN_Str8Builder_AppendF(&builder, "{%2d, ", row_index); + + // NOTE: name + DN_Str8Builder_AppendF(&builder, + "DN_STR8(\"%.*s\"),%*s", + DN_STR_FMT(cpp_name.column.string), + cpp_name_padding, + ""); + + // NOTE: label + DN_Str8Builder_AppendF(&builder, + "DN_STR8(\"%.*s\"),%*s", + DN_STR_FMT(cpp_label_str8), + cpp_label_str8_padding, + ""); + + // NOTE: value + DN_Str8Builder_AppendF(&builder, + "/*value*/ 0, "); + + // NOTE: offsetof(a, b) + DN_Str8Builder_AppendF(&builder, + "offsetof(%.*s%.*s, %.*s),%*s", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(struct_or_enum_name), + DN_STR_FMT(cpp_name.column.string), + cpp_name_padding, + ""); + + // NOTE: sizeof(a->b) + DN_Str8Builder_AppendF(&builder, + "sizeof(((%.*s%.*s*)0)->%.*s),%*s", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(struct_or_enum_name), + DN_STR_FMT(cpp_name.column.string), + cpp_name_padding, + ""); + + // NOTE: alignof(a) + if (cpp_type_name == DN_STR8("void")) { + DN_Str8 proxy_type = DN_STR8("char"); + DN_USize proxy_type_padding = 1 + longest_cpp_type_name - proxy_type.size; + DN_Str8Builder_AppendF(&builder, "alignof(%.*s),%*s", DN_STR_FMT(proxy_type), proxy_type_padding, ""); + } else { + DN_Str8Builder_AppendF(&builder, + "alignof(%.*s),%*s", + DN_STR_FMT(cpp_type_name), + cpp_type_padding, + ""); + } + + // NOTE: Type string + DN_Str8Builder_AppendF(&builder, + "DN_STR8(\"%.*s\"),%*s", + DN_STR_FMT(cpp_type_name), + cpp_type_padding, + ""); + + // NOTE: Type as enum + DN_Str8Builder_AppendF(&builder, + "%.*s,%*s", + DN_STR_FMT(cpp_type_enum), + cpp_type_enum_padding, + ""); + + DN_Str8Builder_AppendF(&builder, + "/*is_pointer*/ %s,%s /*array_size*/ %.*s, /*array_size_field*/ %.*s},", + cpp_is_ptr_b32 ? "true" : "false", + cpp_is_ptr_b32 ? " " : "", + DN_STR_FMT(cpp_array_size_str8), + DN_STR_FMT(cpp_array_size_field_str8)); + + DN_Str8 line = DN_Str8Builder_Build(&builder, tmem.arena); + DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); + } + } else { + DN_Assert(table->type == DN_CGenTableType_CodeGenEnum); + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + DN_CGenLookupColumnAtHeader cpp_value = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppValue].string, row); + DN_CGenLookupColumnAtHeader cpp_label = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppLabel].string, row); + if (cpp_name.column.string.size <= 0) + continue; + + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_USize cpp_name_padding = 1 + it.table->headers[cpp_name.index].longest_string - cpp_name.column.string.size; + DN_Str8 cpp_value_str8 = DN_Str8_HasData(cpp_value.column.string) ? cpp_value.column.string : DN_Str8_InitFFromTLS("%zu", row_index); + DN_Str8 cpp_type_enum = DN_Str8_InitFFromTLS("%.*sType_%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_or_enum_name)); + + DN_Str8 cpp_label_str8 = cpp_name.column.string; + DN_USize cpp_label_str8_padding = cpp_name_padding; + if (cpp_label.column.string.size) { + cpp_label_str8 = cpp_label.column.string; + cpp_label_str8_padding = 1 + it.table->headers[cpp_label.index].longest_string - cpp_label.column.string.size; + } + + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + // NOTE: row + DN_Str8Builder_AppendF(&builder, "{%2d, ", row_index); + + // NOTE: name + DN_Str8Builder_AppendF(&builder, + "DN_STR8(\"%.*s\"),%*s", + DN_STR_FMT(cpp_name.column.string), + cpp_name_padding, + ""); + + // NOTE: label + DN_Str8Builder_AppendF(&builder, + "DN_STR8(\"%.*s\"),%*s", + DN_STR_FMT(cpp_label_str8), + cpp_label_str8_padding, + ""); + + // NOTE: value + DN_Str8Builder_AppendF(&builder, "/*value*/ %.*s, ", DN_STR_FMT(cpp_value_str8)); + + // NOTE: offsetof(a, b) + DN_Str8Builder_AppendF(&builder, "/*offsetof*/ 0, "); + + // NOTE: sizeof(a->b) + DN_Str8Builder_AppendF(&builder, + "sizeof(%.*s%.*s), ", + DN_STR_FMT(emit_prefix), + DN_STR_FMT(struct_or_enum_name)); + + // NOTE: alignof(a->b) + DN_Str8Builder_AppendF(&builder, "alignof(%.*s%.*s), ", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_or_enum_name)); + + // TODO: Type string + DN_Str8Builder_AppendF(&builder, "DN_STR8(\"\"), "); + + // NOTE: Type as enum + DN_Str8Builder_AppendF(&builder, "%.*s, ", DN_STR_FMT(cpp_type_enum)); + + DN_Str8Builder_AppendF(&builder, "/*is_pointer*/ false, "); + DN_Str8Builder_AppendF(&builder, "/*array_size*/ 0, "); + DN_Str8Builder_AppendF(&builder, "/*array_size_field*/ NULL},"); + + DN_Str8 line = DN_Str8Builder_BuildFromTLS(&builder); + DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); + } + } + } + } + } + + int longest_name_across_all_tables = 0; + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) + type_name = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + + longest_name_across_all_tables = DN_Max(longest_name_across_all_tables, DN_CAST(int) type_name.size); + } + } + + DN_CppBlock(cpp, ";\n\n", "DN_TypeInfo const g_%.*stypes[] =", DN_STR_FMT(emit_prefix)) + { + DN_CppLine(cpp, "{DN_STR8(\"\"),%*sDN_TypeKind_Nil, 0, /*fields*/ NULL, /*count*/ 0},", 1 + longest_name_across_all_tables, ""); + + DN_USize longest_type_name = 0; + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) + type_name = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + longest_type_name = DN_Max(longest_type_name, type_name.size); + } + } + + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) + type_name = DN_Str8_InitFFromTLS("%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + + int name_padding = 1 + longest_name_across_all_tables - DN_CAST(int) type_name.size; + DN_Str8 type_info_kind = {}; + char const *type_info_kind_padding = ""; + if (table->type == DN_CGenTableType_CodeGenEnum) { + type_info_kind = DN_STR8("DN_TypeKind_Enum"); + type_info_kind_padding = " "; + } else if (table->type == DN_CGenTableType_CodeGenStruct) { + type_info_kind = DN_STR8("DN_TypeKind_Struct"); + } else { + DN_Assert(table->type == DN_CGenTableType_CodeGenBuiltinTypes); + type_info_kind = DN_STR8("DN_TypeKind_Basic"); + type_info_kind_padding = " "; + } + + DN_Str8 fields_count = {}; + if (table->type == DN_CGenTableType_CodeGenBuiltinTypes) { + fields_count = DN_STR8("0"); + } else { + DN_Assert(table->type == DN_CGenTableType_CodeGenStruct || table->type == DN_CGenTableType_CodeGenEnum); + int fields_count_int = 0; + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + fields_count_int += DN_Str8_HasData(cpp_name.column.string); + } + fields_count = DN_Str8_InitFFromTLS("%d", fields_count_int); + } + + DN_Str8 fields = DN_STR8("NULL"); + int fields_padding = 1; + if (table->type != DN_CGenTableType_CodeGenBuiltinTypes) { + fields_padding = name_padding; + fields = DN_Str8_InitF(tmem.arena, "g_%.*s_type_fields", DN_STR_FMT(type_name)); + } + + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + + // NOTE: name + DN_Str8Builder_AppendF(&builder, "{DN_STR8(\"%.*s\"),%*s", DN_STR_FMT(type_name), name_padding, ""); + + // NOTE: DN_TypeKind_{Nil|Basic|Enum|Struct} + DN_Str8Builder_AppendF(&builder, "%.*s,%s", DN_STR_FMT(type_info_kind), type_info_kind_padding); + + // NOTE: sizeof(T) + if (type_name == DN_STR8("void")) + DN_Str8Builder_AppendF(&builder, "0,%*s", name_padding, ""); + else + DN_Str8Builder_AppendF(&builder, "sizeof(%.*s),%*s", DN_STR_FMT(type_name), name_padding, ""); + + // NOTE: Pointer to DN_TypeField[] + DN_Str8Builder_AppendF(&builder, "/*fields*/ %.*s,%*s", DN_STR_FMT(fields), fields_padding, ""); + + // NOTE: DN_TypeField length + DN_Str8Builder_AppendF(&builder, "/*count*/ %.*s},", DN_STR_FMT(fields_count)); + + DN_Str8 line = DN_Str8Builder_BuildFromTLS(&builder); + DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); + } + } + } + + // NOTE: Type to DN_TypeField function + DN_CppFuncBlock(cpp, "DN_TypeInfo const *%.*sType_Info(%.*sType type)", DN_STR_FMT(emit_prefix), DN_STR_FMT(emit_prefix)) + { + DN_CppLine(cpp, "DN_TypeInfo const *result = g_%.*stypes + 0;", DN_STR_FMT(emit_prefix)); + DN_CppSwitchBlock(cpp, "type") + { + DN_CppLine(cpp, "case %.*sType_Nil: break;", DN_STR_FMT(emit_prefix)); + DN_CppLine(cpp, "case %.*sType_Count: break;", DN_STR_FMT(emit_prefix)); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 enum_name = DN_CGen_ConvertTemplatesToEmittableLiterals_(tmem.arena, it.cgen_table_column[DN_CGenTableHeaderType_Name].string); + DN_Str8 full_enum_name = DN_Str8_InitF(tmem.arena, "%.*sType_%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(enum_name)); + + // NOTE: Builtin types don't have type info + DN_Str8 full_variable_name = DN_Str8_InitF(tmem.arena, "g_%.*s%.*s_type_fields", DN_STR_FMT(emit_prefix), DN_STR_FMT(enum_name)); + if (it.cgen_table->type == DN_CGenTableType_CodeGenBuiltinTypes) + full_variable_name = DN_STR8("nullptr"); + + DN_CppLine(cpp, "case %.*s: result = g_%.*stypes + %.*s; break;", DN_STR_FMT(full_enum_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(full_enum_name)); + } + } + } + DN_CppLine(cpp, "return result;"); + } + + // NOTE: Str8 to enum conversion //////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenEnum) + continue; + + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + + DN_CppFuncBlock(cpp, "%.*s%.*sStr8ToEnumResult %.*s%.*s_NameStr8ToEnum(DN_Str8 string)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppLine(cpp, "%.*s%.*sStr8ToEnumResult result = {};", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + DN_CppForBlock(cpp, "DN_USize index = 0; !result.success && index < DN_ArrayCountU(g_%.*s%.*s_type_fields); index++", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppIfChain(cpp) + { + DN_CppLine(cpp, "DN_TypeField field = g_%.*s%.*s_type_fields[index];", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + DN_CppIfOrElseIfBlock(cpp, "DN_Str8_EqInsensitive(string, field.name)") + { + DN_CppLine(cpp, "result.success = true;"); + DN_CppLine(cpp, "result.value = (%.*s%.*s)index;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + } + } + } + DN_CppLine(cpp, "return result;"); + } + DN_CppNewLine(cpp); + + DN_CppFuncBlock(cpp, "%.*s%.*sStr8ToEnumResult %.*s%.*s_LabelStr8ToEnum(DN_Str8 string)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppLine(cpp, "%.*s%.*sStr8ToEnumResult result = {};", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + DN_CppForBlock(cpp, "DN_USize index = 0; !result.success && index < DN_ArrayCountU(g_%.*s%.*s_type_fields); index++", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppIfChain(cpp) + { + DN_CppLine(cpp, "DN_TypeField field = g_%.*s%.*s_type_fields[index];", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + DN_CppIfOrElseIfBlock(cpp, "DN_Str8_EqInsensitive(string, field.label)") + { + DN_CppLine(cpp, "result.success = true;"); + DN_CppLine(cpp, "result.value = (%.*s%.*s)index;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); + } + } + } + DN_CppLine(cpp, "return result;"); + } + DN_CppNewLine(cpp); + } + } + + // NOTE: Operator == and != //////////////////////////////////////////////////////////////// + for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { + if (table->type != DN_CGenTableType_CodeGenStruct) + continue; + + for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { + DN_Str8 cpp_op_equals = it.cgen_table_column[DN_CGenTableHeaderType_CppOpEquals].string; + if (cpp_op_equals != DN_STR8("true")) + continue; + + DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; + DN_CppFuncBlock(cpp, "bool operator==(%.*s%.*s const &lhs, %.*s%.*s const &rhs)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { + DN_CGenTableRow const *row = it.table->rows + row_index; + DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); + DN_CGenLookupColumnAtHeader cpp_is_ptr = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppIsPtr].string, row); + DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); + DN_CGenLookupColumnAtHeader cpp_array_size_field = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySizeField].string, row); + + // TODO(doyle): Check if we're an integral type or not to double check if we + // can use memcmp or operator== + if (DN_Str8_HasData(cpp_array_size_field.column.string)) { + DN_CppIfChain(cpp) + { + DN_CppIfOrElseIfBlock(cpp, + "lhs.%.*s != rhs.%.*s", + DN_STR_FMT(cpp_array_size_field.column.string), + DN_STR_FMT(cpp_array_size_field.column.string)) + { + DN_CppLine(cpp, "return false;"); + } + } + DN_CppIfChain(cpp) + { + DN_CppIfOrElseIfBlock(cpp, + "DN_Memcmp(lhs.%.*s, rhs.%.*s, lhs.%.*s) != 0", + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_array_size_field.column.string)) + { + DN_CppLine(cpp, "return false;"); + } + } + } else if (DN_Str8_HasData(cpp_array_size.column.string)) { + DN_CppIfChain(cpp) + { + DN_CppIfOrElseIfBlock(cpp, + "DN_Memcmp(lhs.%.*s, rhs.%.*s, %.*s) != 0", + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_array_size.column.string)) + { + DN_CppLine(cpp, "return false;"); + } + } + } else if (cpp_is_ptr.column.string == DN_STR8("true")) { + DN_CppIfChain(cpp) + { + DN_CppIfOrElseIfBlock(cpp, + "*lhs.%.*s != *rhs.%.*s", + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_name.column.string)) + { + DN_CppLine(cpp, "return false;"); + } + } + } else { + DN_CppIfChain(cpp) + { + DN_CppIfOrElseIfBlock(cpp, + "lhs.%.*s != rhs.%.*s", + DN_STR_FMT(cpp_name.column.string), + DN_STR_FMT(cpp_name.column.string)) + { + DN_CppLine(cpp, "return false;"); + } + } + } + } + DN_CppLine(cpp, "return true;"); + } + DN_CppNewLine(cpp); + + DN_CppFuncBlock(cpp, "bool operator!=(%.*s%.*s const &lhs, %.*s%.*s const &rhs)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) + { + DN_CppLine(cpp, "bool result = !(lhs == rhs);"); + DN_CppLine(cpp, "return result;"); + } + DN_CppNewLine(cpp); + } + } + } +} diff --git a/Extra/dn_cgen.h b/Extra/dn_cgen.h new file mode 100644 index 0000000..0b1e7c7 --- /dev/null +++ b/Extra/dn_cgen.h @@ -0,0 +1,200 @@ +#if !defined(DN_CGEN_H) +#define DN_CGEN_H + +#if !defined(DN_NO_METADESK) + #if !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS + #define DN_UNDO_CRT_SECURE_NO_WARNINGS + #endif + + // NOTE: Metadesk does not have the header for 'size_t' + #if defined(DN_COMPILER_GCC) + #include + #endif + + #define MD_DEFAULT_SPRINTF 0 + #define MD_IMPL_Vsnprintf DN_VSNPrintF + #include "../External/metadesk/md.h" + #if defined(DN_UNDO_CRT_SECURE_NO_WARNINGS) + #undef _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#if !defined(DN_CPP_FILE_H) + #error dn_cpp_file.h must be included before this +#endif + +#if defined(DN_PLATFORM_WINDOWS) && !defined(DN_NO_WINDOWS_H_REPLACEMENT_HEADER) + #error DN replacement header must be disabled with DN_NO_WINDOWS_H_REPLACEMENT_HEADER since Metadesk includes +#endif + +#if !defined(MD_H) +#error Metadesk 'md.h' must be included before 'dn_cgen.h' +#endif + +#if !defined(DN_BASE_INC_H) + #error dn_base_inc.h must be included before this +#endif + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$$$$$$$\ $$\ $$\ +// $$ __$$\ $$ __$$\ $$ _____|$$$\ $$ | +// $$ / \__|$$ / \__|$$ | $$$$\ $$ | +// $$ | $$ |$$$$\ $$$$$\ $$ $$\$$ | +// $$ | $$ |\_$$ |$$ __| $$ \$$$$ | +// $$ | $$\ $$ | $$ |$$ | $$ |\$$$ | +// \$$$$$$ |\$$$$$$ |$$$$$$$$\ $$ | \$$ | +// \______/ \______/ \________|\__| \__| +// +// dn_cgen.h -- C/C++ code generation from table data in Metadesk files +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +enum DN_CGenTableKeyType +{ + DN_CGenTableKeyType_Nil, + DN_CGenTableKeyType_Name, + DN_CGenTableKeyType_Type, +}; + +enum DN_CGenTableType +{ + DN_CGenTableType_Nil, + DN_CGenTableType_Data, + DN_CGenTableType_CodeGenBuiltinTypes, + DN_CGenTableType_CodeGenStruct, + DN_CGenTableType_CodeGenEnum, + DN_CGenTableType_Count, +}; + +enum DN_CGenTableRowTagType +{ + DN_CGenTableRowTagType_Nil, + DN_CGenTableRowTagType_CommentDivider, + DN_CGenTableRowTagType_EmptyLine, +}; + +enum DN_CGenTableRowTagCommentDivider +{ + DN_CGenTableRowTagCommentDivider_Nil, + DN_CGenTableRowTagCommentDivider_Label, +}; + +enum DN_CGenTableHeaderType +{ + DN_CGenTableHeaderType_Name, + DN_CGenTableHeaderType_Table, + DN_CGenTableHeaderType_CppType, + DN_CGenTableHeaderType_CppName, + DN_CGenTableHeaderType_CppValue, + DN_CGenTableHeaderType_CppIsPtr, + DN_CGenTableHeaderType_CppOpEquals, + DN_CGenTableHeaderType_CppArraySize, + DN_CGenTableHeaderType_CppArraySizeField, + DN_CGenTableHeaderType_CppLabel, + DN_CGenTableHeaderType_GenTypeInfo, + DN_CGenTableHeaderType_GenEnumCount, + DN_CGenTableHeaderType_Count, +}; + +struct DN_CGenTableHeader +{ + MD_String8 name; + int longest_string; +}; + +struct DN_CGenTableRowTag +{ + DN_CGenTableRowTagType type; + MD_String8 comment; + DN_CGenTableRowTag *next; +}; + +struct DN_CGenTableColumn +{ + MD_Node *node; + DN_Str8 string; +}; + +struct DN_CGenTableRow +{ + DN_CGenTableRowTag *first_tag; + DN_CGenTableRowTag *last_tag; + DN_CGenTableColumn *columns; +}; + +struct DN_CGenTable +{ + DN_CGenTableType type; + DN_Str8 name; + MD_Map headers_map; + DN_CGenTableHeader *headers; + DN_CGenTableRow *rows; + size_t column_count; + size_t row_count; + + MD_Node *node; + MD_Node *headers_node; + DN_USize column_indexes[DN_CGenTableHeaderType_Count]; + DN_CGenTable *next; +}; + +struct DN_CGen +{ + MD_Arena *arena; + MD_Node *file_list; + MD_Map table_map; + DN_CGenTable *first_table; + DN_CGenTable *last_table; + DN_USize table_counts[DN_CGenTableType_Count]; +}; + +struct DN_CGenMapNodeToEnum +{ + uint32_t enum_val; + DN_Str8 node_string; +}; + +struct DN_CGenLookupTableIterator +{ + DN_CGenTable *cgen_table; + DN_CGenTableRow *cgen_table_row; + DN_CGenTableColumn cgen_table_column[DN_CGenTableHeaderType_Count]; + DN_CGenTable *table; + DN_USize row_index; +}; + +struct DN_CGenLookupColumnAtHeader +{ + DN_USize index; + DN_CGenTableHeader header; + DN_CGenTableColumn column; +}; + +enum DN_CGenEmit +{ + DN_CGenEmit_Prototypes = 1 << 0, + DN_CGenEmit_Implementation = 1 << 1, +}; + +#define DN_CGen_MDToDNStr8(str8) DN_Str8_Init((str8).str, (str8).size) +#define DN_CGen_DNToMDStr8(str8) \ + { \ + DN_CAST(MD_u8 *) \ + (str8).data, \ + (str8).size \ + } + +DN_API DN_CGen DN_CGen_InitFilesArgV(int argc, char const **argv, DN_OSErrSink *err); +DN_API DN_Str8 DN_CGen_TableHeaderTypeToDeclStr8(DN_CGenTableHeaderType type); +DN_API DN_CGenMapNodeToEnum DN_CGen_MapNodeToEnumOrExit(MD_Node const *node, DN_CGenMapNodeToEnum const *valid_keys, DN_USize valid_keys_size, char const *fmt, ...); +DN_API DN_USize DN_CGen_NodeChildrenCount(MD_Node const *node); +DN_API void DN_CGen_LogF(MD_MessageKind kind, MD_Node *node, DN_OSErrSink *err, char const *fmt, ...); +DN_API bool DN_CGen_TableHasHeaders(DN_CGenTable const *table, DN_Str8 const *headers, DN_USize header_count, DN_OSErrSink *err); +DN_API DN_CGenLookupColumnAtHeader DN_CGen_LookupColumnAtHeader(DN_CGenTable *table, DN_Str8 header, DN_CGenTableRow const *row); +DN_API bool DN_CGen_LookupNextTableInCodeGenTable(DN_CGen *cgen, DN_CGenTable *cgen_table, DN_CGenLookupTableIterator *it); +DN_API void DN_CGen_EmitCodeForTables(DN_CGen *cgen, DN_CGenEmit emit, DN_CppFile *cpp, DN_Str8 emit_prefix); +#endif // DN_CGEN_H diff --git a/Extra/dn_csv.cpp b/Extra/dn_csv.cpp new file mode 100644 index 0000000..aece9d2 --- /dev/null +++ b/Extra/dn_csv.cpp @@ -0,0 +1,288 @@ +#include "dn_csv.h" + +static DN_CSVTokeniser DN_CSV_TokeniserInit(DN_Str8 string, char delimiter) +{ + DN_CSVTokeniser result = {}; + result.string = string; + result.delimiter = delimiter; + return result; +} + +static bool DN_CSV_TokeniserValid(DN_CSVTokeniser *tokeniser) +{ + bool result = tokeniser && !tokeniser->bad; + return result; +} + +static bool DN_CSV_TokeniserNextRow(DN_CSVTokeniser *tokeniser) +{ + bool result = false; + if (DN_CSV_TokeniserValid(tokeniser) && DN_Str8_HasData(tokeniser->string)) { + // NOTE: First time querying row iterator is nil, let tokeniser advance + if (tokeniser->it) { + // NOTE: Only advance the tokeniser if we're at the end of the line and + // there's more to tokenise. + char const *end = tokeniser->string.data + tokeniser->string.size; + if (tokeniser->it != end && tokeniser->end_of_line) { + tokeniser->end_of_line = false; + result = true; + } + } + } + + return result; +} + +static DN_Str8 DN_CSV_TokeniserNextField(DN_CSVTokeniser *tokeniser) +{ + DN_Str8 result = {}; + if (!DN_CSV_TokeniserValid(tokeniser)) + return result; + + if (!DN_Str8_HasData(tokeniser->string)) { + tokeniser->bad = true; + return result; + } + + // NOTE: First time tokeniser is invoked with a string, set up initial state. + char const *string_end = tokeniser->string.data + tokeniser->string.size; + if (!tokeniser->it) { + tokeniser->it = tokeniser->string.data; + // NOTE: Skip any leading new lines + while (tokeniser->it[0] == '\n' || tokeniser->it[0] == '\r') + if (++tokeniser->it == string_end) + break; + } + + // NOTE: Tokeniser pointing at end, no more valid data to parse. + if (tokeniser->it == string_end) + return result; + + // NOTE: Scan forward until the next control character. + // 1. '"' Double quoted field, extract everything between the quotes. + // 2. tokeniser->delimiter End of the field, extract everything leading up to the delimiter. + // 3. '\n' Last field in record, extract everything leading up the the new line. + char const *begin = tokeniser->it; + while (tokeniser->it != string_end && (tokeniser->it[0] != '"' && + tokeniser->it[0] != tokeniser->delimiter && + tokeniser->it[0] != '\n')) + tokeniser->it++; + + bool quoted_field = (tokeniser->it != string_end) && tokeniser->it[0] == '"'; + if (quoted_field) { + begin = ++tokeniser->it; // Begin after the quote + + // NOTE: Scan forward until the next '"' which marks the end + // of the field unless it is escaped by another '"'. + find_next_quote: + while (tokeniser->it != string_end && tokeniser->it[0] != '"') + tokeniser->it++; + + // NOTE: If we encounter a '"' right after, the quotes were escaped + // and we need to skip to the next instance of a '"'. + if (tokeniser->it != string_end && tokeniser->it + 1 != string_end && tokeniser->it[1] == '"') { + tokeniser->it += 2; + goto find_next_quote; + } + } + + // NOTE: Mark the end of the field + char const *end = tokeniser->it; + tokeniser->end_of_line = tokeniser->it == string_end || end[0] == '\n'; + + // NOTE: In files with \r\n style new lines ensure that we don't include + // the \r byte in the CSV field we produce. + if (end != string_end && end[0] == '\n') { + DN_Assert((uintptr_t)(end - 1) > (uintptr_t)tokeniser->string.data && + "Internal error: The string iterator is pointing behind the start of the string we're reading"); + if (end[-1] == '\r') + end = end - 1; + } + + // NOTE: Quoted fields may have whitespace after the closing quote, we skip + // until we reach the field terminator. + if (quoted_field) + while (tokeniser->it != string_end && (tokeniser->it[0] != tokeniser->delimiter && + tokeniser->it[0] != '\n')) + tokeniser->it++; + + // NOTE: Advance the tokeniser past the field terminator. + if (tokeniser->it != string_end) + tokeniser->it++; + + // NOTE: Generate the record + result.data = DN_CAST(char *) begin; + result.size = DN_CAST(int)(end - begin); + return result; +} + +static DN_Str8 DN_CSV_TokeniserNextColumn(DN_CSVTokeniser *tokeniser) +{ + DN_Str8 result = {}; + if (!DN_CSV_TokeniserValid(tokeniser)) + return result; + + // NOTE: End of line, the user must explicitly advance to the next row + if (tokeniser->end_of_line) + return result; + + // NOTE: Advance tokeniser to the next field in the row + result = DN_CSV_TokeniserNextField(tokeniser); + return result; +} + +static void DN_CSV_TokeniserSkipLine(DN_CSVTokeniser *tokeniser) +{ + while (DN_CSV_TokeniserValid(tokeniser) && !tokeniser->end_of_line) + DN_CSV_TokeniserNextColumn(tokeniser); + DN_CSV_TokeniserNextRow(tokeniser); +} + +static int DN_CSV_TokeniserNextN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator) +{ + if (!DN_CSV_TokeniserValid(tokeniser) || !fields || fields_size <= 0) + return 0; + + int result = 0; + for (; result < fields_size; result++) { + fields[result] = column_iterator ? DN_CSV_TokeniserNextColumn(tokeniser) : DN_CSV_TokeniserNextField(tokeniser); + if (!DN_CSV_TokeniserValid(tokeniser) || !DN_Str8_HasData(fields[result])) + break; + } + + return result; +} + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4505) // 'x': unreferenced function with internal linkage has been removed +static int DN_CSV_TokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) +{ + int result = DN_CSV_TokeniserNextN(tokeniser, fields, fields_size, true /*column_iterator*/); + return result; +} + +static int DN_CSV_TokeniserNextFieldN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size) +{ + int result = DN_CSV_TokeniserNextN(tokeniser, fields, fields_size, false /*column_iterator*/); + return result; +} + +static void DN_CSV_TokeniserSkipLineN(DN_CSVTokeniser *tokeniser, int count) +{ + for (int i = 0; i < count && DN_CSV_TokeniserValid(tokeniser); i++) + DN_CSV_TokeniserSkipLine(tokeniser); +} + +static void DN_CSV_PackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser); + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(csv_value, 0); + DN_Assert(to_u64.success); + *value = to_u64.value; + } else { + DN_Str8Builder_AppendF(&pack->write_builder, "%s%" PRIu64, pack->write_column++ ? "," : "", *value); + } +} + +static void DN_CSV_PackI64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser); + DN_Str8ToI64Result to_i64 = DN_Str8_ToI64(csv_value, 0); + DN_Assert(to_i64.success); + *value = to_i64.value; + } else { + DN_Str8Builder_AppendF(&pack->write_builder, "%s%" PRIu64, pack->write_column++ ? "," : "", *value); + } +} + +static void DN_CSV_PackI32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value) +{ + DN_I64 u64 = *value; + DN_CSV_PackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI32(u64); +} + +static void DN_CSV_PackI16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value) +{ + DN_I64 u64 = *value; + DN_CSV_PackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI16(u64); +} + +static void DN_CSV_PackI8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value) +{ + DN_I64 u64 = *value; + DN_CSV_PackI64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastI64ToI8(u64); +} + + +static void DN_CSV_PackU32(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value) +{ + DN_U64 u64 = *value; + DN_CSV_PackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastU64ToU32(u64); +} + +static void DN_CSV_PackU16(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value) +{ + DN_U64 u64 = *value; + DN_CSV_PackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = DN_SaturateCastU64ToU16(u64); +} + +static void DN_CSV_PackBoolAsU64(DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value) +{ + DN_U64 u64 = *value; + DN_CSV_PackU64(pack, serialise, &u64); + if (serialise == DN_CSVSerialise_Read) + *value = u64 ? 1 : 0; +} + +static void DN_CSV_PackStr8(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser); + *str8 = DN_Str8_Copy(arena, csv_value); + } else { + DN_Str8Builder_AppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_STR_FMT(*str8)); + } +} + +static void DN_CSV_PackBuffer(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size) +{ + if (serialise == DN_CSVSerialise_Read) { + DN_Str8 csv_value = DN_CSV_TokeniserNextColumn(&pack->read_tokeniser); + *size = DN_Min(*size, csv_value.size); + DN_Memcpy(dest, csv_value.data, *size); + } else { + DN_Str8Builder_AppendF(&pack->write_builder, "%s%.*s", pack->write_column++ ? "," : "", DN_CAST(int)(*size), dest); + } +} + +static void DN_CSV_PackBufferWithMax(DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max) +{ + if (serialise == DN_CSVSerialise_Read) + *size = max; + DN_CSV_PackBuffer(pack, serialise, dest, size); +} + +static bool DN_CSV_PackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise) +{ + bool result = true; + if (serialise == DN_CSVSerialise_Read) { + result = DN_CSV_TokeniserNextRow(&pack->read_tokeniser); + } else { + pack->write_column = 0; + result = DN_Str8Builder_AppendRef(&pack->write_builder, DN_STR8("\n")); + } + return result; +} +DN_MSVC_WARNING_POP diff --git a/Extra/dn_csv.h b/Extra/dn_csv.h new file mode 100644 index 0000000..73b0813 --- /dev/null +++ b/Extra/dn_csv.h @@ -0,0 +1,26 @@ +#if !defined(DN_CSV_H) +#define DN_CSV_H + +enum DN_CSVSerialise +{ + DN_CSVSerialise_Read, + DN_CSVSerialise_Write, +}; + +struct DN_CSVTokeniser +{ + bool bad; + DN_Str8 string; + char delimiter; + char const *it; + bool end_of_line; +}; + +struct DN_CSVPack +{ + DN_Str8Builder write_builder; + DN_USize write_column; + DN_CSVTokeniser read_tokeniser; +}; + +#endif // !defined(DN_CSV_H) diff --git a/dqn_hash.cpp b/Extra/dn_hash.cpp similarity index 96% rename from dqn_hash.cpp rename to Extra/dn_hash.cpp index c8e6301..737a2a1 100644 --- a/dqn_hash.cpp +++ b/Extra/dn_hash.cpp @@ -1,5 +1,4 @@ -#pragma once -#include "dqn.h" +#define DN_HASH_CPP /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,12 +12,12 @@ // $$ | $$ |$$ | $$ |\$$$$$$ |$$ | $$ | // \__| \__|\__| \__| \______/ \__| \__| // -// dqn_hash.cpp +// dn_hash.cpp // //////////////////////////////////////////////////////////////////////////////////////////////////// */ -// NOTE: [$FNV1] DN_FNV1A ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_FNV1A ////////////////////////////////////////////////////////////////////////////////// // Default values recommended by: http://isthe.com/chongo/tech/comp/fnv/ DN_API uint32_t DN_FNV1A32_Iterate(void const *bytes, DN_USize size, uint32_t hash) { @@ -48,7 +47,7 @@ DN_API uint64_t DN_FNV1A64_Hash(void const *bytes, DN_USize size) return result; } -// NOTE: [$MMUR] DN_MurmurHash3 /////////////////////////////////////////////////////////////////// +// NOTE: DN_MurmurHash3 //////////////////////////////////////////////////////////////////////////// #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) #define DN_MMH3_ROTL32(x, y) _rotl(x, y) #define DN_MMH3_ROTL64(x, y) _rotl64(x, y) diff --git a/dqn_hash.h b/Extra/dn_hash.h similarity index 71% rename from dqn_hash.h rename to Extra/dn_hash.h index 386c7fb..c6bb4a8 100644 --- a/dqn_hash.h +++ b/Extra/dn_hash.h @@ -1,5 +1,5 @@ -#pragma once -#include "dqn.h" +#if !defined(DN_HASH_H) +#define DN_HASH_H /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,17 +13,12 @@ // $$ | $$ |$$ | $$ |\$$$$$$ |$$ | $$ | // \__| \__|\__| \__| \______/ \__| \__| // -// dqn_hash.h -- Hashing functions -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$FNV1] DN_FNV1A -- Hash(x) -> 32/64bit via FNV1a -// [$MMUR] DN_MurmurHash3 -- Hash(x) -> 32/128bit via MurmurHash3 +// dn_hash.h -- Hashing functions // //////////////////////////////////////////////////////////////////////////////////////////////////// */ -// NOTE: [$FNV1] DN_FNV1A ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_FNV1A ////////////////////////////////////////////////////////////////////////////////// #if !defined(DN_FNV1A32_SEED) #define DN_FNV1A32_SEED 2166136261U #endif @@ -32,18 +27,19 @@ #define DN_FNV1A64_SEED 14695981039346656037ULL #endif -// NOTE: [$MMUR] DN_MurmurHash3 /////////////////////////////////////////////////////////////////// +// NOTE: DN_MurmurHash3 //////////////////////////////////////////////////////////////////////////// struct DN_MurmurHash3 { uint64_t e[2]; }; -// NOTE: [$FNV1] DN_FNV1A ///////////////////////////////////////////////////////////////////////// +// NOTE: DN_FNV1A ////////////////////////////////////////////////////////////////////////////////// DN_API uint32_t DN_FNV1A32_Hash (void const *bytes, DN_USize size); DN_API uint64_t DN_FNV1A64_Hash (void const *bytes, DN_USize size); DN_API uint32_t DN_FNV1A32_Iterate (void const *bytes, DN_USize size, uint32_t hash); DN_API uint64_t DN_FNV1A64_Iterate (void const *bytes, DN_USize size, uint64_t hash); -// NOTE: [$MMUR] DN_MurmurHash3 /////////////////////////////////////////////////////////////////// +// NOTE: DN_MurmurHash3 //////////////////////////////////////////////////////////////////////////// DN_API uint32_t DN_MurmurHash3_x86U32 (void const *key, int len, uint32_t seed); DN_API DN_MurmurHash3 DN_MurmurHash3_x64U128 (void const *key, int len, uint32_t seed); #define DN_MurmurHash3_x64U128AsU64(key, len, seed) (DN_MurmurHash3_x64U128(key, len, seed).e[0]) #define DN_MurmurHash3_x64U128AsU32(key, len, seed) (DN_CAST(uint32_t)DN_MurmurHash3_x64U128(key, len, seed).e[0]) +#endif // !defined(DN_HASH_H) diff --git a/Extra/dn_helpers.cpp b/Extra/dn_helpers.cpp new file mode 100644 index 0000000..af83c65 --- /dev/null +++ b/Extra/dn_helpers.cpp @@ -0,0 +1,374 @@ +#define DN_HELPERS_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| +// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | +// \__| \__|\________|\________|\__| \________|\__| \__| \______/ +// +// dn_helpers.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_PCG32 ////////////////////////////////////////////////////////////////////////////////// +#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL +#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL + +DN_API DN_PCG32 DN_PCG32_Init(uint64_t seed) +{ + DN_PCG32 result = {}; + DN_PCG32_Next(&result); + result.state += seed; + DN_PCG32_Next(&result); + return result; +} + +DN_API uint32_t DN_PCG32_Next(DN_PCG32 *rng) +{ + uint64_t state = rng->state; + rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64; + + // XSH-RR + uint32_t value = (uint32_t)((state ^ (state >> 18)) >> 27); + int rot = state >> 59; + return rot ? (value >> rot) | (value << (32 - rot)) : value; +} + +DN_API uint64_t DN_PCG32_Next64(DN_PCG32 *rng) +{ + uint64_t value = DN_PCG32_Next(rng); + value <<= 32; + value |= DN_PCG32_Next(rng); + return value; +} + +DN_API uint32_t DN_PCG32_Range(DN_PCG32 *rng, uint32_t low, uint32_t high) +{ + uint32_t bound = high - low; + uint32_t threshold = -(int32_t)bound % bound; + + for (;;) { + uint32_t r = DN_PCG32_Next(rng); + if (r >= threshold) + return low + (r % bound); + } +} + +DN_API float DN_PCG32_NextF32(DN_PCG32 *rng) +{ + uint32_t x = DN_PCG32_Next(rng); + return (float)(int32_t)(x >> 8) * 0x1.0p-24f; +} + +DN_API double DN_PCG32_NextF64(DN_PCG32 *rng) +{ + uint64_t x = DN_PCG32_Next64(rng); + return (double)(int64_t)(x >> 11) * 0x1.0p-53; +} + +DN_API void DN_PCG32_Advance(DN_PCG32 *rng, uint64_t delta) +{ + uint64_t cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64; + uint64_t cur_plus = DN_PCG_DEFAULT_INCREMENT_64; + + uint64_t acc_mult = 1; + uint64_t acc_plus = 0; + + while (delta != 0) { + if (delta & 1) { + acc_mult *= cur_mult; + acc_plus = acc_plus * cur_mult + cur_plus; + } + cur_plus = (cur_mult + 1) * cur_plus; + cur_mult *= cur_mult; + delta >>= 1; + } + + rng->state = acc_mult * rng->state + acc_plus; +} + +#if !defined(DN_NO_JSON_BUILDER) +// NOTE: DN_JSONBuilder //////////////////////////////////////////////////////////////////////////// +DN_API DN_JSONBuilder DN_JSONBuilder_Init(DN_Arena *arena, int spaces_per_indent) +{ + DN_JSONBuilder result = {}; + result.spaces_per_indent = spaces_per_indent; + result.string_builder.arena = arena; + return result; +} + +DN_API DN_Str8 DN_JSONBuilder_Build(DN_JSONBuilder const *builder, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8Builder_Build(&builder->string_builder, arena); + return result; +} + +DN_API void DN_JSONBuilder_KeyValue(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) +{ + if (key.size == 0 && value.size == 0) + return; + + DN_JSONBuilderItem item = DN_JSONBuilderItem_KeyValue; + if (value.size >= 1) { + if (value.data[0] == '{' || value.data[0] == '[') + item = DN_JSONBuilderItem_OpenContainer; + else if (value.data[0] == '}' || value.data[0] == ']') + item = DN_JSONBuilderItem_CloseContainer; + } + + bool adding_to_container_with_items = + item != DN_JSONBuilderItem_CloseContainer && (builder->last_item == DN_JSONBuilderItem_KeyValue || + builder->last_item == DN_JSONBuilderItem_CloseContainer); + + uint8_t prefix_size = 0; + char prefix[2] = {0}; + if (adding_to_container_with_items) + prefix[prefix_size++] = ','; + + if (builder->last_item != DN_JSONBuilderItem_Empty) + prefix[prefix_size++] = '\n'; + + if (item == DN_JSONBuilderItem_CloseContainer) + builder->indent_level--; + + int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2; + int spaces = builder->indent_level * spaces_per_indent; + + if (key.size) + DN_Str8Builder_AppendF(&builder->string_builder, + "%.*s%*c\"%.*s\": %.*s", + prefix_size, + prefix, + spaces, + ' ', + DN_STR_FMT(key), + DN_STR_FMT(value)); + else if (spaces == 0) + DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%.*s", prefix_size, prefix, DN_STR_FMT(value)); + else + DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%*c%.*s", prefix_size, prefix, spaces, ' ', DN_STR_FMT(value)); + + if (item == DN_JSONBuilderItem_OpenContainer) + builder->indent_level++; + + builder->last_item = item; +} + +DN_API void DN_JSONBuilder_KeyValueFV(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(builder->string_builder.arena); + DN_Str8 value = DN_Str8_InitFV(tmem.arena, value_fmt, args); + DN_JSONBuilder_KeyValue(builder, key, value); +} + +DN_API void DN_JSONBuilder_KeyValueF(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...) +{ + va_list args; + va_start(args, value_fmt); + DN_JSONBuilder_KeyValueFV(builder, key, value_fmt, args); + va_end(args); +} + +DN_API void DN_JSONBuilder_ObjectBeginNamed(DN_JSONBuilder *builder, DN_Str8 name) +{ + DN_JSONBuilder_KeyValue(builder, name, DN_STR8("{")); +} + +DN_API void DN_JSONBuilder_ObjectEnd(DN_JSONBuilder *builder) +{ + DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("}")); +} + +DN_API void DN_JSONBuilder_ArrayBeginNamed(DN_JSONBuilder *builder, DN_Str8 name) +{ + DN_JSONBuilder_KeyValue(builder, name, DN_STR8("[")); +} + +DN_API void DN_JSONBuilder_ArrayEnd(DN_JSONBuilder *builder) +{ + DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("]")); +} + +DN_API void DN_JSONBuilder_Str8Named(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) +{ + DN_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data); +} + +DN_API void DN_JSONBuilder_LiteralNamed(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) +{ + DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data); +} + +DN_API void DN_JSONBuilder_U64Named(DN_JSONBuilder *builder, DN_Str8 key, uint64_t value) +{ + DN_JSONBuilder_KeyValueF(builder, key, "%I64u", value); +} + +DN_API void DN_JSONBuilder_I64Named(DN_JSONBuilder *builder, DN_Str8 key, int64_t value) +{ + DN_JSONBuilder_KeyValueF(builder, key, "%I64d", value); +} + +DN_API void DN_JSONBuilder_F64Named(DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places) +{ + if (!builder) + return; + + if (decimal_places >= 16) + decimal_places = 16; + + // NOTE: Generate the format string for the float, depending on how many + // decimals places it wants. + char float_fmt[16]; + if (decimal_places > 0) { + // NOTE: Emit the format string "%.f" i.e. %.1f + DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); + } else { + // NOTE: Emit the format string "%f" + DN_SNPrintF(float_fmt, sizeof(float_fmt), "%%f"); + } + DN_JSONBuilder_KeyValueF(builder, key, float_fmt, value); +} + +DN_API void DN_JSONBuilder_BoolNamed(DN_JSONBuilder *builder, DN_Str8 key, bool value) +{ + DN_Str8 value_string = value ? DN_STR8("true") : DN_STR8("false"); + DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data); +} +#endif // !defined(DN_NO_JSON_BUILDER) + +// NOTE: DN_JobQueue /////////////////////////////////////////////////////////////////////////////// +DN_API DN_JobQueueSPMC DN_OS_JobQueueSPMCInit() +{ + DN_JobQueueSPMC result = {}; + result.thread_wait_for_job_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + result.wait_for_completion_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + result.complete_queue_write_semaphore = DN_OS_SemaphoreInit(DN_ArrayCountU(result.complete_queue)); + result.mutex = DN_OS_MutexInit(); + return result; +} + +DN_API bool DN_OS_JobQueueSPMCCanAdd(DN_JobQueueSPMC const *queue, uint32_t count) +{ + uint32_t read_index = queue->read_index; + uint32_t write_index = queue->write_index; + uint32_t size = write_index - read_index; + bool result = (size + count) <= DN_ArrayCountU(queue->jobs); + return result; +} + +DN_API bool DN_OS_JobQueueSPMCAddArray(DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count) +{ + if (!queue) + return false; + + uint32_t const pot_mask = DN_ArrayCountU(queue->jobs) - 1; + uint32_t read_index = queue->read_index; + uint32_t write_index = queue->write_index; + uint32_t size = write_index - read_index; + + if ((size + count) > DN_ArrayCountU(queue->jobs)) + return false; + + for (size_t offset = 0; offset < count; offset++) { + uint32_t wrapped_write_index = (write_index + offset) & pot_mask; + queue->jobs[wrapped_write_index] = jobs[offset]; + } + + DN_OS_MutexLock(&queue->mutex); + queue->write_index += count; + DN_OS_SemaphoreIncrement(&queue->thread_wait_for_job_semaphore, count); + DN_OS_MutexUnlock(&queue->mutex); + return true; +} + +DN_API bool DN_OS_JobQueueSPMCAdd(DN_JobQueueSPMC *queue, DN_Job job) +{ + bool result = DN_OS_JobQueueSPMCAddArray(queue, &job, 1); + return result; +} + +DN_API int32_t DN_OS_JobQueueSPMCThread(DN_OSThread *thread) +{ + DN_JobQueueSPMC *queue = DN_CAST(DN_JobQueueSPMC *) thread->user_context; + uint32_t const pot_mask = DN_ArrayCountU(queue->jobs) - 1; + static_assert(DN_ArrayCountU(queue->jobs) == DN_ArrayCountU(queue->complete_queue), "PoT mask is used to mask access to both arrays"); + + for (;;) { + DN_OS_SemaphoreWait(&queue->thread_wait_for_job_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); + if (queue->quit) + break; + + DN_Assert(queue->read_index != queue->write_index); + + DN_OS_MutexLock(&queue->mutex); + uint32_t wrapped_read_index = queue->read_index & pot_mask; + DN_Job job = queue->jobs[wrapped_read_index]; + queue->read_index += 1; + DN_OS_MutexUnlock(&queue->mutex); + + job.elapsed_tsc -= DN_CPU_TSC(); + job.func(thread, job.user_context); + job.elapsed_tsc += DN_CPU_TSC(); + + if (job.add_to_completion_queue) { + DN_OS_SemaphoreWait(&queue->complete_queue_write_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); + DN_OS_MutexLock(&queue->mutex); + queue->complete_queue[(queue->complete_write_index++ & pot_mask)] = job; + DN_OS_MutexUnlock(&queue->mutex); + DN_OS_SemaphoreIncrement(&queue->complete_queue_write_semaphore, 1); + } + + // NOTE: Update finish counter + DN_OS_MutexLock(&queue->mutex); + queue->finish_index += 1; + + // NOTE: If all jobs are finished and we have another thread who is + // blocked via `WaitForCompletion` for this job queue, we will go + // release the semaphore to wake them all up. + bool all_jobs_finished = queue->finish_index == queue->write_index; + if (all_jobs_finished && queue->threads_waiting_for_completion) { + DN_OS_SemaphoreIncrement(&queue->wait_for_completion_semaphore, + queue->threads_waiting_for_completion); + queue->threads_waiting_for_completion = 0; + } + DN_OS_MutexUnlock(&queue->mutex); + } + + return queue->quit_exit_code; +} + +DN_API void DN_OS_JobQueueSPMCWaitForCompletion(DN_JobQueueSPMC *queue) +{ + DN_OS_MutexLock(&queue->mutex); + if (queue->finish_index == queue->write_index) { + DN_OS_MutexUnlock(&queue->mutex); + return; + } + queue->threads_waiting_for_completion++; + DN_OS_MutexUnlock(&queue->mutex); + + DN_OS_SemaphoreWait(&queue->wait_for_completion_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); +} + +DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs(DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size) +{ + DN_USize result = 0; + if (!queue || !jobs || jobs_size <= 0) + return result; + + uint32_t const pot_mask = DN_ArrayCountU(queue->jobs) - 1; + DN_OS_MutexLock(&queue->mutex); + while (queue->complete_read_index < queue->complete_write_index && result < jobs_size) + jobs[result++] = queue->complete_queue[(queue->complete_read_index++ & pot_mask)]; + DN_OS_MutexUnlock(&queue->mutex); + + return result; +} diff --git a/Extra/dn_helpers.h b/Extra/dn_helpers.h new file mode 100644 index 0000000..45db015 --- /dev/null +++ b/Extra/dn_helpers.h @@ -0,0 +1,335 @@ +#if !defined(DN_HELPERS_H) +#define DN_HELPERS_H + +#if !defined(DN_BASE_H) + #error dn_base_inc.h must be included before this +#endif + +#if !defined(DN_MATH_H) + #error dn_math.h must be included before this +#endif + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ +// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| +// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | +// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | +// \__| \__|\________|\________|\__| \________|\__| \__| \______/ +// +// dn_helpers.h -- Helper functions/data structures +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_PCG32 ////////////////////////////////////////////////////////////////////////////////// +struct DN_PCG32 { uint64_t state; }; + +#if !defined(DN_NO_JSON_BUILDER) +// NOTE: DN_JSONBuilder //////////////////////////////////////////////////////////////////////////// +enum DN_JSONBuilderItem +{ + DN_JSONBuilderItem_Empty, + DN_JSONBuilderItem_OpenContainer, + DN_JSONBuilderItem_CloseContainer, + DN_JSONBuilderItem_KeyValue, +}; + +struct DN_JSONBuilder +{ + bool use_stdout; // When set, ignore the string builder and dump immediately to stdout + DN_Str8Builder string_builder; // (Internal) + int indent_level; // (Internal) + int spaces_per_indent; // The number of spaces per indent level + DN_JSONBuilderItem last_item; +}; +#endif // !defined(DN_NO_JSON_BUIDLER) + +// NOTE: DN_BinarySearch /////////////////////////////////////////////////////////////////////////// +template +using DN_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); + +template +bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); + +enum DN_BinarySearchType +{ + // Index of the match. If no match is found, found is set to false and the + // index is set to the index where the match should be inserted/exist, if + // it were in the array + DN_BinarySearchType_Match, + + // Index of the first element in the array that is `element >= find`. If no such + // item is found or the array is empty, then, the index is set to the array + // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_LowerBound); + // printf("%zu\n", result.index); // Prints index '4' + + DN_BinarySearchType_LowerBound, + + // Index of the first element in the array that is `element > find`. If no such + // item is found or the array is empty, then, the index is set to the array + // size and found is set to `false`. + // + // For example: + // int array[] = {0, 1, 2, 3, 4, 5}; + // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ArrayCountU(array), 4, DN_BinarySearchType_UpperBound); + // printf("%zu\n", result.index); // Prints index '5' + + DN_BinarySearchType_UpperBound, +}; + +struct DN_BinarySearchResult +{ + bool found; + DN_USize index; +}; + +template +using DN_QSortLessThanProc = bool(T const &a, T const &b, void *user_context); + +// NOTE: Misc ////////////////////////////////////////////////////////////////////////////////////// +// NOTE: DN_JobQueue /////////////////////////////////////////////////////////////////////////////// +typedef void(DN_JobQueueFunc)(DN_OSThread *thread, void *user_context); +struct DN_Job +{ + DN_JobQueueFunc *func; // The function to invoke for the job + void *user_context; // Pointer user can set to use in their `job_func` + uint64_t elapsed_tsc; + uint16_t user_tag; // Arbitrary value the user can set to identiy the type of `user_context` this job has + bool add_to_completion_queue; // When true, on job completion, job must be dequeued from the completion queue via `GetFinishedJobs` +}; + +#if !defined(DN_JOB_QUEUE_SPMC_SIZE) + #define DN_JOB_QUEUE_SPMC_SIZE 128 +#endif + +struct DN_JobQueueSPMC +{ + DN_OSMutex mutex; + DN_OSSemaphore thread_wait_for_job_semaphore; + DN_OSSemaphore wait_for_completion_semaphore; + DN_U32 threads_waiting_for_completion; + + DN_Job jobs[DN_JOB_QUEUE_SPMC_SIZE]; + DN_B32 quit; + DN_U32 quit_exit_code; + DN_U32 volatile read_index; + DN_U32 volatile finish_index; + DN_U32 volatile write_index; + + DN_OSSemaphore complete_queue_write_semaphore; + DN_Job complete_queue[DN_JOB_QUEUE_SPMC_SIZE]; + DN_U32 volatile complete_read_index; + DN_U32 volatile complete_write_index; +}; + +// NOTE: DN_PCG32 ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_PCG32 DN_PCG32_Init (uint64_t seed); +DN_API uint32_t DN_PCG32_Next (DN_PCG32 *rng); +DN_API uint64_t DN_PCG32_Next64 (DN_PCG32 *rng); +DN_API uint32_t DN_PCG32_Range (DN_PCG32 *rng, uint32_t low, uint32_t high); +DN_API DN_F32 DN_PCG32_NextF32 (DN_PCG32 *rng); +DN_API DN_F64 DN_PCG32_NextF64 (DN_PCG32 *rng); +DN_API void DN_PCG32_Advance (DN_PCG32 *rng, uint64_t delta); + +#if !defined(DN_NO_JSON_BUILDER) +// NOTE: DN_JSONBuilder //////////////////////////////////////////////////////////////////////////// +#define DN_JSONBuilder_Object(builder) \ + DN_DEFER_LOOP(DN_JSONBuilder_ObjectBegin(builder), \ + DN_JSONBuilder_ObjectEnd(builder)) + +#define DN_JSONBuilder_ObjectNamed(builder, name) \ + DN_DEFER_LOOP(DN_JSONBuilder_ObjectBeginNamed(builder, name), \ + DN_JSONBuilder_ObjectEnd(builder)) + +#define DN_JSONBuilder_Array(builder) \ + DN_DEFER_LOOP(DN_JSONBuilder_ArrayBegin(builder), \ + DN_JSONBuilder_ArrayEnd(builder)) + +#define DN_JSONBuilder_ArrayNamed(builder, name) \ + DN_DEFER_LOOP(DN_JSONBuilder_ArrayBeginNamed(builder, name), \ + DN_JSONBuilder_ArrayEnd(builder)) + + +DN_API DN_JSONBuilder DN_JSONBuilder_Init (DN_Arena *arena, int spaces_per_indent); +DN_API DN_Str8 DN_JSONBuilder_Build (DN_JSONBuilder const *builder, DN_Arena *arena); +DN_API void DN_JSONBuilder_KeyValue (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); +DN_API void DN_JSONBuilder_KeyValueF (DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...); +DN_API void DN_JSONBuilder_ObjectBeginNamed (DN_JSONBuilder *builder, DN_Str8 name); +DN_API void DN_JSONBuilder_ObjectEnd (DN_JSONBuilder *builder); +DN_API void DN_JSONBuilder_ArrayBeginNamed (DN_JSONBuilder *builder, DN_Str8 name); +DN_API void DN_JSONBuilder_ArrayEnd (DN_JSONBuilder *builder); +DN_API void DN_JSONBuilder_Str8Named (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); +DN_API void DN_JSONBuilder_LiteralNamed (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); +DN_API void DN_JSONBuilder_U64Named (DN_JSONBuilder *builder, DN_Str8 key, uint64_t value); +DN_API void DN_JSONBuilder_I64Named (DN_JSONBuilder *builder, DN_Str8 key, int64_t value); +DN_API void DN_JSONBuilder_F64Named (DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places); +DN_API void DN_JSONBuilder_BoolNamed (DN_JSONBuilder *builder, DN_Str8 key, bool value); + +#define DN_JSONBuilder_ObjectBegin(builder) DN_JSONBuilder_ObjectBeginNamed(builder, DN_STR8("")) +#define DN_JSONBuilder_ArrayBegin(builder) DN_JSONBuilder_ArrayBeginNamed(builder, DN_STR8("")) +#define DN_JSONBuilder_Str8(builder, value) DN_JSONBuilder_Str8Named(builder, DN_STR8(""), value) +#define DN_JSONBuilder_Literal(builder, value) DN_JSONBuilder_LiteralNamed(builder, DN_STR8(""), value) +#define DN_JSONBuilder_U64(builder, value) DN_JSONBuilder_U64Named(builder, DN_STR8(""), value) +#define DN_JSONBuilder_I64(builder, value) DN_JSONBuilder_I64Named(builder, DN_STR8(""), value) +#define DN_JSONBuilder_F64(builder, value) DN_JSONBuilder_F64Named(builder, DN_STR8(""), value) +#define DN_JSONBuilder_Bool(builder, value) DN_JSONBuilder_BoolNamed(builder, DN_STR8(""), value) +#endif // !defined(DN_NO_JSON_BUILDER) + +// NOTE: DN_BinarySearch /////////////////////////////////////////////////////////////////////////// +template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); +template DN_BinarySearchResult DN_BinarySearch (T const *array, + DN_USize array_size, + T const &find, + DN_BinarySearchType type = DN_BinarySearchType_Match, + DN_BinarySearchLessThanProc less_than = DN_BinarySearch_DefaultLessThan); + +// NOTE: DN_QSort ////////////////////////////////////////////////////////////////////////////////// +template bool DN_QSort_DefaultLessThan(T const &lhs, T const &rhs); +template void DN_QSort (T *array, + DN_USize array_size, + void *user_context, + DN_QSortLessThanProc less_than = DN_QSort_DefaultLessThan); + +// NOTE: DN_JobQueue /////////////////////////////////////////////////////////////////////////////// +DN_API DN_JobQueueSPMC DN_OS_JobQueueSPMCInit (); +DN_API bool DN_OS_JobQueueSPMCCanAdd (DN_JobQueueSPMC const *queue, uint32_t count); +DN_API bool DN_OS_JobQueueSPMCAddArray (DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count); +DN_API bool DN_OS_JobQueueSPMCAdd (DN_JobQueueSPMC *queue, DN_Job job); +DN_API void DN_OS_JobQueueSPMCWaitForCompletion (DN_JobQueueSPMC *queue); +DN_API int32_t DN_OS_JobQueueSPMCThread (DN_OSThread *thread); +DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs (DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size); + +// NOTE: DN_BinarySearch /////////////////////////////////////////////////////////////////////////// +template +bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) +{ + bool result = lhs < rhs; + return result; +} + +template +DN_BinarySearchResult DN_BinarySearch(T const *array, + DN_USize array_size, + T const &find, + DN_BinarySearchType type, + DN_BinarySearchLessThanProc less_than) +{ + DN_BinarySearchResult result = {}; + if (!array || array_size <= 0 || !less_than) + return result; + + T const *end = array + array_size; + T const *first = array; + T const *last = end; + while (first != last) { + DN_USize count = last - first; + T const *it = first + (count / 2); + + bool advance_first = false; + if (type == DN_BinarySearchType_UpperBound) + advance_first = !less_than(find, it[0]); + else + advance_first = less_than(it[0], find); + + if (advance_first) + first = it + 1; + else + last = it; + } + + switch (type) { + case DN_BinarySearchType_Match: { + result.found = first != end && !less_than(find, *first); + } break; + + case DN_BinarySearchType_LowerBound: /*FALLTHRU*/ + case DN_BinarySearchType_UpperBound: { + result.found = first != end; + } break; + } + + result.index = first - array; + return result; +} + +// NOTE: DN_QSort ////////////////////////////////////////////////////////////////////////////////// +template +bool DN_QSort_DefaultLessThan(T const &lhs, T const &rhs, void *user_context) +{ + (void)user_context; + bool result = lhs < rhs; + return result; +} + +template +void DN_QSort(T *array, DN_USize array_size, void *user_context, DN_QSortLessThanProc less_than) +{ + if (!array || array_size <= 1 || !less_than) + return; + + // NOTE: Insertion Sort, under 24->32 is an optimal amount ///////////////////////////////////// + const DN_USize QSORT_THRESHOLD = 24; + if (array_size < QSORT_THRESHOLD) { + for (DN_USize item_to_insert_index = 1; item_to_insert_index < array_size; item_to_insert_index++) { + for (DN_USize index = 0; index < item_to_insert_index; index++) { + if (!less_than(array[index], array[item_to_insert_index], user_context)) { + T item_to_insert = array[item_to_insert_index]; + for (DN_USize i = item_to_insert_index; i > index; i--) + array[i] = array[i - 1]; + + array[index] = item_to_insert; + break; + } + } + } + return; + } + + // NOTE: Quick sort, under 24->32 is an optimal amount ///////////////////////////////////////// + DN_USize last_index = array_size - 1; + DN_USize pivot_index = array_size / 2; + DN_USize partition_index = 0; + DN_USize start_index = 0; + + // Swap pivot with last index, so pivot is always at the end of the array. + // This makes logic much simpler. + DN_Swap(array[last_index], array[pivot_index]); + pivot_index = last_index; + + // 4^, 8, 7, 5, 2, 3, 6 + if (less_than(array[start_index], array[pivot_index], user_context)) + partition_index++; + start_index++; + + // 4, |8, 7, 5^, 2, 3, 6* + // 4, 5, |7, 8, 2^, 3, 6* + // 4, 5, 2, |8, 7, ^3, 6* + // 4, 5, 2, 3, |7, 8, ^6* + for (DN_USize index = start_index; index < last_index; index++) { + if (less_than(array[index], array[pivot_index], user_context)) { + DN_Swap(array[partition_index], array[index]); + partition_index++; + } + } + + // Move pivot to right of partition + // 4, 5, 2, 3, |6, 8, ^7* + DN_Swap(array[partition_index], array[pivot_index]); + DN_QSort(array, partition_index, user_context, less_than); + + // Skip the value at partion index since that is guaranteed to be sorted. + // 4, 5, 2, 3, (x), 8, 7 + DN_USize one_after_partition_index = partition_index + 1; + DN_QSort(array + one_after_partition_index, (array_size - one_after_partition_index), user_context, less_than); +} + +#endif // !defined(DN_HELPERS_H) diff --git a/dqn_json.cpp b/Extra/dn_json.cpp similarity index 97% rename from dqn_json.cpp rename to Extra/dn_json.cpp index e8b099f..0aa1d60 100644 --- a/dqn_json.cpp +++ b/Extra/dn_json.cpp @@ -1,5 +1,4 @@ -#pragma once -#include "dqn.h" +#define DN_JSON_CPP // NOTE: DN_JSON ////////////////////////////////////////////////////////////////////////////////// void *DN_JSON_ArenaAllocFunc(void *user_data, size_t count) @@ -60,7 +59,7 @@ bool DN_JSON_ItPushObjElement(DN_JSONIt *it, json_object_element_s *element) { if (!it || !element) return false; - DN_ASSERT(it->stack_count < DN_ARRAY_ICOUNT(it->stack)); + DN_Assert(it->stack_count < DN_ArrayCountI(it->stack)); it->stack[it->stack_count++] = {DN_JSON_ItEntryTypeObjElement, element}; return true; } @@ -69,7 +68,7 @@ bool DN_JSON_ItPushObj(DN_JSONIt *it, json_object_s *obj) { if (!it || !obj) return false; - DN_ASSERT(it->stack_count < DN_ARRAY_ICOUNT(it->stack)); + DN_Assert(it->stack_count < DN_ArrayCountI(it->stack)); it->stack[it->stack_count++] = {DN_JSON_ItEntryTypeObj, obj}; return true; } @@ -78,7 +77,7 @@ bool DN_JSON_ItPushArrayElement(DN_JSONIt *it, json_array_element_s *element) { if (!it || !element) return false; - DN_ASSERT(it->stack_count < DN_ARRAY_ICOUNT(it->stack)); + DN_Assert(it->stack_count < DN_ArrayCountI(it->stack)); it->stack[it->stack_count++] = {DN_JSON_ItEntryTypeArrayElement, element}; return true; } @@ -87,7 +86,7 @@ bool DN_JSON_ItPushArray(DN_JSONIt *it, json_value_s *value) { if (!it || !value || json_value_as_array(value) == nullptr) return false; - DN_ASSERT(it->stack_count < DN_ARRAY_ICOUNT(it->stack)); + DN_Assert(it->stack_count < DN_ArrayCountI(it->stack)); it->stack[it->stack_count++] = {DN_JSON_ItEntryTypeArray, value}; return true; } @@ -111,7 +110,7 @@ void DN_JSON_ItPop(DN_JSONIt *it) { if (!it) return; - DN_ASSERT(it->stack_count > 0); + DN_Assert(it->stack_count > 0); if (it->stack_count > 0) it->stack_count--; } @@ -136,11 +135,11 @@ json_value_s *DN_JSON_ItPushCurrValue(DN_JSONIt *it) if (result->type == json_type_array) { json_array_s *array = json_value_as_array(result); - DN_ASSERT(array); + DN_Assert(array); DN_JSON_ItPushArray(it, result); } else if (result->type == json_type_object) { json_object_s *obj = json_value_as_object(result); - DN_ASSERT(obj); + DN_Assert(obj); DN_JSON_ItPushObj(it, obj); } diff --git a/dqn_json.h b/Extra/dn_json.h similarity index 94% rename from dqn_json.h rename to Extra/dn_json.h index a129842..ef81c8d 100644 --- a/dqn_json.h +++ b/Extra/dn_json.h @@ -1,5 +1,5 @@ -#pragma once -#include "dqn.h" +#if !defined(DN_JSON_H) +#define DN_JSON_H #if !defined(SHEREDOM_JSON_H_INCLUDED) #error Sheredom json.h (github.com/sheredom/json.h) must be included before this file @@ -50,7 +50,7 @@ json_value_s *DN_JSON_ItPushCurrValue(DN_JSONIt *it); bool DN_JSON_ItNext(DN_JSONIt *it); #define DN_JSON_ItPushCurrValueIterateThenPop(it) \ - for(void *DN_UNIQUE_NAME(ptr) = DN_JSON_ItPushCurrValue(it); DN_UNIQUE_NAME(ptr); DN_JSON_ItPop(it), DN_UNIQUE_NAME(ptr) = nullptr) \ + for(void *DN_UniqueName(ptr) = DN_JSON_ItPushCurrValue(it); DN_UniqueName(ptr); DN_JSON_ItPop(it), DN_UniqueName(ptr) = nullptr) \ while (DN_JSON_ItNext(it)) // NOTE: DN_JSON_ItCurr ///////////////////////////////////////////////////////////////////// @@ -87,3 +87,5 @@ bool DN_JSON_ItValueToBool(DN_JSONIt *it); #define DN_JSON_ItErrorUnknownKeyValue(it) DN_JSON_ItErrorUnknownKeyValue_(it, DN_CALL_SITE) void DN_JSON_ItErrorUnknownKeyValue_(DN_JSONIt *it, DN_CallSite call_site); + +#endif // !defined(DN_JSON_H) diff --git a/Extra/dn_math.cpp b/Extra/dn_math.cpp new file mode 100644 index 0000000..e51c6ec --- /dev/null +++ b/Extra/dn_math.cpp @@ -0,0 +1,1585 @@ +#define DN_MATH_CPP + +#if !defined(DN_NO_V2) +// NOTE: DN_V2 ///////////////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V2I32 +DN_API bool operator==(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator!=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator>=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2I32 lhs, DN_V2I32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DN_API DN_V2I32 operator-(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2I32 operator-(DN_V2I32 lhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(-lhs.x, -lhs.y); + return result; +} + +DN_API DN_V2I32 operator+(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_F32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2I32 operator*(DN_V2I32 lhs, int32_t rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_V2I32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_F32 rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2I32 operator/(DN_V2I32 lhs, int32_t rhs) +{ + DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, int32_t rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2I32 &operator-=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2I32 &operator+=(DN_V2I32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2I32 DN_V2I32_Min(DN_V2I32 a, DN_V2I32 b) +{ + DN_V2I32 result = DN_V2I32_Init2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); + return result; +} + +DN_API DN_V2I32 DN_V2I32_Max(DN_V2I32 a, DN_V2I32 b) +{ + DN_V2I32 result = DN_V2I32_Init2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); + return result; +} + +DN_API DN_V2I32 DN_V2I32_Abs(DN_V2I32 a) +{ + DN_V2I32 result = DN_V2I32_Init2N(DN_Abs(a.x), DN_Abs(a.y)); + return result; +} + +// NOTE: DN_V2U16 +DN_API bool operator!=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator>=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2U16 lhs, DN_V2U16 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +DN_API DN_V2U16 operator-(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2U16 operator+(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_F32 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2U16 operator*(DN_V2U16 lhs, int32_t rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_V2U16 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_F32 rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2U16 operator/(DN_V2U16 lhs, int32_t rhs) +{ + DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, int32_t rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2U16 &operator-=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2U16 &operator+=(DN_V2U16 &lhs, DN_V2U16 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +// NOTE: DN_V2 +DN_API bool operator!=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); + return result; +} + +DN_API bool operator>=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); + return result; +} + +DN_API bool operator<=(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + return result; +} + +DN_API bool operator<(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); + return result; +} + +DN_API bool operator>(DN_V2F32 lhs, DN_V2F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); + return result; +} + +// NOTE: DN_V2F32 operator- ////////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 operator-(DN_V2F32 lhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(-lhs.x, -lhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs, lhs.y - rhs); + return result; +} + +DN_API DN_V2F32 operator-(DN_V2F32 lhs, int32_t rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs, lhs.y - rhs); + return result; +} + +// NOTE: DN_V2F32 operator+ ////////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs, lhs.y + rhs); + return result; +} + +DN_API DN_V2F32 operator+(DN_V2F32 lhs, int32_t rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs, lhs.y + rhs); + return result; +} + +// NOTE: DN_V2F32 operator* ////////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +DN_API DN_V2F32 operator*(DN_V2F32 lhs, int32_t rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs, lhs.y * rhs); + return result; +} + +// NOTE: DN_V2F32 operator/ ////////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2I32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_F32 rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +DN_API DN_V2F32 operator/(DN_V2F32 lhs, int32_t rhs) +{ + DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs, lhs.y / rhs); + return result; +} + +// NOTE: DN_V2F32 operator*/ ///////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +// NOTE: DN_V2F32 operator// ///////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, int32_t rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +// NOTE: DN_V2F32 operator-/ ///////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, int32_t rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +// NOTE: DN_V2F32 operator+/ ///////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2I32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, int32_t rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_V2F32 DN_V2F32_Min(DN_V2F32 a, DN_V2F32 b) +{ + DN_V2F32 result = DN_V2F32_Init2N(DN_Min(a.x, b.x), DN_Min(a.y, b.y)); + return result; +} + +DN_API DN_V2F32 DN_V2F32_Max(DN_V2F32 a, DN_V2F32 b) +{ + DN_V2F32 result = DN_V2F32_Init2N(DN_Max(a.x, b.x), DN_Max(a.y, b.y)); + return result; +} + +DN_API DN_V2F32 DN_V2F32_Abs(DN_V2F32 a) +{ + DN_V2F32 result = DN_V2F32_Init2N(DN_Abs(a.x), DN_Abs(a.y)); + return result; +} + +DN_API DN_F32 DN_V2F32_Dot(DN_V2F32 a, DN_V2F32 b) +{ + // NOTE: Scalar projection of B onto A ///////////////////////////////////////////////////////// + // + // Scalar projection calculates the signed distance between `b` and `a` + // where `a` is a unit vector then, the dot product calculates the projection + // of `b` onto the infinite line that the direction of `a` represents. This + // calculation is the signed distance. + // + // signed_distance = dot_product(a, b) = (a.x * b.x) + (a.y * b.y) + // + // Y + // ^ b + // | /| + // | / | + // | / | + // | / | Projection + // | / | + // |/ V + // +--->--------> X + // . a . + // . . + // |------| <- Calculated signed distance + // + // The signed-ness of the result indicates the relationship: + // + // Distance <0 means `b` is behind `a` + // Distance >0 means `b` is in-front of `a` + // Distance ==0 means `b` is perpendicular to `a` + // + // If `a` is not normalized then the signed-ness of the result still holds + // however result no longer represents the actual distance between the + // 2 objects. One of the vectors must be normalised (e.g. turned into a unit + // vector). + // + // NOTE: DN_V projection ///////////////////////////////////////////////////////////////////// + // + // DN_V projection calculates the exact X,Y coordinates of where `b` meets + // `a` when it was projected. This is calculated by multipying the + // 'scalar projection' result by the unit vector of `a` + // + // vector_projection = a * signed_distance = a * dot_product(a, b) + + DN_F32 result = (a.x * b.x) + (a.y * b.y); + return result; +} + +DN_API DN_F32 DN_V2F32_LengthSq_V2x2(DN_V2F32 lhs, DN_V2F32 rhs) +{ + // NOTE: Pythagoras's theorem (a^2 + b^2 = c^2) without the square root + DN_F32 a = rhs.x - lhs.x; + DN_F32 b = rhs.y - lhs.y; + DN_F32 c_squared = DN_Squared(a) + DN_Squared(b); + DN_F32 result = c_squared; + return result; +} + +DN_API DN_F32 DN_V2F32_Length_V2x2(DN_V2F32 lhs, DN_V2F32 rhs) +{ + DN_F32 result_squared = DN_V2F32_LengthSq_V2x2(lhs, rhs); + DN_F32 result = DN_SqrtF32(result_squared); + return result; +} + +DN_API DN_F32 DN_V2F32_LengthSq(DN_V2F32 lhs) +{ + // NOTE: Pythagoras's theorem without the square root + DN_F32 c_squared = DN_Squared(lhs.x) + DN_Squared(lhs.y); + DN_F32 result = c_squared; + return result; +} + +DN_API DN_F32 DN_V2F32_Length(DN_V2F32 lhs) +{ + DN_F32 c_squared = DN_V2F32_LengthSq(lhs); + DN_F32 result = DN_SqrtF32(c_squared); + return result; +} + +DN_API DN_V2F32 DN_V2F32_Normalise(DN_V2F32 a) +{ + DN_F32 length = DN_V2F32_Length(a); + DN_V2F32 result = a / length; + return result; +} + +DN_API DN_V2F32 DN_V2F32_Perpendicular(DN_V2F32 a) +{ + // NOTE: Matrix form of a 2D vector can be defined as + // + // x' = x cos(t) - y sin(t) + // y' = x sin(t) + y cos(t) + // + // Calculate a line perpendicular to a vector means rotating the vector by + // 90 degrees + // + // x' = x cos(90) - y sin(90) + // y' = x sin(90) + y cos(90) + // + // Where `cos(90) = 0` and `sin(90) = 1` then, + // + // x' = -y + // y' = +x + + DN_V2F32 result = DN_V2F32_Init2N(-a.y, a.x); + return result; +} + +DN_API DN_V2F32 DN_V2F32_Reflect(DN_V2F32 in, DN_V2F32 surface) +{ + DN_V2F32 normal = DN_V2F32_Perpendicular(surface); + DN_V2F32 normal_norm = DN_V2F32_Normalise(normal); + DN_F32 signed_dist = DN_V2F32_Dot(in, normal_norm); + DN_V2F32 result = DN_V2F32_Init2N(in.x, in.y + (-signed_dist * 2.f)); + return result; +} + +DN_API DN_F32 DN_V2F32_Area(DN_V2F32 a) +{ + DN_F32 result = a.w * a.h; + return result; +} +#endif // !defined(DN_NO_V2) + +#if !defined(DN_NO_V3) +// NOTE: DN_V3 ///////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator!=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator==(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z); + return result; +} + +DN_API bool operator>=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z); + return result; +} + +DN_API bool operator<=(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z); + return result; +} + +DN_API bool operator<(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z); + return result; +} + +DN_API bool operator>(DN_V3F32 lhs, DN_V3F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z); + return result; +} + +DN_API DN_V3F32 operator-(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); + return result; +} + +DN_API DN_V3F32 operator-(DN_V3F32 lhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(-lhs.x, -lhs.y, -lhs.z); + return result; +} + +DN_API DN_V3F32 operator+(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); + return result; +} + +DN_API DN_V3F32 operator*(DN_V3F32 lhs, int32_t rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_V3F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_F32 rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); + return result; +} + +DN_API DN_V3F32 operator/(DN_V3F32 lhs, int32_t rhs) +{ + DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); + return result; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_F32 rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, int32_t rhs) +{ + lhs = lhs / rhs; + return lhs; +} + +DN_API DN_V3F32 &operator-=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V3F32 &operator+=(DN_V3F32 &lhs, DN_V3F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_F32 DN_V3_LengthSq(DN_V3F32 a) +{ + DN_F32 result = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); + return result; +} + +DN_API DN_F32 DN_V3_Length(DN_V3F32 a) +{ + DN_F32 length_sq = DN_Squared(a.x) + DN_Squared(a.y) + DN_Squared(a.z); + DN_F32 result = DN_SqrtF32(length_sq); + return result; +} + +DN_API DN_V3F32 DN_V3_Normalise(DN_V3F32 a) +{ + DN_F32 length = DN_V3_Length(a); + DN_V3F32 result = a / length; + return result; +} +#endif // !defined(DN_NO_V3) + +#if !defined(DN_NO_V4) +// NOTE: DN_V4 ///////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator==(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z) && (lhs.w == rhs.w); + return result; +} + +DN_API bool operator!=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API bool operator>=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z) && (lhs.w >= rhs.w); + return result; +} + +DN_API bool operator<=(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z) && (lhs.w <= rhs.w); + return result; +} + +DN_API bool operator<(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z) && (lhs.w < rhs.w); + return result; +} + +DN_API bool operator>(DN_V4F32 lhs, DN_V4F32 rhs) +{ + bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z) && (lhs.w > rhs.w); + return result; +} + +DN_API DN_V4F32 operator-(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); + return result; +} + +DN_API DN_V4F32 operator-(DN_V4F32 lhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(-lhs.x, -lhs.y, -lhs.z, -lhs.w); + return result; +} + +DN_API DN_V4F32 operator+(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_V4F32 rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_F32 rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + return result; +} + +DN_API DN_V4F32 operator*(DN_V4F32 lhs, int32_t rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + return result; +} + +DN_API DN_V4F32 operator/(DN_V4F32 lhs, DN_F32 rhs) +{ + DN_V4F32 result = DN_V4F32_Init4N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); + return result; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_F32 rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, int32_t rhs) +{ + lhs = lhs * rhs; + return lhs; +} + +DN_API DN_V4F32 &operator-=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs - rhs; + return lhs; +} + +DN_API DN_V4F32 &operator+=(DN_V4F32 &lhs, DN_V4F32 rhs) +{ + lhs = lhs + rhs; + return lhs; +} + +DN_API DN_F32 DN_V4F32Dot(DN_V4F32 a, DN_V4F32 b) +{ + DN_F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); + return result; +} +#endif // !defined(DN_NO_V4) + +#if !defined(DN_NO_M4) +// NOTE: DN_M4 ///////////////////////////////////////////////////////////////////////////////////// +DN_API DN_M4 DN_M4_Identity() +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_ScaleF(DN_F32 x, DN_F32 y, DN_F32 z) +{ + DN_M4 result = + { + { + {x, 0, 0, 0}, + {0, y, 0, 0}, + {0, 0, z, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Scale(DN_V3F32 xyz) +{ + DN_M4 result = + { + { + {xyz.x, 0, 0, 0}, + {0, xyz.y, 0, 0}, + {0, 0, xyz.z, 0}, + {0, 0, 0, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_TranslateF(DN_F32 x, DN_F32 y, DN_F32 z) +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {x, y, z, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Translate(DN_V3F32 xyz) +{ + DN_M4 result = + { + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {xyz.x, xyz.y, xyz.z, 1}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Transpose(DN_M4 mat) +{ + DN_M4 result = {}; + for (int col = 0; col < 4; col++) + for (int row = 0; row < 4; row++) + result.columns[col][row] = mat.columns[row][col]; + return result; +} + +DN_API DN_M4 DN_M4_Rotate(DN_V3F32 axis01, DN_F32 radians) +{ + DN_AssertF(DN_Abs(DN_V3_Length(axis01) - 1.f) <= 0.01f, + "Rotation axis must be normalised, length = %f", + DN_V3_Length(axis01)); + + DN_F32 sin = DN_SinF32(radians); + DN_F32 cos = DN_CosF32(radians); + DN_F32 one_minus_cos = 1.f - cos; + + DN_F32 x = axis01.x; + DN_F32 y = axis01.y; + DN_F32 z = axis01.z; + DN_F32 x2 = DN_Squared(x); + DN_F32 y2 = DN_Squared(y); + DN_F32 z2 = DN_Squared(z); + + DN_M4 result = + { + { + {cos + x2 * one_minus_cos, y * x * one_minus_cos + z * sin, z * x * one_minus_cos - y * sin, 0}, // Col 1 + {x * y * one_minus_cos - z * sin, cos + y2 * one_minus_cos, z * y * one_minus_cos + x * sin, 0}, // Col 2 + {x * z * one_minus_cos + y * sin, y * z * one_minus_cos - x * sin, cos + z2 * one_minus_cos, 0}, // Col 3 + {0, 0, 0, 1}, // Col 4 + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Orthographic(DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far) +{ + // NOTE: Here is the matrix in column major for readability. Below it's + // transposed due to how you have to declare column major matrices in C/C++. + // + // m = [2/r-l, 0, 0, -1*(r+l)/(r-l)] + // [0, 2/t-b, 0, 1*(t+b)/(t-b)] + // [0, 0, -2/f-n, -1*(f+n)/(f-n)] + // [0, 0, 0, 1 ] + + DN_M4 result = + { + { + {2.f / (right - left), 0.f, 0.f, 0.f}, + {0.f, 2.f / (top - bottom), 0.f, 0.f}, + {0.f, 0.f, -2.f / (z_far - z_near), 0.f}, + {(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Perspective(DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far) +{ + DN_F32 tan_fov = DN_TanF32(fov / 2.f); + DN_M4 result = + { + { + {1.f / (aspect * tan_fov), 0.f, 0.f, 0.f}, + {0, 1.f / tan_fov, 0.f, 0.f}, + {0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f}, + {0.f, 0.f, (2.f * z_near * z_far) / (z_near - z_far), 0.f}, + } + }; + + return result; +} + +DN_API DN_M4 DN_M4_Add(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4_Sub(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4_Mul(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + DN_F32 sum = 0; + for (int f32_it = 0; f32_it < 4; f32_it++) + sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it]; + + result.columns[col][row] = sum; + } + } + return result; +} + +DN_API DN_M4 DN_M4_Div(DN_M4 lhs, DN_M4 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it]; + return result; +} + +DN_API DN_M4 DN_M4_AddF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] + rhs; + return result; +} + +DN_API DN_M4 DN_M4_SubF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] - rhs; + return result; +} + +DN_API DN_M4 DN_M4_MulF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] * rhs; + return result; +} + +DN_API DN_M4 DN_M4_DivF(DN_M4 lhs, DN_F32 rhs) +{ + DN_M4 result; + for (int col = 0; col < 4; col++) + for (int it = 0; it < 4; it++) + result.columns[col][it] = lhs.columns[col][it] / rhs; + return result; +} + + #if !defined(DN_NO_FSTR8) +DN_API DN_FStr8<256> DN_M4_ColumnMajorString(DN_M4 mat) +{ + DN_FStr8<256> result = {}; + for (int row = 0; row < 4; row++) { + for (int it = 0; it < 4; it++) { + if (it == 0) + DN_FStr8_Add(&result, DN_STR8("|")); + DN_FStr8_AddF(&result, "%.5f", mat.columns[it][row]); + if (it != 3) + DN_FStr8_Add(&result, DN_STR8(", ")); + else + DN_FStr8_Add(&result, DN_STR8("|\n")); + } + } + + return result; +} + #endif +#endif // !defined(DN_M4) + +// NOTE: DN_M2x3 /////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator==(DN_M2x3 const &lhs, DN_M2x3 const &rhs) +{ + bool result = DN_Memcmp(lhs.e, rhs.e, sizeof(lhs.e[0]) * DN_ArrayCountU(lhs.e)) == 0; + return result; +} + +DN_API bool operator!=(DN_M2x3 const &lhs, DN_M2x3 const &rhs) +{ + bool result = !(lhs == rhs); + return result; +} + +DN_API DN_M2x3 DN_M2x3_Identity() +{ + DN_M2x3 result = { + { + 1, + 0, + 0, + 0, + 1, + 0, + } + }; + return result; +} + +DN_API DN_M2x3 DN_M2x3_Translate(DN_V2F32 offset) +{ + DN_M2x3 result = { + { + 1, + 0, + offset.x, + 0, + 1, + offset.y, + } + }; + return result; +} + +DN_API DN_M2x3 DN_M2x3_Scale(DN_V2F32 scale) +{ + DN_M2x3 result = { + { + scale.x, + 0, + 0, + 0, + scale.y, + 0, + } + }; + return result; +} + +DN_API DN_M2x3 DN_M2x3_Rotate(DN_F32 radians) +{ + DN_M2x3 result = { + { + DN_CosF32(radians), + DN_SinF32(radians), + 0, + -DN_SinF32(radians), + DN_CosF32(radians), + 0, + } + }; + return result; +} + +DN_API DN_M2x3 DN_M2x3_Mul(DN_M2x3 m1, DN_M2x3 m2) +{ + // NOTE: Ordinarily you can't multiply M2x3 with M2x3 because column count + // (3) != row count (2). We pretend we have two 3x3 matrices with the last + // row set to [0 0 1] and perform a 3x3 matrix multiply. + // + // | (0)a (1)b (2)c | | (0)g (1)h (2)i | + // | (3)d (4)e (5)f | x | (3)j (4)k (5)l | + // | (6)0 (7)0 (8)1 | | (6)0 (7)0 (8)1 | + + DN_M2x3 result = { + { + m1.e[0] * m2.e[0] + m1.e[1] * m2.e[3], // a*g + b*j + c*0[omitted], + m1.e[0] * m2.e[1] + m1.e[1] * m2.e[4], // a*h + b*k + c*0[omitted], + m1.e[0] * m2.e[2] + m1.e[1] * m2.e[5] + m1.e[2], // a*i + b*l + c*1, + + m1.e[3] * m2.e[0] + m1.e[4] * m2.e[3], // d*g + e*j + f*0[omitted], + m1.e[3] * m2.e[1] + m1.e[4] * m2.e[4], // d*h + e*k + f*0[omitted], + m1.e[3] * m2.e[2] + m1.e[4] * m2.e[5] + m1.e[5], // d*i + e*l + f*1, + } + }; + + return result; +} + +DN_API DN_V2F32 DN_M2x3_Mul2F32(DN_M2x3 m1, DN_F32 x, DN_F32 y) +{ + // NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3) + // != row count (2). We pretend we have a V3 with `z` set to `1`. + // + // | (0)a (1)b (2)c | | x | + // | (3)d (4)e (5)f | x | y | + // | 1 | + + DN_V2F32 result = { + { + m1.e[0] * x + m1.e[1] * y + m1.e[2], // a*x + b*y + c*1 + m1.e[3] * x + m1.e[4] * y + m1.e[5], // d*x + e*y + f*1 + } + }; + return result; +} + +DN_API DN_V2F32 DN_M2x3_MulV2(DN_M2x3 m1, DN_V2F32 v2) +{ + DN_V2F32 result = DN_M2x3_Mul2F32(m1, v2.x, v2.y); + return result; +} + +#if !defined(DN_NO_RECT) +// NOTE: DN_Rect /////////////////////////////////////////////////////////////////////////////////// +DN_API bool operator==(const DN_Rect &lhs, const DN_Rect &rhs) +{ + bool result = (lhs.pos == rhs.pos) && (lhs.size == rhs.size); + return result; +} + +DN_API DN_V2F32 DN_Rect_Center(DN_Rect rect) +{ + DN_V2F32 result = rect.pos + (rect.size * .5f); + return result; +} + +DN_API bool DN_Rect_ContainsPoint(DN_Rect rect, DN_V2F32 p) +{ + DN_V2F32 min = rect.pos; + DN_V2F32 max = rect.pos + rect.size; + bool result = (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); + return result; +} + +DN_API bool DN_Rect_ContainsRect(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + bool result = (b_min >= a_min && b_max <= a_max); + return result; +} + +DN_API DN_Rect DN_Rect_Expand(DN_Rect a, DN_F32 amount) +{ + DN_Rect result = a; + result.pos -= amount; + result.size += (amount * 2.f); + return result; +} + +DN_API DN_Rect DN_Rect_ExpandV2(DN_Rect a, DN_V2F32 amount) +{ + DN_Rect result = a; + result.pos -= amount; + result.size += (amount * 2.f); + return result; +} + +DN_API bool DN_Rect_Intersects(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + bool result = (a_min.x <= b_max.x && a_max.x >= b_min.x) && + (a_min.y <= b_max.y && a_max.y >= b_min.y); + return result; +} + +DN_API DN_Rect DN_Rect_Intersection(DN_Rect a, DN_Rect b) +{ + DN_Rect result = DN_Rect_Init2V2(a.pos, DN_V2F32_Init1N(0)); + if (DN_Rect_Intersects(a, b)) { + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + + DN_V2F32 min = {}; + DN_V2F32 max = {}; + min.x = DN_Max(a_min.x, b_min.x); + min.y = DN_Max(a_min.y, b_min.y); + max.x = DN_Min(a_max.x, b_max.x); + max.y = DN_Min(a_max.y, b_max.y); + result = DN_Rect_Init2V2(min, max - min); + } + return result; +} + +DN_API DN_Rect DN_Rect_Union(DN_Rect a, DN_Rect b) +{ + DN_V2F32 a_min = a.pos; + DN_V2F32 a_max = a.pos + a.size; + DN_V2F32 b_min = b.pos; + DN_V2F32 b_max = b.pos + b.size; + + DN_V2F32 min, max; + min.x = DN_Min(a_min.x, b_min.x); + min.y = DN_Min(a_min.y, b_min.y); + max.x = DN_Max(a_max.x, b_max.x); + max.y = DN_Max(a_max.y, b_max.y); + DN_Rect result = DN_Rect_Init2V2(min, max - min); + return result; +} + +DN_API DN_RectMinMax DN_Rect_MinMax(DN_Rect a) +{ + DN_RectMinMax result = {}; + result.min = a.pos; + result.max = a.pos + a.size; + return result; +} + +DN_API DN_F32 DN_Rect_Area(DN_Rect a) +{ + DN_F32 result = a.size.w * a.size.h; + return result; +} + +DN_API DN_Rect DN_Rect_CutLeftClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_x = rect->pos.x; + DN_F32 max_x = rect->pos.x + rect->size.w; + DN_F32 result_max_x = min_x + amount; + if (clip) + result_max_x = DN_Min(result_max_x, max_x); + DN_Rect result = DN_Rect_Init4N(min_x, rect->pos.y, result_max_x - min_x, rect->size.h); + rect->pos.x = result_max_x; + rect->size.w = max_x - result_max_x; + return result; +} + +DN_API DN_Rect DN_Rect_CutRightClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_x = rect->pos.x; + DN_F32 max_x = rect->pos.x + rect->size.w; + DN_F32 result_min_x = max_x - amount; + if (clip) + result_min_x = DN_Max(result_min_x, 0); + DN_Rect result = DN_Rect_Init4N(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h); + rect->size.w = result_min_x - min_x; + return result; +} + +DN_API DN_Rect DN_Rect_CutTopClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_y = rect->pos.y; + DN_F32 max_y = rect->pos.y + rect->size.h; + DN_F32 result_max_y = min_y + amount; + if (clip) + result_max_y = DN_Min(result_max_y, max_y); + DN_Rect result = DN_Rect_Init4N(rect->pos.x, min_y, rect->size.w, result_max_y - min_y); + rect->pos.y = result_max_y; + rect->size.h = max_y - result_max_y; + return result; +} + +DN_API DN_Rect DN_Rect_CutBottomClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) +{ + DN_F32 min_y = rect->pos.y; + DN_F32 max_y = rect->pos.y + rect->size.h; + DN_F32 result_min_y = max_y - amount; + if (clip) + result_min_y = DN_Max(result_min_y, 0); + DN_Rect result = DN_Rect_Init4N(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y); + rect->size.h = result_min_y - min_y; + return result; +} + +DN_API DN_Rect DN_RectCut_Cut(DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip) +{ + DN_Rect result = {}; + if (rect_cut.rect) { + switch (rect_cut.side) { + case DN_RectCutSide_Left: result = DN_Rect_CutLeftClip(rect_cut.rect, size.w, clip); break; + case DN_RectCutSide_Right: result = DN_Rect_CutRightClip(rect_cut.rect, size.w, clip); break; + case DN_RectCutSide_Top: result = DN_Rect_CutTopClip(rect_cut.rect, size.h, clip); break; + case DN_RectCutSide_Bottom: result = DN_Rect_CutBottomClip(rect_cut.rect, size.h, clip); break; + } + } + return result; +} + +DN_API DN_V2F32 DN_Rect_InterpolatedPoint(DN_Rect rect, DN_V2F32 t01) +{ + DN_V2F32 result = DN_V2F32_Init2N(rect.pos.w + (rect.size.w * t01.x), + rect.pos.h + (rect.size.h * t01.y)); + return result; +} + +DN_API DN_V2F32 DN_Rect_TopLeft(DN_Rect rect) +{ + DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(0, 0)); + return result; +} + +DN_API DN_V2F32 DN_Rect_TopRight(DN_Rect rect) +{ + DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(1, 0)); + return result; +} + +DN_API DN_V2F32 DN_Rect_BottomLeft(DN_Rect rect) +{ + DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(0, 1)); + return result; +} + +DN_API DN_V2F32 DN_Rect_BottomRight(DN_Rect rect) +{ + DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(1, 1)); + return result; +} +#endif // !defined(DN_NO_RECT) + +// NOTE: Raycast /////////////////////////////////////////////////////////////////////////////////// + +DN_API DN_RaycastLineIntersectV2Result DN_Raycast_LineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b) +{ + // NOTE: Parametric equation of a line + // + // p = o + (t*d) + // + // - o is the starting 2d point + // - d is the direction of the line + // - t is a scalar that scales along the direction of the point + // + // To determine if a ray intersections a ray, we want to solve + // + // (o_a + (t_a * d_a)) = (o_b + (t_b * d_b)) + // + // Where '_a' and '_b' represent the 1st and 2nd point's origin, direction + // and 't' components respectively. This is 2 equations with 2 unknowns + // (`t_a` and `t_b`) which we can solve for by expressing the equation in + // terms of `t_a` and `t_b`. + // + // Working that math out produces the formula below for 't'. + + DN_RaycastLineIntersectV2Result result = {}; + DN_F32 denominator = ((dir_b.y * dir_a.x) - (dir_b.x * dir_a.y)); + if (denominator != 0.0f) { + result.t_a = (((origin_a.y - origin_b.y) * dir_b.x) + ((origin_b.x - origin_a.x) * dir_b.y)) / denominator; + result.t_b = (((origin_a.y - origin_b.y) * dir_a.x) + ((origin_b.x - origin_a.x) * dir_a.y)) / denominator; + result.hit = true; + } + return result; +} + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DN_API DN_V2F32 DN_Lerp_V2F32(DN_V2F32 a, DN_F32 t, DN_V2F32 b) +{ + DN_V2F32 result = {}; + result.x = a.x + ((b.x - a.x) * t); + result.y = a.y + ((b.y - a.y) * t); + return result; +} + +DN_API DN_F32 DN_Lerp_F32(DN_F32 a, DN_F32 t, DN_F32 b) +{ + DN_F32 result = a + ((b - a) * t); + return result; +} diff --git a/dqn_math.h b/Extra/dn_math.h similarity index 89% rename from dqn_math.h rename to Extra/dn_math.h index 949a0de..ef5bc9a 100644 --- a/dqn_math.h +++ b/Extra/dn_math.h @@ -1,37 +1,10 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$\ $$$$$$$$\ $$\ $$\ -// $$$\ $$$ |$$ __$$\\__$$ __|$$ | $$ | -// $$$$\ $$$$ |$$ / $$ | $$ | $$ | $$ | -// $$\$$\$$ $$ |$$$$$$$$ | $$ | $$$$$$$$ | -// $$ \$$$ $$ |$$ __$$ | $$ | $$ __$$ | -// $$ |\$ /$$ |$$ | $$ | $$ | $$ | $$ | -// $$ | \_/ $$ |$$ | $$ | $$ | $$ | $$ | -// \__| \__|\__| \__| \__| \__| \__| -// -// dqn_math.h -- Basic math functions -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$VEC2] DN_V2F32, V2I32 -- DN_V2 -// [$VEC3] DN_V3F32, V3I32 -- DN_V3 -// [$VEC4] DN_V4F32, V4I32 -- DN_V4 -// [$MAT4] DN_M4 -- DN_M4 -// [$M2x3] DN_M2x3 -- -// [$RECT] DN_Rect -- DN_RECT -// [$MATH] Other -- -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ +#if !defined(DN_MATH_H) +#define DN_MATH_H DN_MSVC_WARNING_PUSH DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union #if !defined(DN_NO_V2) -// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V2 ///////////////////////////////////////////////////////////////////////////////////// union DN_V2I32 { struct { int32_t x, y; }; @@ -55,7 +28,7 @@ union DN_V2F32 #endif // !defined(DN_NO_V2) #if !defined(DN_NO_V3) -// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V3 ///////////////////////////////////////////////////////////////////////////////////// union DN_V3F32 { struct { DN_F32 x, y, z; }; @@ -66,7 +39,7 @@ union DN_V3F32 #endif // !defined(DN_NO_V3) #if !defined(DN_NO_V4) -// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V4 ///////////////////////////////////////////////////////////////////////////////////// union DN_V4F32 { struct { DN_F32 x, y, z, w; }; @@ -81,21 +54,21 @@ union DN_V4F32 DN_MSVC_WARNING_POP #if !defined(DN_NO_M4) -// NOTE: [$MAT4] DN_M4 //////////////////////////////////////////////////////////////////////////// +// NOTE: DN_M4 ///////////////////////////////////////////////////////////////////////////////////// struct DN_M4 { DN_F32 columns[4][4]; // Column major matrix }; #endif // !defined(DN_M4) -// NOTE: [$M2x3] DN_M2x3 ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_M2x3 /////////////////////////////////////////////////////////////////////////////////// union DN_M2x3 { DN_F32 e[6]; DN_F32 row[2][3]; }; -// NOTE: [$RECT] DN_Rect ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Rect /////////////////////////////////////////////////////////////////////////////////// #if !defined(DN_NO_RECT) #if defined(DN_NO_V2) #error "Rectangles requires V2, DN_NO_V2 must not be defined" @@ -131,7 +104,7 @@ struct DN_RectCut }; #endif // !defined(DN_NO_RECT) -// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// // NOTE: API struct DN_RaycastLineIntersectV2Result { @@ -141,7 +114,7 @@ struct DN_RaycastLineIntersectV2Result }; #if !defined(DN_NO_V2) -// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V2 ///////////////////////////////////////////////////////////////////////////////////// #define DN_V2I32_Zero DN_LITERAL(DN_V2I32){{(int32_t)(0), (int32_t)(0)}} #define DN_V2I32_One DN_LITERAL(DN_V2I32){{(int32_t)(1), (int32_t)(1)}} #define DN_V2I32_Init1N(x) DN_LITERAL(DN_V2I32){{(int32_t)(x), (int32_t)(x)}} @@ -258,21 +231,21 @@ DN_API DN_V2F32 & operator+= (DN_V2F32& lhs DN_API DN_V2F32 & operator+= (DN_V2F32& lhs, DN_F32 rhs); DN_API DN_V2F32 & operator+= (DN_V2F32& lhs, int32_t rhs); -DN_API DN_V2F32 DN_V2_Min (DN_V2F32 a, DN_V2F32 b); -DN_API DN_V2F32 DN_V2_Max (DN_V2F32 a, DN_V2F32 b); -DN_API DN_V2F32 DN_V2_Abs (DN_V2F32 a); -DN_API DN_F32 DN_V2_Dot (DN_V2F32 a, DN_V2F32 b); -DN_API DN_F32 DN_V2_LengthSq_V2x2 (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_F32 DN_V2_Length_V2x2 (DN_V2F32 lhs, DN_V2F32 rhs); -DN_API DN_F32 DN_V2_LengthSq (DN_V2F32 lhs); -DN_API DN_F32 DN_V2_Length (DN_V2F32 lhs); -DN_API DN_V2F32 DN_V2_Normalise (DN_V2F32 a); -DN_API DN_V2F32 DN_V2_Perpendicular (DN_V2F32 a); -DN_API DN_V2F32 DN_V2_Reflect (DN_V2F32 in, DN_V2F32 surface); -DN_API DN_F32 DN_V2_Area (DN_V2F32 a); +DN_API DN_V2F32 DN_V2F32_Min (DN_V2F32 a, DN_V2F32 b); +DN_API DN_V2F32 DN_V2F32_Max (DN_V2F32 a, DN_V2F32 b); +DN_API DN_V2F32 DN_V2F32_Abs (DN_V2F32 a); +DN_API DN_F32 DN_V2F32_Dot (DN_V2F32 a, DN_V2F32 b); +DN_API DN_F32 DN_V2F32_LengthSq_V2x2 (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_F32 DN_V2F32_Length_V2x2 (DN_V2F32 lhs, DN_V2F32 rhs); +DN_API DN_F32 DN_V2F32_LengthSq (DN_V2F32 lhs); +DN_API DN_F32 DN_V2F32_Length (DN_V2F32 lhs); +DN_API DN_V2F32 DN_V2F32_Normalise (DN_V2F32 a); +DN_API DN_V2F32 DN_V2F32_Perpendicular (DN_V2F32 a); +DN_API DN_V2F32 DN_V2F32_Reflect (DN_V2F32 in, DN_V2F32 surface); +DN_API DN_F32 DN_V2F32_Area (DN_V2F32 a); #endif // !defined(DN_NO_V2) #if !defined(DN_NO_V3) -// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V3 ///////////////////////////////////////////////////////////////////////////////////// #define DN_V3F32_Init1N(x) DN_LITERAL(DN_V3F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} #define DN_V3F32_Init3F32(x, y, z) DN_LITERAL(DN_V3F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z)}} #define DN_V3F32_InitV2F32_1F32(xy, z) DN_LITERAL(DN_V3F32){{(DN_F32)(xy.x), (DN_F32)(xy.y), (DN_F32)(z)}} @@ -305,7 +278,7 @@ DN_API DN_F32 DN_V3F32_Length (DN_V3F32 a); DN_API DN_V3F32 DN_V3F32_Normalise (DN_V3F32 a); #endif // !defined(DN_NO_V3) #if !defined(DN_NO_V4) -// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_V4 ///////////////////////////////////////////////////////////////////////////////////// #define DN_V4F32_Init1N(x) DN_LITERAL(DN_V4F32){{(DN_F32)(x), (DN_F32)(x), (DN_F32)(x), (DN_F32)(x)}} #define DN_V4F32_Init4N(x, y, z, w) DN_LITERAL(DN_V4F32){{(DN_F32)(x), (DN_F32)(y), (DN_F32)(z), (DN_F32)(w)}} #define DN_V4F32_InitV3_1N(xyz, w) DN_LITERAL(DN_V4F32){{xyz.x, xyz.y, xyz.z, w}} @@ -328,7 +301,7 @@ DN_API DN_V4F32 & operator-= (DN_V4F32 &lhs, DN_API DN_V4F32 & operator+= (DN_V4F32 &lhs, DN_V4F32 rhs); #endif // !defined(DN_NO_V4) #if !defined(DN_NO_M4) -// NOTE: [$MAT4] DN_M4 //////////////////////////////////////////////////////////////////////////// +// NOTE: DN_M4 ///////////////////////////////////////////////////////////////////////////////////// DN_API DN_F32 DN_V4F32Dot (DN_V4F32 a, DN_V4F32 b); DN_API DN_M4 DN_M4_Identity (); DN_API DN_M4 DN_M4_ScaleF (DN_F32 x, DN_F32 y, DN_F32 z); @@ -351,7 +324,7 @@ DN_API DN_M4 DN_M4_DivF (DN_M4 lhs, DN_ DN_API DN_FStr8<256> DN_M4_ColumnMajorString (DN_M4 mat); #endif #endif // !defined(DN_NO_M4) -// NOTE: [$M2x3] DN_M2x3 ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_M2x3 /////////////////////////////////////////////////////////////////////////////////// DN_API bool operator== (DN_M2x3 const &lhs, DN_M2x3 const &rhs); DN_API bool operator!= (DN_M2x3 const &lhs, DN_M2x3 const &rhs); DN_API DN_M2x3 DN_M2x3_Identity (); @@ -362,7 +335,7 @@ DN_API DN_M2x3 DN_M2x3_Mul (DN_M2x3 m1, DN DN_API DN_V2F32 DN_M2x3_Mul2F32 (DN_M2x3 m1, DN_F32 x, DN_F32 y); DN_API DN_V2F32 DN_M2x3_MulV2 (DN_M2x3 m1, DN_V2F32 v2); #if !defined(DN_NO_RECT) -// NOTE: [$RECT] DN_Rect ////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Rect /////////////////////////////////////////////////////////////////////////////////// #define DN_Rect_Init2V2(pos, size) DN_LITERAL(DN_Rect){(pos), (size)} #define DN_Rect_Init4N(x, y, w, h) DN_LITERAL(DN_Rect){DN_LITERAL(DN_V2F32){{x, y}}, DN_LITERAL(DN_V2F32){{w, h}}} @@ -405,7 +378,9 @@ DN_API DN_Rect DN_RectCut_Cut (DN_RectCut rec #define DN_RectCut_Top(rect) DN_LITERAL(DN_RectCut){rect, DN_RectCutSide_Top} #define DN_RectCut_Bottom(rect) DN_LITERAL(DN_RectCut){rect, DN_RectCutSide_Bottom} #endif // !defined(DN_NO_RECT) -// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// DN_API DN_RaycastLineIntersectV2Result DN_Raycast_LineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b); DN_API DN_V2F32 DN_Lerp_V2F32 (DN_V2F32 a, DN_F32 t, DN_V2F32 b); DN_API DN_F32 DN_Lerp_F32 (DN_F32 a, DN_F32 t, DN_F32 b); + +#endif // !defined(DN_MATH_H) diff --git a/Extra/dn_tests.cpp b/Extra/dn_tests.cpp new file mode 100644 index 0000000..577d2ec --- /dev/null +++ b/Extra/dn_tests.cpp @@ -0,0 +1,2466 @@ +#if !defined(DN_UT_H) + #error dn_utest.h must be included before this +#endif + +#if !defined(DN_UT_IMPLEMENTATION) + #error DN_UT_IMPLEMENTATION must be defined before dn_utest.h +#endif + +#include + +struct DN_TestsResult +{ + bool passed; + int total_tests; + int total_good_tests; +}; + +enum DN_TestsPrint +{ + DN_TestsPrint_No, + DN_TestsPrint_OnFailure, + DN_TestsPrint_Yes, +}; + +// NOTE: Taken from MSDN __cpuid example implementation +// https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170 +#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) +struct DN_RefImplCPUReport { + unsigned int nIds_ = 0; + unsigned int nExIds_ = 0; + char vendor_[0x20] = {}; + int vendorSize_ = 0; + char brand_[0x40] = {}; + int brandSize_ = 0; + bool isIntel_ = false; + bool isAMD_ = false; + uint32_t f_1_ECX_ = 0; + uint32_t f_1_EDX_ = 0; + uint32_t f_7_EBX_ = 0; + uint32_t f_7_ECX_ = 0; + uint32_t f_81_ECX_ = 0; + uint32_t f_81_EDX_ = 0; + int data_[400][4] = {}; + size_t dataSize_ = 0; + int extdata_[400][4] = {}; + size_t extdataSize_ = 0; + + bool SSE3(void) const { return f_1_ECX_ & (1 << 0); } + bool PCLMULQDQ(void) const { return f_1_ECX_ & (1 << 1); } + bool MONITOR(void) const { return f_1_ECX_ & (1 << 3); } + bool SSSE3(void) const { return f_1_ECX_ & (1 << 9); } + bool FMA(void) const { return f_1_ECX_ & (1 << 12); } + bool CMPXCHG16B(void) const { return f_1_ECX_ & (1 << 13); } + bool SSE41(void) const { return f_1_ECX_ & (1 << 19); } + bool SSE42(void) const { return f_1_ECX_ & (1 << 20); } + bool MOVBE(void) const { return f_1_ECX_ & (1 << 22); } + bool POPCNT(void) const { return f_1_ECX_ & (1 << 23); } + bool AES(void) const { return f_1_ECX_ & (1 << 25); } + bool XSAVE(void) const { return f_1_ECX_ & (1 << 26); } + bool OSXSAVE(void) const { return f_1_ECX_ & (1 << 27); } + bool AVX(void) const { return f_1_ECX_ & (1 << 28); } + bool F16C(void) const { return f_1_ECX_ & (1 << 29); } + bool RDRAND(void) const { return f_1_ECX_ & (1 << 30); } + + bool MSR(void) const { return f_1_EDX_ & (1 << 5); } + bool CX8(void) const { return f_1_EDX_ & (1 << 8); } + bool SEP(void) const { return f_1_EDX_ & (1 << 11); } + bool CMOV(void) const { return f_1_EDX_ & (1 << 15); } + bool CLFSH(void) const { return f_1_EDX_ & (1 << 19); } + bool MMX(void) const { return f_1_EDX_ & (1 << 23); } + bool FXSR(void) const { return f_1_EDX_ & (1 << 24); } + bool SSE(void) const { return f_1_EDX_ & (1 << 25); } + bool SSE2(void) const { return f_1_EDX_ & (1 << 26); } + + bool FSGSBASE(void) const { return f_7_EBX_ & (1 << 0); } + bool BMI1(void) const { return f_7_EBX_ & (1 << 3); } + bool HLE(void) const { return isIntel_ && f_7_EBX_ & (1 << 4); } + bool AVX2(void) const { return f_7_EBX_ & (1 << 5); } + bool BMI2(void) const { return f_7_EBX_ & (1 << 8); } + bool ERMS(void) const { return f_7_EBX_ & (1 << 9); } + bool INVPCID(void) const { return f_7_EBX_ & (1 << 10); } + bool RTM(void) const { return isIntel_ && f_7_EBX_ & (1 << 11); } + bool AVX512F(void) const { return f_7_EBX_ & (1 << 16); } + bool RDSEED(void) const { return f_7_EBX_ & (1 << 18); } + bool ADX(void) const { return f_7_EBX_ & (1 << 19); } + bool AVX512PF(void) const { return f_7_EBX_ & (1 << 26); } + bool AVX512ER(void) const { return f_7_EBX_ & (1 << 27); } + bool AVX512CD(void) const { return f_7_EBX_ & (1 << 28); } + bool SHA(void) const { return f_7_EBX_ & (1 << 29); } + + bool PREFETCHWT1(void) const { return f_7_ECX_ & (1 << 0); } + + bool LAHF(void) const { return f_81_ECX_ & (1 << 0); } + bool LZCNT(void) const { return isIntel_ && f_81_ECX_ & (1 << 5); } + bool ABM(void) const { return isAMD_ && f_81_ECX_ & (1 << 5); } + bool SSE4a(void) const { return isAMD_ && f_81_ECX_ & (1 << 6); } + bool XOP(void) const { return isAMD_ && f_81_ECX_ & (1 << 11); } + bool TBM(void) const { return isAMD_ && f_81_ECX_ & (1 << 21); } + + bool SYSCALL(void) const { return isIntel_ && f_81_EDX_ & (1 << 11); } + bool MMXEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 22); } + bool RDTSCP(void) const { return f_81_EDX_ & (1 << 27); } + bool _3DNOWEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 30); } + bool _3DNOW(void) const { return isAMD_ && f_81_EDX_ & (1 << 31); } +}; + +static DN_RefImplCPUReport DN_RefImplCPUReport_Init() +{ + DN_RefImplCPUReport result = {}; + + // int cpuInfo[4] = {-1}; + int cpui[4]; + + // Calling __cpuid with 0x0 as the function_id argument + // gets the number of the highest valid function ID. + __cpuid(cpui, 0); + result.nIds_ = cpui[0]; + + for (unsigned int i = 0; i <= result.nIds_; ++i) { + __cpuidex(cpui, i, 0); + memcpy(result.data_[result.dataSize_++], cpui, sizeof(cpui)); + } + + // Capture vendor string + *reinterpret_cast(result.vendor_) = result.data_[0][1]; + *reinterpret_cast(result.vendor_ + 4) = result.data_[0][3]; + *reinterpret_cast(result.vendor_ + 8) = result.data_[0][2]; + result.vendorSize_ = (int)strlen(result.vendor_); + + if (strcmp(result.vendor_, "GenuineIntel") == 0) + result.isIntel_ = true; + else if (strcmp(result.vendor_, "AuthenticAMD") == 0) + result.isAMD_ = true; + + // load bitset with flags for function 0x00000001 + if (result.nIds_ >= 1) { + result.f_1_ECX_ = result.data_[1][2]; + result.f_1_EDX_ = result.data_[1][3]; + } + + // load bitset with flags for function 0x00000007 + if (result.nIds_ >= 7) { + result.f_7_EBX_ = result.data_[7][1]; + result.f_7_ECX_ = result.data_[7][2]; + } + + // Calling __cpuid with 0x80000000 as the function_id argument + // gets the number of the highest valid extended ID. + __cpuid(cpui, 0x80000000); + result.nExIds_ = cpui[0]; + + for (unsigned int i = 0x80000000; i <= result.nExIds_; ++i) { + __cpuidex(cpui, i, 0); + memcpy(result.extdata_[result.extdataSize_++], cpui, sizeof(cpui)); + } + + // load bitset with flags for function 0x80000001 + if (result.nExIds_ >= 0x80000001) { + result.f_81_ECX_ = result.extdata_[1][2]; + result.f_81_EDX_ = result.extdata_[1][3]; + } + + // Interpret CPU brand string if reported + if (result.nExIds_ >= 0x80000004) { + memcpy(result.brand_, result.extdata_[2], sizeof(cpui)); + memcpy(result.brand_ + 16, result.extdata_[3], sizeof(cpui)); + memcpy(result.brand_ + 32, result.extdata_[4], sizeof(cpui)); + result.brandSize_ = (int)strlen(result.brand_); + } + + return result; +} + +#if 0 +static void DN_RefImpl_CPUReportDump() // Print out supported instruction set features +{ + auto support_message = [](std::string isa_feature, bool is_supported) { + printf("%s %s\n", isa_feature.c_str(), is_supported ? "supported" : "not supported"); + }; + + printf("%s\n", DN_RefImplCPUReport::Vendor().c_str()); + printf("%s\n", DN_RefImplCPUReport::Brand().c_str()); + + support_message("3DNOW", DN_RefImplCPUReport::_3DNOW()); + support_message("3DNOWEXT", DN_RefImplCPUReport::_3DNOWEXT()); + support_message("ABM", DN_RefImplCPUReport::ABM()); + support_message("ADX", DN_RefImplCPUReport::ADX()); + support_message("AES", DN_RefImplCPUReport::AES()); + support_message("AVX", DN_RefImplCPUReport::AVX()); + support_message("AVX2", DN_RefImplCPUReport::AVX2()); + support_message("AVX512CD", DN_RefImplCPUReport::AVX512CD()); + support_message("AVX512ER", DN_RefImplCPUReport::AVX512ER()); + support_message("AVX512F", DN_RefImplCPUReport::AVX512F()); + support_message("AVX512PF", DN_RefImplCPUReport::AVX512PF()); + support_message("BMI1", DN_RefImplCPUReport::BMI1()); + support_message("BMI2", DN_RefImplCPUReport::BMI2()); + support_message("CLFSH", DN_RefImplCPUReport::CLFSH()); + support_message("CMPXCHG16B", DN_RefImplCPUReport::CMPXCHG16B()); + support_message("CX8", DN_RefImplCPUReport::CX8()); + support_message("ERMS", DN_RefImplCPUReport::ERMS()); + support_message("F16C", DN_RefImplCPUReport::F16C()); + support_message("FMA", DN_RefImplCPUReport::FMA()); + support_message("FSGSBASE", DN_RefImplCPUReport::FSGSBASE()); + support_message("FXSR", DN_RefImplCPUReport::FXSR()); + support_message("HLE", DN_RefImplCPUReport::HLE()); + support_message("INVPCID", DN_RefImplCPUReport::INVPCID()); + support_message("LAHF", DN_RefImplCPUReport::LAHF()); + support_message("LZCNT", DN_RefImplCPUReport::LZCNT()); + support_message("MMX", DN_RefImplCPUReport::MMX()); + support_message("MMXEXT", DN_RefImplCPUReport::MMXEXT()); + support_message("MONITOR", DN_RefImplCPUReport::MONITOR()); + support_message("MOVBE", DN_RefImplCPUReport::MOVBE()); + support_message("MSR", DN_RefImplCPUReport::MSR()); + support_message("OSXSAVE", DN_RefImplCPUReport::OSXSAVE()); + support_message("PCLMULQDQ", DN_RefImplCPUReport::PCLMULQDQ()); + support_message("POPCNT", DN_RefImplCPUReport::POPCNT()); + support_message("PREFETCHWT1", DN_RefImplCPUReport::PREFETCHWT1()); + support_message("RDRAND", DN_RefImplCPUReport::RDRAND()); + support_message("RDSEED", DN_RefImplCPUReport::RDSEED()); + support_message("RDTSCP", DN_RefImplCPUReport::RDTSCP()); + support_message("RTM", DN_RefImplCPUReport::RTM()); + support_message("SEP", DN_RefImplCPUReport::SEP()); + support_message("SHA", DN_RefImplCPUReport::SHA()); + support_message("SSE", DN_RefImplCPUReport::SSE()); + support_message("SSE2", DN_RefImplCPUReport::SSE2()); + support_message("SSE3", DN_RefImplCPUReport::SSE3()); + support_message("SSE4.1", DN_RefImplCPUReport::SSE41()); + support_message("SSE4.2", DN_RefImplCPUReport::SSE42()); + support_message("SSE4a", DN_RefImplCPUReport::SSE4a()); + support_message("SSSE3", DN_RefImplCPUReport::SSSE3()); + support_message("SYSCALL", DN_RefImplCPUReport::SYSCALL()); + support_message("TBM", DN_RefImplCPUReport::TBM()); + support_message("XOP", DN_RefImplCPUReport::XOP()); + support_message("XSAVE", DN_RefImplCPUReport::XSAVE()); +}; +#endif +#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + +static DN_UTCore DN_Tests_Base() +{ + DN_UTCore result = DN_UT_Init(); +#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); + DN_UT_LogF(&result, "DN_Base\n"); + { + DN_UT_Test(&result, "Query CPUID") + { + DN_CPUReport cpu_report = DN_CPU_Report(); + + // NOTE: Sanity check our report against MSDN's example //////////////////////////////////////// + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); + +// NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example ///////////// +#if 0 + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP()); + DN_UT_Assert(&result, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE()); +#endif + } + } +#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + return result; +} + +static DN_UTCore DN_Tests_Arena() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_Arena\n"); + { + DN_UT_Test(&result, "Reused memory is zeroed out") + { + uint8_t alignment = 1; + DN_USize alloc_size = DN_Kilobytes(128); + DN_Arena arena = DN_Arena_InitFromOSVMem(0, 0, DN_ArenaFlags_Nil); + DN_DEFER + { + DN_Arena_Deinit(&arena); + }; + + // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena + uintptr_t first_ptr_address = 0; + { + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(&arena); + void *ptr = DN_Arena_Alloc(&arena, alloc_size, alignment, DN_ZeroMem_Yes); + first_ptr_address = DN_CAST(uintptr_t) ptr; + DN_Memset(ptr, 'z', alloc_size); + DN_Arena_TempMemEnd(temp_mem); + } + + // NOTE: Reallocate 128 kilobytes + char *ptr = DN_CAST(char *) DN_Arena_Alloc(&arena, alloc_size, alignment, DN_ZeroMem_Yes); + + // NOTE: Double check we got the same pointer + DN_UT_Assert(&result, first_ptr_address == DN_CAST(uintptr_t) ptr); + + // NOTE: Check that the bytes are set to 0 + for (DN_USize i = 0; i < alloc_size; i++) + DN_UT_Assert(&result, ptr[i] == 0); + } + + DN_UT_Test(&result, "Test arena grows naturally, 1mb + 4mb") + { + // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow + DN_Arena arena = DN_Arena_InitFromOSVMem(DN_Megabytes(2), DN_Megabytes(2), DN_ArenaFlags_Nil); + DN_DEFER + { + DN_Arena_Deinit(&arena); + }; + + char *ptr_1mb = DN_Arena_NewArray(&arena, char, DN_Megabytes(1), DN_ZeroMem_Yes); + char *ptr_4mb = DN_Arena_NewArray(&arena, char, DN_Megabytes(4), DN_ZeroMem_Yes); + DN_UT_Assert(&result, ptr_1mb); + DN_UT_Assert(&result, ptr_4mb); + + DN_ArenaBlock const *block_4mb_begin = arena.curr; + char const *block_4mb_end = DN_CAST(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; + DN_UT_AssertF(&result, block_1mb_begin, "New block should have been allocated"); + char const *block_1mb_end = DN_CAST(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_UT_AssertF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_UT_AssertF(&result, ptr_1mb >= DN_CAST(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_UT_AssertF(&result, ptr_4mb >= DN_CAST(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + + DN_UT_Test(&result, "Test arena grows naturally, 1mb, temp memory 4mb") + { + DN_Arena arena = DN_Arena_InitFromOSVMem(DN_Megabytes(2), DN_Megabytes(2), DN_ArenaFlags_Nil); + DN_DEFER + { + DN_Arena_Deinit(&arena); + }; + + // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow + char *ptr_1mb = DN_CAST(char *) DN_Arena_Alloc(&arena, DN_Megabytes(1), 1 /*align*/, DN_ZeroMem_Yes); + DN_UT_Assert(&result, ptr_1mb); + + DN_ArenaTempMem temp_memory = DN_Arena_TempMemBegin(&arena); + { + char *ptr_4mb = DN_Arena_NewArray(&arena, char, DN_Megabytes(4), DN_ZeroMem_Yes); + DN_UT_Assert(&result, ptr_4mb); + + DN_ArenaBlock const *block_4mb_begin = arena.curr; + char const *block_4mb_end = DN_CAST(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; + char const *block_1mb_end = DN_CAST(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_UT_AssertF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_UT_AssertF(&result, ptr_1mb >= DN_CAST(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_UT_AssertF(&result, ptr_4mb >= DN_CAST(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + DN_Arena_TempMemEnd(temp_memory); + DN_UT_Assert(&result, arena.curr->prev == nullptr); + DN_UT_AssertF(&result, + arena.curr->reserve >= DN_Megabytes(1), + "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", + (arena.curr->reserve / 1024 / 1024), + arena.curr->reserve, + DN_Megabytes(1)); + } + } + return result; +} + +static DN_UTCore DN_Tests_Bin() +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_UTCore test = DN_UT_Init(); + DN_UT_LogF(&test, "DN_Bin\n"); + { + DN_UT_Test(&test, "Convert 0x123") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("0x123")); + DN_UT_AssertF(&test, result == 0x123, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert 0xFFFF") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("0xFFFF")); + DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert FFFF") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("FFFF")); + DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert abCD") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("abCD")); + DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert 0xabCD") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("0xabCD")); + DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert 0x") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("0x")); + DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert 0X") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("0X")); + DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert 3") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("3")); + DN_UT_AssertF(&test, result == 3, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert f") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("f")); + DN_UT_AssertF(&test, result == 0xf, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert g") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("g")); + DN_UT_AssertF(&test, result == 0, "result: %" PRIu64, result); + } + + DN_UT_Test(&test, "Convert -0x3") + { + uint64_t result = DN_CVT_HexToU64(DN_STR8("-0x3")); + DN_UT_AssertF(&test, result == 0, "result: %" PRIu64, result); + } + + uint32_t number = 0xd095f6; + DN_UT_Test(&test, "Convert %x to string", number) + { + DN_Str8 number_hex = DN_CVT_BytesToHex(tmem.arena, &number, sizeof(number)); + DN_UT_AssertF(&test, DN_Str8_Eq(number_hex, DN_STR8("f695d000")), "number_hex=%.*s", DN_STR_FMT(number_hex)); + } + + number = 0xf6ed00; + DN_UT_Test(&test, "Convert %x to string", number) + { + DN_Str8 number_hex = DN_CVT_BytesToHex(tmem.arena, &number, sizeof(number)); + DN_UT_AssertF(&test, DN_Str8_Eq(number_hex, DN_STR8("00edf600")), "number_hex=%.*s", DN_STR_FMT(number_hex)); + } + + DN_Str8 hex = DN_STR8("0xf6ed00"); + DN_UT_Test(&test, "Convert %.*s to bytes", DN_STR_FMT(hex)) + { + DN_Str8 bytes = DN_CVT_HexToBytes(tmem.arena, hex); + DN_UT_AssertF(&test, + DN_Str8_Eq(bytes, DN_STR8("\xf6\xed\x00")), + "number_hex=%.*s", + DN_STR_FMT(DN_CVT_BytesToHex(tmem.arena, bytes.data, bytes.size))); + } + } + return test; +} + +static DN_UTCore DN_Tests_BinarySearch() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_BinarySearch\n"); + { + DN_UT_Test(&result, "Search array of 1 item") + { + uint32_t array[] = {1}; + DN_BinarySearchResult search = {}; + + // NOTE: Match ============================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + // NOTE: Lower bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + // NOTE: Upper bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + } + + DN_UT_Test(&result, "Search array of 2 items") + { + uint32_t array[] = {1}; + DN_BinarySearchResult search = {}; + + // NOTE: Match ============================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + // NOTE: Lower bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + // NOTE: Upper bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 1); + } + + DN_UT_Test(&result, "Search array of 3 items") + { + uint32_t array[] = {1, 2, 3}; + DN_BinarySearchResult search = {}; + + // NOTE: Match ============================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 3); + + // NOTE: Lower bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 3); + + // NOTE: Upper bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 3); + } + + DN_UT_Test(&result, "Search array of 4 items") + { + uint32_t array[] = {1, 2, 3, 4}; + DN_BinarySearchResult search = {}; + + // NOTE: Match ============================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 4); + + // NOTE: Lower bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 4); + + // NOTE: Upper bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 5U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 4); + } + + DN_UT_Test(&result, "Search array with duplicate items") + { + uint32_t array[] = {1, 1, 2, 2, 3}; + DN_BinarySearchResult search = {}; + + // NOTE: Match ============================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_Match); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 5); + + // NOTE: Lower bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U /*find*/, DN_BinarySearchType_LowerBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 5); + + // NOTE: Upper bound ======================================================================= + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, search.found); + DN_UT_Assert(&result, search.index == 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U /*find*/, DN_BinarySearchType_UpperBound); + DN_UT_Assert(&result, !search.found); + DN_UT_Assert(&result, search.index == 5); + } + } + return result; +} + +static DN_UTCore DN_Tests_DSMap() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_DSMap\n"); + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + { + DN_Arena arena = DN_Arena_InitFromOSVMem(0, 0, DN_ArenaFlags_Nil); + uint32_t const MAP_SIZE = 64; + DN_DSMap map = DN_DSMap_Init(&arena, MAP_SIZE, DN_DSMapFlags_Nil); + DN_DEFER + { + DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); + }; + + DN_UT_Test(&result, "Find non-existent value") + { + DN_DSMapResult find = DN_DSMap_FindKeyStr8(&map, DN_STR8("Foo")); + DN_UT_Assert(&result, !find.found); + DN_UT_Assert(&result, map.size == MAP_SIZE); + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/); + } + + DN_DSMapKey key = DN_DSMap_KeyCStr8(&map, "Bar"); + DN_UT_Test(&result, "Insert value and lookup") + { + uint64_t desired_value = 0xF00BAA; + uint64_t *slot_value = DN_DSMap_Set(&map, key, desired_value).value; + DN_UT_Assert(&result, slot_value); + DN_UT_Assert(&result, map.size == MAP_SIZE); + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.occupied == 2); + + uint64_t *value = DN_DSMap_Find(&map, key).value; + DN_UT_Assert(&result, value); + DN_UT_Assert(&result, *value == desired_value); + } + + DN_UT_Test(&result, "Remove key") + { + DN_DSMap_Erase(&map, key); + DN_UT_Assert(&result, map.size == MAP_SIZE); + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/); + } + } + + enum DSMapTestType + { + DSMapTestType_Set, + DSMapTestType_MakeSlot, + DSMapTestType_Count + }; + + for (int result_type = 0; result_type < DSMapTestType_Count; result_type++) { + DN_Str8 prefix = {}; + switch (result_type) { + case DSMapTestType_Set: prefix = DN_STR8("Set"); break; + case DSMapTestType_MakeSlot: prefix = DN_STR8("Make slot"); break; + } + + DN_ArenaTempMemScope temp_mem_scope = DN_ArenaTempMemScope(tmem.arena); + DN_Arena arena = DN_Arena_InitFromOSVMem(0, 0, DN_ArenaFlags_Nil); + uint32_t const MAP_SIZE = 64; + DN_DSMap map = DN_DSMap_Init(&arena, MAP_SIZE, DN_DSMapFlags_Nil); + DN_DEFER + { + DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); + }; + + DN_UT_Test(&result, "%.*s: Test growing", DN_STR_FMT(prefix)) + { + uint64_t map_start_size = map.size; + uint64_t value = 0; + uint64_t grow_threshold = map_start_size * 3 / 4; + for (; map.occupied != grow_threshold; value++) { + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); + DN_UT_Assert(&result, !DN_DSMap_Find(&map, key).found); + DN_DSMapResult make_result = {}; + if (result_type == DSMapTestType_Set) + make_result = DN_DSMap_Set(&map, key, value); + else + make_result = DN_DSMap_Make(&map, key); + DN_UT_Assert(&result, !make_result.found); + DN_UT_Assert(&result, DN_DSMap_Find(&map, key).value); + } + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.size == map_start_size); + DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/ + value); + + { // NOTE: One more item should cause the table to grow by 2x + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); + DN_DSMapResult make_result = {}; + if (result_type == DSMapTestType_Set) + make_result = DN_DSMap_Set(&map, key, value); + else + make_result = DN_DSMap_Make(&map, key); + + value++; + DN_UT_Assert(&result, !make_result.found); + DN_UT_Assert(&result, map.size == map_start_size * 2); + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/ + value); + } + } + + DN_UT_Test(&result, "%.*s: Check the sentinel is present", DN_STR_FMT(prefix)) + { + DN_DSMapSlot NIL_SLOT = {}; + DN_DSMapSlot sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT]; + DN_UT_Assert(&result, DN_Memcmp(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0); + } + + DN_UT_Test(&result, "%.*s: Recheck all the hash tables values after growing", DN_STR_FMT(prefix)) + { + for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) { + DN_DSMapSlot const *slot = map.slots + index; + + // NOTE: Validate each slot value + uint64_t value_result = index - 1; + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value_result); + DN_UT_Assert(&result, DN_DSMap_KeyEquals(slot->key, key)); + if (result_type == DSMapTestType_Set) + DN_UT_Assert(&result, slot->value == value_result); + else + DN_UT_Assert(&result, slot->value == 0); // NOTE: Make slot does not set the key so should be 0 + DN_UT_Assert(&result, slot->key.hash == DN_DSMap_Hash(&map, slot->key)); + + // NOTE: Check the reverse lookup is correct + DN_DSMapResult check = DN_DSMap_Find(&map, slot->key); + DN_UT_Assert(&result, slot->value == *check.value); + } + } + + DN_UT_Test(&result, "%.*s: Test shrinking", DN_STR_FMT(prefix)) + { + uint64_t start_map_size = map.size; + uint64_t start_map_occupied = map.occupied; + uint64_t value = 0; + uint64_t shrink_threshold = map.size * 1 / 4; + for (; map.occupied != shrink_threshold; value++) { + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); + DN_UT_Assert(&result, DN_DSMap_Find(&map, key).found); + DN_DSMap_Erase(&map, key); + DN_UT_Assert(&result, !DN_DSMap_Find(&map, key).found); + } + DN_UT_Assert(&result, map.size == start_map_size); + DN_UT_Assert(&result, map.occupied == start_map_occupied - value); + + { // NOTE: One more item should cause the table to shrink by 2x + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); + DN_DSMap_Erase(&map, key); + value++; + + DN_UT_Assert(&result, map.size == start_map_size / 2); + DN_UT_Assert(&result, map.occupied == start_map_occupied - value); + } + + { // NOTE: Check the sentinel is present + DN_DSMapSlot NIL_SLOT = {}; + DN_DSMapSlot sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT]; + DN_UT_Assert(&result, DN_Memcmp(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0); + } + + // NOTE: Recheck all the hash table values after shrinking + for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) { + // NOTE: Generate the key + uint64_t value_result = value + (index - 1); + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value_result); + + // NOTE: Validate each slot value + DN_DSMapResult find_result = DN_DSMap_Find(&map, key); + DN_UT_Assert(&result, find_result.value); + DN_UT_Assert(&result, find_result.slot->key == key); + if (result_type == DSMapTestType_Set) + DN_UT_Assert(&result, *find_result.value == value_result); + else + DN_UT_Assert(&result, *find_result.value == 0); // NOTE: Make slot does not set the key so should be 0 + DN_UT_Assert(&result, find_result.slot->key.hash == DN_DSMap_Hash(&map, find_result.slot->key)); + + // NOTE: Check the reverse lookup is correct + DN_DSMapResult check = DN_DSMap_Find(&map, find_result.slot->key); + DN_UT_Assert(&result, *find_result.value == *check.value); + } + + for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table + DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); + DN_UT_Assert(&result, DN_DSMap_Find(&map, key).found); + DN_DSMap_Erase(&map, key); + DN_UT_Assert(&result, !DN_DSMap_Find(&map, key).found); + } + DN_UT_Assert(&result, map.initial_size == MAP_SIZE); + DN_UT_Assert(&result, map.size == map.initial_size); + DN_UT_Assert(&result, map.occupied == 1 /*Sentinel*/); + } + } + } + return result; +} + +static DN_UTCore DN_Tests_FStr8() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_FStr8\n"); + { + DN_UT_Test(&result, "Append too much fails") + { + DN_FStr8<4> str = {}; + DN_UT_Assert(&result, !DN_FStr8_Add(&str, DN_STR8("abcde"))); + } + + DN_UT_Test(&result, "Append format string too much fails") + { + DN_FStr8<4> str = {}; + DN_UT_Assert(&result, !DN_FStr8_AddF(&str, "abcde")); + } + } + return result; +} + +static DN_UTCore DN_Tests_FArray() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_FArray\n"); + { + DN_UT_Test(&result, "Initialise from raw array") + { + int raw_array[] = {1, 2}; + auto array = DN_FArray_Init(raw_array, DN_ArrayCountU(raw_array)); + DN_UT_Assert(&result, array.size == 2); + DN_UT_Assert(&result, array.data[0] == 1); + DN_UT_Assert(&result, array.data[1] == 2); + } + + DN_UT_Test(&result, "Erase stable 1 element from array") + { + int raw_array[] = {1, 2, 3}; + auto array = DN_FArray_Init(raw_array, DN_ArrayCountU(raw_array)); + DN_FArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Stable); + DN_UT_Assert(&result, array.size == 2); + DN_UT_Assert(&result, array.data[0] == 1); + DN_UT_Assert(&result, array.data[1] == 3); + } + + DN_UT_Test(&result, "Erase unstable 1 element from array") + { + int raw_array[] = {1, 2, 3}; + auto array = DN_FArray_Init(raw_array, DN_ArrayCountU(raw_array)); + DN_FArray_EraseRange(&array, 0 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Unstable); + DN_UT_Assert(&result, array.size == 2); + DN_UT_Assert(&result, array.data[0] == 3); + DN_UT_Assert(&result, array.data[1] == 2); + } + + DN_UT_Test(&result, "Add 1 element to array") + { + int const ITEM = 2; + int raw_array[] = {1}; + auto array = DN_FArray_Init(raw_array, DN_ArrayCountU(raw_array)); + DN_FArray_Add(&array, ITEM); + DN_UT_Assert(&result, array.size == 2); + DN_UT_Assert(&result, array.data[0] == 1); + DN_UT_Assert(&result, array.data[1] == ITEM); + } + + DN_UT_Test(&result, "Clear array") + { + int raw_array[] = {1}; + auto array = DN_FArray_Init(raw_array, DN_ArrayCountU(raw_array)); + DN_FArray_Clear(&array); + DN_UT_Assert(&result, array.size == 0); + } + } + return result; +} + +static DN_UTCore DN_Tests_Intrinsics() +{ + DN_UTCore result = DN_UT_Init(); + // TODO(dn): We don't have meaningful results here, but since + // atomics/intrinsics are implemented using macros we ensure the macro was + // written properly with these results. + + DN_MSVC_WARNING_PUSH + + // NOTE: MSVC SAL complains that we are using Interlocked functionality on + // variables it has detected as *not* being shared across threads. This is + // fine, we're just running some basic results, so permit it. + // + // Warning 28112 is a knock-on effect of this that it doesn't like us + // reading the value of the variable that has been used in an Interlocked + // function locally. + DN_MSVC_WARNING_DISABLE(28113) // Accessing a local variable val via an Interlocked function. + DN_MSVC_WARNING_DISABLE(28112) // A variable (val) which is accessed via an Interlocked function must always be accessed via an Interlocked function. See line 759. + + DN_UT_LogF(&result, "DN_Atomic\n"); + { + DN_UT_Test(&result, "DN_Atomic_AddU32") + { + uint32_t val = 0; + DN_Atomic_AddU32(&val, 1); + DN_UT_AssertF(&result, val == 1, "val: %u", val); + } + + DN_UT_Test(&result, "DN_Atomic_AddU64") + { + uint64_t val = 0; + DN_Atomic_AddU64(&val, 1); + DN_UT_AssertF(&result, val == 1, "val: %" PRIu64, val); + } + + DN_UT_Test(&result, "DN_Atomic_SubU32") + { + uint32_t val = 1; + DN_Atomic_SubU32(&val, 1); + DN_UT_AssertF(&result, val == 0, "val: %u", val); + } + + DN_UT_Test(&result, "DN_Atomic_SubU64") + { + uint64_t val = 1; + DN_Atomic_SubU64(&val, 1); + DN_UT_AssertF(&result, val == 0, "val: %" PRIu64, val); + } + + DN_UT_Test(&result, "DN_Atomic_SetValue32") + { + long a = 0; + long b = 111; + DN_Atomic_SetValue32(&a, b); + DN_UT_AssertF(&result, a == b, "a: %ld, b: %ld", a, b); + } + + DN_UT_Test(&result, "DN_Atomic_SetValue64") + { + int64_t a = 0; + int64_t b = 111; + DN_Atomic_SetValue64(DN_CAST(uint64_t *) & a, b); + DN_UT_AssertF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); + } + + DN_UT_BeginF(&result, "DN_CPU_TSC"); + DN_CPU_TSC(); + DN_UT_End(&result); + + DN_UT_BeginF(&result, "DN_CompilerReadBarrierAndCPUReadFence"); + DN_CompilerReadBarrierAndCPUReadFence; + DN_UT_End(&result); + + DN_UT_BeginF(&result, "DN_CompilerWriteBarrierAndCPUWriteFence"); + DN_CompilerWriteBarrierAndCPUWriteFence; + DN_UT_End(&result); + } + DN_MSVC_WARNING_POP + + return result; +} + +#if defined(DN_UNIT_TESTS_WITH_KECCAK) +DN_GCC_WARNING_PUSH +DN_GCC_WARNING_DISABLE(-Wunused-parameter) +DN_GCC_WARNING_DISABLE(-Wsign-compare) + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(4244) +DN_MSVC_WARNING_DISABLE(4100) +DN_MSVC_WARNING_DISABLE(6385) +// NOTE: Keccak Reference Implementation /////////////////////////////////////////////////////////// +// A very compact Keccak implementation taken from the reference implementation +// repository +// +// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c + + #define FOR(i, n) for (i = 0; i < n; ++i) +void DN_RefImpl_Keccak_(int r, int c, const uint8_t *in, uint64_t inLen, uint8_t sfx, uint8_t *out, uint64_t outLen); + +void DN_RefImpl_FIPS202_SHAKE128_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen) +{ + DN_RefImpl_Keccak_(1344, 256, in, inLen, 0x1F, out, outLen); +} + +void DN_RefImpl_FIPS202_SHAKE256_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen) +{ + DN_RefImpl_Keccak_(1088, 512, in, inLen, 0x1F, out, outLen); +} + +void DN_RefImpl_FIPS202_SHA3_224_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_RefImpl_Keccak_(1152, 448, in, inLen, 0x06, out, 28); +} + +void DN_RefImpl_FIPS202_SHA3_256_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_RefImpl_Keccak_(1088, 512, in, inLen, 0x06, out, 32); +} + +void DN_RefImpl_FIPS202_SHA3_384_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_RefImpl_Keccak_(832, 768, in, inLen, 0x06, out, 48); +} + +void DN_RefImpl_FIPS202_SHA3_512_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_RefImpl_Keccak_(576, 1024, in, inLen, 0x06, out, 64); +} + +int DN_RefImpl_LFSR86540_(uint8_t *R) +{ + (*R) = ((*R) << 1) ^ (((*R) & 0x80) ? 0x71 : 0); + return ((*R) & 2) >> 1; +} + + #define ROL(a, o) ((((uint64_t)a) << o) ^ (((uint64_t)a) >> (64 - o))) + +static uint64_t DN_RefImpl_load64_(const uint8_t *x) +{ + int i; + uint64_t u = 0; + FOR(i, 8) + { + u <<= 8; + u |= x[7 - i]; + } + return u; +} + +static void DN_RefImpl_store64_(uint8_t *x, uint64_t u) +{ + int i; + FOR(i, 8) + { + x[i] = u; + u >>= 8; + } +} + +static void DN_RefImpl_xor64_(uint8_t *x, uint64_t u) +{ + int i; + FOR(i, 8) + { + x[i] ^= u; + u >>= 8; + } +} + + #define rL(x, y) DN_RefImpl_load64_((uint8_t *)s + 8 * (x + 5 * y)) + #define wL(x, y, l) DN_RefImpl_store64_((uint8_t *)s + 8 * (x + 5 * y), l) + #define XL(x, y, l) DN_RefImpl_xor64_((uint8_t *)s + 8 * (x + 5 * y), l) + +void DN_RefImpl_Keccak_F1600(void *s) +{ + int r, x, y, i, j, Y; + uint8_t R = 0x01; + uint64_t C[5], D; + for (i = 0; i < 24; i++) { + /*??*/ FOR(x, 5) C[x] = rL(x, 0) ^ rL(x, 1) ^ rL(x, 2) ^ rL(x, 3) ^ rL(x, 4); + FOR(x, 5) + { + D = C[(x + 4) % 5] ^ ROL(C[(x + 1) % 5], 1); + FOR(y, 5) + XL(x, y, D); + } + /*????*/ x = 1; + y = r = 0; + D = rL(x, y); + FOR(j, 24) + { + r += j + 1; + Y = (2 * x + 3 * y) % 5; + x = y; + y = Y; + C[0] = rL(x, y); + wL(x, y, ROL(D, r % 64)); + D = C[0]; + } + /*??*/ FOR(y, 5) + { + FOR(x, 5) + C[x] = rL(x, y); + FOR(x, 5) + wL(x, y, C[x] ^ ((~C[(x + 1) % 5]) & C[(x + 2) % 5])); + } + /*??*/ FOR(j, 7) if (DN_RefImpl_LFSR86540_(&R)) XL(0, 0, (uint64_t)1 << ((1 << j) - 1)); + } +} + +void DN_RefImpl_Keccak_(int r, int c, const uint8_t *in, uint64_t inLen, uint8_t sfx, uint8_t *out, uint64_t outLen) +{ + /*initialize*/ uint8_t s[200]; + int R = r / 8; + int i, b = 0; + FOR(i, 200) + s[i] = 0; + /*absorb*/ while (inLen > 0) { + b = (inLen < R) ? inLen : R; + FOR(i, b) + s[i] ^= in[i]; + in += b; + inLen -= b; + if (b == R) { + DN_RefImpl_Keccak_F1600(s); + b = 0; + } + } + /*pad*/ s[b] ^= sfx; + if ((sfx & 0x80) && (b == (R - 1))) + DN_RefImpl_Keccak_F1600(s); + s[R - 1] ^= 0x80; + DN_RefImpl_Keccak_F1600(s); + /*squeeze*/ while (outLen > 0) { + b = (outLen < R) ? outLen : R; + FOR(i, b) + out[i] = s[i]; + out += b; + outLen -= b; + if (outLen > 0) + DN_RefImpl_Keccak_F1600(s); + } +} + + #undef XL + #undef wL + #undef rL + #undef ROL + #undef FOR +DN_MSVC_WARNING_POP +DN_GCC_WARNING_POP + + #define DN_KC_IMPLEMENTATION + #include "../Standalone/dn_keccak.h" + + #define DN_UT_HASH_X_MACRO \ + DN_UT_HASH_X_ENTRY(SHA3_224, "SHA3-224") \ + DN_UT_HASH_X_ENTRY(SHA3_256, "SHA3-256") \ + DN_UT_HASH_X_ENTRY(SHA3_384, "SHA3-384") \ + DN_UT_HASH_X_ENTRY(SHA3_512, "SHA3-512") \ + DN_UT_HASH_X_ENTRY(Keccak_224, "Keccak-224") \ + DN_UT_HASH_X_ENTRY(Keccak_256, "Keccak-256") \ + DN_UT_HASH_X_ENTRY(Keccak_384, "Keccak-384") \ + DN_UT_HASH_X_ENTRY(Keccak_512, "Keccak-512") \ + DN_UT_HASH_X_ENTRY(Count, "Keccak-512") + +enum DN_Tests__HashType +{ + + #define DN_UT_HASH_X_ENTRY(enum_val, string) Hash_##enum_val, + DN_UT_HASH_X_MACRO + #undef DN_UT_HASH_X_ENTRY +}; + +DN_Str8 const DN_UT_HASH_STRING_[] = + { + #define DN_UT_HASH_X_ENTRY(enum_val, string) DN_STR8(string), + DN_UT_HASH_X_MACRO + #undef DN_UT_HASH_X_ENTRY +}; + +void DN_Tests_KeccakDispatch_(DN_UTCore *test, int hash_type, DN_Str8 input) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 input_hex = DN_CVT_BytesToHex(tmem.arena, input.data, input.size); + + switch (hash_type) { + case Hash_SHA3_224: { + DN_KCBytes28 hash = DN_KC_SHA3_224Str8(input); + DN_KCBytes28 expect; + DN_RefImpl_FIPS202_SHA3_224_(DN_CAST(uint8_t *) input.data, input.size, (uint8_t *)expect.data); + DN_UT_AssertF(test, + DN_KC_Bytes28Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING56_FMT(DN_KC_Bytes28ToHex(&hash).data), + DN_KC_STRING56_FMT(DN_KC_Bytes28ToHex(&expect).data)); + } break; + + case Hash_SHA3_256: { + DN_KCBytes32 hash = DN_KC_SHA3_256Str8(input); + DN_KCBytes32 expect; + DN_RefImpl_FIPS202_SHA3_256_(DN_CAST(uint8_t *) input.data, input.size, (uint8_t *)expect.data); + DN_UT_AssertF(test, + DN_KC_Bytes32Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING64_FMT(DN_KC_Bytes32ToHex(&hash).data), + DN_KC_STRING64_FMT(DN_KC_Bytes32ToHex(&expect).data)); + } break; + + case Hash_SHA3_384: { + DN_KCBytes48 hash = DN_KC_SHA3_384Str8(input); + DN_KCBytes48 expect; + DN_RefImpl_FIPS202_SHA3_384_(DN_CAST(uint8_t *) input.data, input.size, (uint8_t *)expect.data); + DN_UT_AssertF(test, + DN_KC_Bytes48Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING96_FMT(DN_KC_Bytes48ToHex(&hash).data), + DN_KC_STRING96_FMT(DN_KC_Bytes48ToHex(&expect).data)); + } break; + + case Hash_SHA3_512: { + DN_KCBytes64 hash = DN_KC_SHA3_512Str8(input); + DN_KCBytes64 expect; + DN_RefImpl_FIPS202_SHA3_512_(DN_CAST(uint8_t *) input.data, input.size, (uint8_t *)expect.data); + DN_UT_AssertF(test, + DN_KC_Bytes64Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING128_FMT(DN_KC_Bytes64ToHex(&hash).data), + DN_KC_STRING128_FMT(DN_KC_Bytes64ToHex(&expect).data)); + } break; + + case Hash_Keccak_224: { + DN_KCBytes28 hash = DN_KC_Keccak224Str8(input); + DN_KCBytes28 expect; + DN_RefImpl_Keccak_(1152, 448, DN_CAST(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_UT_AssertF(test, + DN_KC_Bytes28Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING56_FMT(DN_KC_Bytes28ToHex(&hash).data), + DN_KC_STRING56_FMT(DN_KC_Bytes28ToHex(&expect).data)); + } break; + + case Hash_Keccak_256: { + DN_KCBytes32 hash = DN_KC_Keccak256Str8(input); + DN_KCBytes32 expect; + DN_RefImpl_Keccak_(1088, 512, DN_CAST(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_UT_AssertF(test, + DN_KC_Bytes32Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING64_FMT(DN_KC_Bytes32ToHex(&hash).data), + DN_KC_STRING64_FMT(DN_KC_Bytes32ToHex(&expect).data)); + } break; + + case Hash_Keccak_384: { + DN_KCBytes48 hash = DN_KC_Keccak384Str8(input); + DN_KCBytes48 expect; + DN_RefImpl_Keccak_(832, 768, DN_CAST(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_UT_AssertF(test, + DN_KC_Bytes48Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING96_FMT(DN_KC_Bytes48ToHex(&hash).data), + DN_KC_STRING96_FMT(DN_KC_Bytes48ToHex(&expect).data)); + } break; + + case Hash_Keccak_512: { + DN_KCBytes64 hash = DN_KC_Keccak512Str8(input); + DN_KCBytes64 expect; + DN_RefImpl_Keccak_(576, 1024, DN_CAST(uint8_t *) input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_UT_AssertF(test, + DN_KC_Bytes64Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s", + DN_STR_FMT(input_hex), + DN_KC_STRING128_FMT(DN_KC_Bytes64ToHex(&hash).data), + DN_KC_STRING128_FMT(DN_KC_Bytes64ToHex(&expect).data)); + } break; + } +} + +DN_UTCore DN_Tests_Keccak() +{ + DN_UTCore test = DN_UT_Init(); + DN_Str8 const INPUTS[] = { + DN_STR8("abc"), + DN_STR8(""), + DN_STR8("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), + DN_STR8("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno" + "pqrstnopqrstu"), + }; + + DN_UT_LogF(&test, "DN_KC\n"); + { + for (int hash_type = 0; hash_type < Hash_Count; hash_type++) { + DN_PCG32 rng = DN_PCG32_Init(0xd48e'be21'2af8'733d); + for (DN_Str8 input : INPUTS) { + DN_UT_BeginF(&test, "%.*s - Input: %.*s", DN_STR_FMT(DN_UT_HASH_STRING_[hash_type]), DN_CAST(int) DN_Min(input.size, 54), input.data); + DN_Tests_KeccakDispatch_(&test, hash_type, input); + DN_UT_End(&test); + } + + DN_UT_BeginF(&test, "%.*s - Deterministic random inputs", DN_STR_FMT(DN_UT_HASH_STRING_[hash_type])); + for (DN_USize index = 0; index < 128; index++) { + char src[4096] = {}; + uint32_t src_size = DN_PCG32_Range(&rng, 0, sizeof(src)); + + for (DN_USize src_index = 0; src_index < src_size; src_index++) + src[src_index] = DN_CAST(char) DN_PCG32_Range(&rng, 0, 255); + + DN_Str8 input = DN_Str8_Init(src, src_size); + DN_Tests_KeccakDispatch_(&test, hash_type, input); + } + DN_UT_End(&test); + } + } + return test; +} +#endif // defined(DN_UNIT_TESTS_WITH_KECCAK) + +static DN_UTCore DN_Tests_M4() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_M4\n"); + { + DN_UT_Test(&result, "Simple translate and scale matrix") + { + DN_M4 translate = DN_M4_TranslateF(1, 2, 3); + DN_M4 scale = DN_M4_ScaleF(2, 2, 2); + DN_M4 mul_result = DN_M4_Mul(translate, scale); + + const DN_M4 EXPECT = { + { + {2, 0, 0, 0}, + {0, 2, 0, 0}, + {0, 0, 2, 0}, + {1, 2, 3, 1}, + } + }; + + DN_UT_AssertF(&result, + memcmp(mul_result.columns, EXPECT.columns, sizeof(EXPECT)) == 0, + "\nresult =\n%s\nexpected =\n%s", + DN_M4_ColumnMajorString(mul_result).data, + DN_M4_ColumnMajorString(EXPECT).data); + } + } + return result; +} + +static DN_UTCore DN_Tests_OS() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_OS\n"); + { + DN_UT_Test(&result, "Generate secure RNG bytes with nullptr") + { + DN_B32 os_result = DN_OS_SecureRNGBytes(nullptr, 1); + DN_UT_Assert(&result, os_result == false); + } + + DN_UT_Test(&result, "Generate secure RNG 32 bytes") + { + char const ZERO[32] = {}; + char buf[32] = {}; + bool os_result = DN_OS_SecureRNGBytes(buf, DN_ArrayCountU(buf)); + DN_UT_Assert(&result, os_result); + DN_UT_Assert(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0); + } + + DN_UT_Test(&result, "Generate secure RNG 0 bytes") + { + char buf[32] = {}; + buf[0] = 'Z'; + DN_B32 os_result = DN_OS_SecureRNGBytes(buf, 0); + DN_UT_Assert(&result, os_result); + DN_UT_Assert(&result, buf[0] == 'Z'); + } + + DN_UT_Test(&result, "Query executable directory") + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 os_result = DN_OS_EXEDir(tmem.arena); + DN_UT_Assert(&result, DN_Str8_HasData(os_result)); + DN_UT_AssertF(&result, DN_OS_DirExists(os_result), "result(%zu): %.*s", os_result.size, DN_STR_FMT(os_result)); + } + + DN_UT_Test(&result, "DN_OS_PerfCounterNow") + { + uint64_t os_result = DN_OS_PerfCounterNow(); + DN_UT_Assert(&result, os_result != 0); + } + + DN_UT_Test(&result, "Consecutive ticks are ordered") + { + uint64_t a = DN_OS_PerfCounterNow(); + uint64_t b = DN_OS_PerfCounterNow(); + DN_UT_AssertF(&result, b >= a, "a: %" PRIu64 ", b: %" PRIu64, a, b); + } + + DN_UT_Test(&result, "Ticks to time are a correct order of magnitude") + { + uint64_t a = DN_OS_PerfCounterNow(); + uint64_t b = DN_OS_PerfCounterNow(); + DN_F64 s = DN_OS_PerfCounterS(a, b); + DN_F64 ms = DN_OS_PerfCounterMs(a, b); + DN_F64 us = DN_OS_PerfCounterUs(a, b); + DN_F64 ns = DN_OS_PerfCounterNs(a, b); + DN_UT_AssertF(&result, s <= ms, "s: %f, ms: %f", s, ms); + DN_UT_AssertF(&result, ms <= us, "ms: %f, us: %f", ms, us); + DN_UT_AssertF(&result, us <= ns, "us: %f, ns: %f", us, ns); + } + } + + DN_UT_LogF(&result, "\nDN_OS Filesystem\n"); + { + DN_UT_Test(&result, "Make directory recursive \"abcd/efgh\"") + { + DN_UT_AssertF(&result, DN_OS_MakeDir(DN_STR8("abcd/efgh")), "Failed to make directory"); + DN_UT_AssertF(&result, DN_OS_DirExists(DN_STR8("abcd")), "Directory was not made"); + DN_UT_AssertF(&result, DN_OS_DirExists(DN_STR8("abcd/efgh")), "Subdirectory was not made"); + DN_UT_AssertF(&result, DN_OS_FileExists(DN_STR8("abcd")) == false, "This function should only return true for files"); + DN_UT_AssertF(&result, DN_OS_FileExists(DN_STR8("abcd/efgh")) == false, "This function should only return true for files"); + DN_UT_AssertF(&result, DN_OS_PathDelete(DN_STR8("abcd/efgh")), "Failed to delete directory"); + DN_UT_AssertF(&result, DN_OS_PathDelete(DN_STR8("abcd")), "Failed to cleanup directory"); + } + + DN_UT_Test(&result, "File write, read, copy, move and delete") + { + // NOTE: Write step + DN_Str8 const SRC_FILE = DN_STR8("dn_result_file"); + DN_B32 write_result = DN_OS_WriteAll(SRC_FILE, DN_STR8("1234"), nullptr); + DN_UT_Assert(&result, write_result); + DN_UT_Assert(&result, DN_OS_FileExists(SRC_FILE)); + + // NOTE: Read step + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 read_file = DN_OS_ReadAll(tmem.arena, SRC_FILE, nullptr); + DN_UT_AssertF(&result, DN_Str8_HasData(read_file), "Failed to load file"); + DN_UT_AssertF(&result, read_file.size == 4, "File read wrong amount of bytes (%zu)", read_file.size); + DN_UT_AssertF(&result, DN_Str8_Eq(read_file, DN_STR8("1234")), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.size, DN_STR_FMT(read_file)); + + // NOTE: Copy step + DN_Str8 const COPY_FILE = DN_STR8("dn_result_file_copy"); + DN_B32 copy_result = DN_OS_CopyFile(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr); + DN_UT_Assert(&result, copy_result); + DN_UT_Assert(&result, DN_OS_FileExists(COPY_FILE)); + + // NOTE: Move step + DN_Str8 const MOVE_FILE = DN_STR8("dn_result_file_move"); + DN_B32 move_result = DN_OS_MoveFile(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); + DN_UT_Assert(&result, move_result); + DN_UT_Assert(&result, DN_OS_FileExists(MOVE_FILE)); + DN_UT_AssertF(&result, DN_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original"); + + // NOTE: Delete step + DN_B32 delete_src_file = DN_OS_PathDelete(SRC_FILE); + DN_B32 delete_moved_file = DN_OS_PathDelete(MOVE_FILE); + DN_UT_Assert(&result, delete_src_file); + DN_UT_Assert(&result, delete_moved_file); + + // NOTE: Deleting non-existent file fails + DN_B32 delete_non_existent_src_file = DN_OS_PathDelete(SRC_FILE); + DN_B32 delete_non_existent_moved_file = DN_OS_PathDelete(MOVE_FILE); + DN_UT_Assert(&result, delete_non_existent_moved_file == false); + DN_UT_Assert(&result, delete_non_existent_src_file == false); + } + } + + return result; +} + +static DN_UTCore DN_Tests_Rect() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_Rect\n"); + { + DN_UT_Test(&result, "No intersection") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init1N(0), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(200, 0), DN_V2F32_Init2N(200, 200)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 0 && ab_max.y == 0, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "A's min intersects B") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(50, 50), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "B's min intersects A") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(50, 50), DN_V2F32_Init2N(100, 100)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "A's max intersects B") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(-50, -50), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "B's max intersects A") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(-50, -50), DN_V2F32_Init2N(100, 100)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "B contains A") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(25, 25), DN_V2F32_Init2N(25, 25)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "A contains B") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(25, 25), DN_V2F32_Init2N(25, 25)); + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + + DN_UT_Test(&result, "A equals B") + { + DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); + DN_Rect b = a; + DN_Rect ab = DN_Rect_Intersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_UT_AssertF(&result, + ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 100 && ab_max.y == 100, + "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", + ab.pos.x, + ab.pos.y, + ab_max.x, + ab_max.y); + } + } + return result; +} + +static DN_UTCore DN_Tests_Str8() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_Str8\n"); + { + DN_UT_Test(&result, "Initialise with string literal w/ macro") + { + DN_Str8 string = DN_STR8("AB"); + DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size); + DN_UT_AssertF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_UT_AssertF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + } + + DN_UT_Test(&result, "Initialise with format string") + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_InitF(tmem.arena, "%s", "AB"); + DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size); + DN_UT_AssertF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_UT_AssertF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + DN_UT_AssertF(&result, string.data[2] == 0, "string[2]: %c", string.data[2]); + } + + DN_UT_Test(&result, "Copy string") + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_STR8("AB"); + DN_Str8 copy = DN_Str8_Copy(tmem.arena, string); + DN_UT_AssertF(&result, copy.size == 2, "size: %zu", copy.size); + DN_UT_AssertF(&result, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]); + DN_UT_AssertF(&result, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]); + DN_UT_AssertF(&result, copy.data[2] == 0, "copy[2]: %c", copy.data[2]); + } + + DN_UT_Test(&result, "Trim whitespace around string") + { + DN_Str8 string = DN_Str8_TrimWhitespaceAround(DN_STR8(" AB ")); + DN_UT_AssertF(&result, DN_Str8_Eq(string, DN_STR8("AB")), "[string=%.*s]", DN_STR_FMT(string)); + } + + DN_UT_Test(&result, "Allocate string from arena") + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 string = DN_Str8_Alloc(tmem.arena, 2, DN_ZeroMem_No); + DN_UT_AssertF(&result, string.size == 2, "size: %zu", string.size); + } + + // NOTE: TrimPrefix/Suffix ///////////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "Trim prefix with matching prefix") + { + DN_Str8 input = DN_STR8("nft/abc"); + DN_Str8 str_result = DN_Str8_TrimPrefix(input, DN_STR8("nft/")); + DN_UT_AssertF(&result, DN_Str8_Eq(str_result, DN_STR8("abc")), "%.*s", DN_STR_FMT(str_result)); + } + + DN_UT_Test(&result, "Trim prefix with non matching prefix") + { + DN_Str8 input = DN_STR8("nft/abc"); + DN_Str8 str_result = DN_Str8_TrimPrefix(input, DN_STR8(" ft/")); + DN_UT_AssertF(&result, DN_Str8_Eq(str_result, input), "%.*s", DN_STR_FMT(str_result)); + } + + DN_UT_Test(&result, "Trim suffix with matching suffix") + { + DN_Str8 input = DN_STR8("nft/abc"); + DN_Str8 str_result = DN_Str8_TrimSuffix(input, DN_STR8("abc")); + DN_UT_AssertF(&result, DN_Str8_Eq(str_result, DN_STR8("nft/")), "%.*s", DN_STR_FMT(str_result)); + } + + DN_UT_Test(&result, "Trim suffix with non matching suffix") + { + DN_Str8 input = DN_STR8("nft/abc"); + DN_Str8 str_result = DN_Str8_TrimSuffix(input, DN_STR8("ab")); + DN_UT_AssertF(&result, DN_Str8_Eq(str_result, input), "%.*s", DN_STR_FMT(str_result)); + } + + // NOTE: DN_Str8_IsAllDigits ////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "Is all digits fails on non-digit string") + { + DN_B32 str_result = DN_Str8_IsAll(DN_STR8("@123string"), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, str_result == false); + } + + DN_UT_Test(&result, "Is all digits fails on nullptr") + { + DN_B32 str_result = DN_Str8_IsAll(DN_Str8_Init(nullptr, 0), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, str_result == false); + } + + DN_UT_Test(&result, "Is all digits fails on nullptr w/ size") + { + DN_B32 str_result = DN_Str8_IsAll(DN_Str8_Init(nullptr, 1), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, str_result == false); + } + + DN_UT_Test(&result, "Is all digits fails on string w/ 0 size") + { + char const buf[] = "@123string"; + DN_B32 str_result = DN_Str8_IsAll(DN_Str8_Init(buf, 0), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, !str_result); + } + + DN_UT_Test(&result, "Is all digits success") + { + DN_B32 str_result = DN_Str8_IsAll(DN_STR8("23"), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, DN_CAST(bool) str_result == true); + } + + DN_UT_Test(&result, "Is all digits fails on whitespace") + { + DN_B32 str_result = DN_Str8_IsAll(DN_STR8("23 "), DN_Str8IsAll_Digits); + DN_UT_Assert(&result, DN_CAST(bool) str_result == false); + } + + // NOTE: DN_Str8_BinarySplit /////////////////////////////////////////////////////////////////// + { + { + char const *TEST_FMT = "Binary split \"%.*s\" with \"%.*s\""; + DN_Str8 delimiter = DN_STR8("/"); + DN_Str8 input = DN_STR8("abcdef"); + DN_UT_Test(&result, TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) + { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); + DN_UT_AssertF(&result, DN_Str8_Eq(split.lhs, DN_STR8("abcdef")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); + DN_UT_AssertF(&result, DN_Str8_Eq(split.rhs, DN_STR8("")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); + } + + input = DN_STR8("abc/def"); + DN_UT_Test(&result, TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) + { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); + DN_UT_AssertF(&result, DN_Str8_Eq(split.lhs, DN_STR8("abc")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); + DN_UT_AssertF(&result, DN_Str8_Eq(split.rhs, DN_STR8("def")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); + } + + input = DN_STR8("/abcdef"); + DN_UT_Test(&result, TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) + { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); + DN_UT_AssertF(&result, DN_Str8_Eq(split.lhs, DN_STR8("")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); + DN_UT_AssertF(&result, DN_Str8_Eq(split.rhs, DN_STR8("abcdef")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); + } + } + + { + DN_Str8 delimiter = DN_STR8("-=-"); + DN_Str8 input = DN_STR8("123-=-456"); + DN_UT_Test(&result, "Binary split \"%.*s\" with \"%.*s\"", DN_STR_FMT(input), DN_STR_FMT(delimiter)) + { + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); + DN_UT_AssertF(&result, DN_Str8_Eq(split.lhs, DN_STR8("123")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); + DN_UT_AssertF(&result, DN_Str8_Eq(split.rhs, DN_STR8("456")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); + } + } + } + + // NOTE: DN_Str8_ToI64 ///////////////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "To I64: Convert null string") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_Str8_Init(nullptr, 5), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 0); + } + + DN_UT_Test(&result, "To I64: Convert empty string") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8(""), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 0); + } + + DN_UT_Test(&result, "To I64: Convert \"1\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("1"), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 1); + } + + DN_UT_Test(&result, "To I64: Convert \"-0\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("-0"), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 0); + } + + DN_UT_Test(&result, "To I64: Convert \"-1\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("-1"), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == -1); + } + + DN_UT_Test(&result, "To I64: Convert \"1.2\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("1.2"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_Assert(&result, str_result.value == 1); + } + + DN_UT_Test(&result, "To I64: Convert \"1,234\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("1,234"), ','); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 1234); + } + + DN_UT_Test(&result, "To I64: Convert \"1,2\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("1,2"), ','); + DN_UT_Assert(&result, str_result.success); + DN_UT_Assert(&result, str_result.value == 12); + } + + DN_UT_Test(&result, "To I64: Convert \"12a3\"") + { + DN_Str8ToI64Result str_result = DN_Str8_ToI64(DN_STR8("12a3"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_Assert(&result, str_result.value == 12); + } + + // NOTE: DN_Str8_ToU64 ///////////////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "To U64: Convert nullptr") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_Str8_Init(nullptr, 5), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert empty string") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8(""), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"1\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("1"), 0); + DN_UT_Assert(&result, str_result.success); + DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"-0\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("-0"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"-1\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("-1"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"1.2\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("1.2"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"1,234\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("1,234"), ','); + DN_UT_Assert(&result, str_result.success); + DN_UT_AssertF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"1,2\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("1,2"), ','); + DN_UT_Assert(&result, str_result.success); + DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + DN_UT_Test(&result, "To U64: Convert \"12a3\"") + { + DN_Str8ToU64Result str_result = DN_Str8_ToU64(DN_STR8("12a3"), 0); + DN_UT_Assert(&result, !str_result.success); + DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + // NOTE: DN_Str8_Find ///////////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "Find: String (char) is not in buffer") + { + DN_Str8 buf = DN_STR8("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_STR8("2"); + DN_Str8FindResult str_result = DN_Str8_FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_UT_Assert(&result, !str_result.found); + DN_UT_Assert(&result, str_result.index == 0); + DN_UT_Assert(&result, str_result.match.data == nullptr); + DN_UT_Assert(&result, str_result.match.size == 0); + } + + DN_UT_Test(&result, "Find: String (char) is in buffer") + { + DN_Str8 buf = DN_STR8("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_STR8("6"); + DN_Str8FindResult str_result = DN_Str8_FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_UT_Assert(&result, str_result.found); + DN_UT_Assert(&result, str_result.index == 2); + DN_UT_Assert(&result, str_result.match.data[0] == '6'); + } + + // NOTE: DN_Str8_FileNameFromPath ////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "File name from Windows path") + { + DN_Str8 buf = DN_STR8("C:\\ABC\\str_result.exe"); + DN_Str8 str_result = DN_Str8_FileNameFromPath(buf); + DN_UT_AssertF(&result, str_result == DN_STR8("str_result.exe"), "%.*s", DN_STR_FMT(str_result)); + } + + DN_UT_Test(&result, "File name from Linux path") + { + DN_Str8 buf = DN_STR8("/ABC/str_result.exe"); + DN_Str8 str_result = DN_Str8_FileNameFromPath(buf); + DN_UT_AssertF(&result, str_result == DN_STR8("str_result.exe"), "%.*s", DN_STR_FMT(str_result)); + } + + // NOTE: DN_Str8_TrimPrefix //////////////////////////////////////////////////////////////////// + DN_UT_Test(&result, "Trim prefix") + { + DN_Str8 prefix = DN_STR8("@123"); + DN_Str8 buf = DN_STR8("@123string"); + DN_Str8 str_result = DN_Str8_TrimPrefix(buf, prefix, DN_Str8EqCase_Sensitive); + DN_UT_Assert(&result, str_result == DN_STR8("string")); + } + } + return result; +} + +static DN_UTCore DN_Tests_TicketMutex() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_TicketMutex\n"); + { + DN_UT_Test(&result, "Ticket mutex start and stop") + { + // TODO: We don't have a meaningful result but since atomics are + // implemented with a macro this ensures that we result that they are + // written correctly. + DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + DN_TicketMutex_End(&mutex); + DN_UT_Assert(&result, mutex.ticket == mutex.serving); + } + + DN_UT_Test(&result, "Ticket mutex start and stop w/ advanced API") + { + DN_TicketMutex mutex = {}; + unsigned int ticket_a = DN_TicketMutex_MakeTicket(&mutex); + unsigned int ticket_b = DN_TicketMutex_MakeTicket(&mutex); + DN_UT_Assert(&result, DN_CAST(bool) DN_TicketMutex_CanLock(&mutex, ticket_b) == false); + DN_UT_Assert(&result, DN_CAST(bool) DN_TicketMutex_CanLock(&mutex, ticket_a) == true); + + DN_TicketMutex_BeginTicket(&mutex, ticket_a); + DN_TicketMutex_End(&mutex); + DN_TicketMutex_BeginTicket(&mutex, ticket_b); + DN_TicketMutex_End(&mutex); + + DN_UT_Assert(&result, mutex.ticket == mutex.serving); + DN_UT_Assert(&result, mutex.ticket == ticket_b + 1); + } + } + return result; +} + +static DN_UTCore DN_Tests_VArray() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "DN_VArray\n"); + { + { + DN_VArray array = DN_VArray_InitByteSize(DN_Kilobytes(64)); + DN_DEFER + { + DN_VArray_Deinit(&array); + }; + + DN_UT_Test(&result, "Test adding an array of items to the array") + { + uint32_t array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + DN_VArray_AddArray(&array, array_literal, DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test stable erase, 1 item, the '2' value from the array") + { + DN_VArray_EraseRange(&array, 2 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Stable); + uint32_t array_literal[] = {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test unstable erase, 1 item, the '1' value from the array") + { + DN_VArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Unstable); + uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_ArrayErase erase_enums[] = {DN_ArrayErase_Stable, DN_ArrayErase_Unstable}; + DN_UT_Test(&result, "Test un/stable erase, OOB") + { + for (DN_ArrayErase erase : erase_enums) { + uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + DN_VArray_EraseRange(&array, DN_ArrayCountU(array_literal) /*begin_index*/, DN_ArrayCountU(array_literal) + 100 /*count*/, erase); + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + } + + DN_UT_Test(&result, "Test flipped begin/end index stable erase, 2 items, the '15, 3' value from the array") + { + DN_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Stable); + uint32_t array_literal[] = {0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test flipped begin/end index unstable erase, 2 items, the '4, 5' value from the array") + { + DN_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Unstable); + uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10, 11, 12}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test stable erase range, 2+1 (oob) item, the '13, 14, +1 OOB' value from the array") + { + DN_VArray_EraseRange(&array, 8 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Stable); + uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test unstable erase range, 3+1 (oob) item, the '11, 12, +1 OOB' value from the array") + { + DN_VArray_EraseRange(&array, 6 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Unstable); + uint32_t array_literal[] = {0, 13, 14, 6, 7, 8}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test stable erase -overflow OOB, erasing the '0, 13' value from the array") + { + DN_VArray_EraseRange(&array, 1 /*begin_index*/, -DN_ISIZE_MAX /*count*/, DN_ArrayErase_Stable); + uint32_t array_literal[] = {14, 6, 7, 8}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test unstable erase +overflow OOB, erasing the '7, 8' value from the array") + { + DN_VArray_EraseRange(&array, 2 /*begin_index*/, DN_ISIZE_MAX /*count*/, DN_ArrayErase_Unstable); + uint32_t array_literal[] = {14, 6}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(array_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, array_literal, DN_ArrayCountU(array_literal) * sizeof(array_literal[0])) == 0); + } + + DN_UT_Test(&result, "Test adding an array of items after erase") + { + uint32_t array_literal[] = {0, 1, 2, 3}; + DN_VArray_AddArray(&array, array_literal, DN_ArrayCountU(array_literal)); + + uint32_t expected_literal[] = {14, 6, 0, 1, 2, 3}; + DN_UT_Assert(&result, array.size == DN_ArrayCountU(expected_literal)); + DN_UT_Assert(&result, DN_Memcmp(array.data, expected_literal, DN_ArrayCountU(expected_literal) * sizeof(expected_literal[0])) == 0); + } + } + + DN_UT_Test(&result, "Array of unaligned objects are contiguously laid out in memory") + { + // NOTE: Since we allocate from a virtual memory block, each time + // we request memory from the block we can demand some alignment + // on the returned pointer from the memory block. If there's + // additional alignment done in that function then we can no + // longer access the items in the array contiguously leading to + // confusing memory "corruption" errors. + // + // This result makes sure that the unaligned objects are allocated + // from the memory block (and hence the array) contiguously + // when the size of the object is not aligned with the required + // alignment of the object. + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4324) // warning C4324: 'TestVArray::UnalignedObject': structure was padded due to alignment specifier + + struct alignas(8) UnalignedObject + { + char data[511]; + }; + + DN_MSVC_WARNING_POP + + DN_VArray array = DN_VArray_InitByteSize(DN_Kilobytes(64)); + DN_DEFER + { + DN_VArray_Deinit(&array); + }; + + // NOTE: Verify that the items returned from the data array are + // contiguous in memory. + UnalignedObject *make_item_a = DN_VArray_MakeArray(&array, 1, DN_ZeroMem_Yes); + UnalignedObject *make_item_b = DN_VArray_MakeArray(&array, 1, DN_ZeroMem_Yes); + DN_Memset(make_item_a->data, 'a', sizeof(make_item_a->data)); + DN_Memset(make_item_b->data, 'b', sizeof(make_item_b->data)); + DN_UT_Assert(&result, (uintptr_t)make_item_b == (uintptr_t)(make_item_a + 1)); + + // NOTE: Verify that accessing the items from the data array yield + // the same object. + DN_UT_Assert(&result, array.size == 2); + UnalignedObject *data_item_a = array.data + 0; + UnalignedObject *data_item_b = array.data + 1; + DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)(data_item_a + 1)); + DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)(make_item_a + 1)); + DN_UT_Assert(&result, (uintptr_t)data_item_b == (uintptr_t)make_item_b); + + for (DN_USize i = 0; i < sizeof(data_item_a->data); i++) + DN_UT_Assert(&result, data_item_a->data[i] == 'a'); + + for (DN_USize i = 0; i < sizeof(data_item_b->data); i++) + DN_UT_Assert(&result, data_item_b->data[i] == 'b'); + } + } + return result; +} + +#if defined(DN_PLATFORM_WIN32) +static DN_UTCore DN_Tests_Win() +{ + DN_UTCore result = DN_UT_Init(); + DN_UT_LogF(&result, "OS Win32\n"); + { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 input8 = DN_STR8("String"); + DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; + + DN_UT_Test(&result, "Str8 to Str16") + { + DN_Str16 str_result = DN_Win_Str8ToStr16(tmem.arena, input8); + DN_UT_Assert(&result, str_result == input16); + } + + DN_UT_Test(&result, "Str16 to Str8") + { + DN_Str8 str_result = DN_Win_Str16ToStr8(tmem.arena, input16); + DN_UT_Assert(&result, str_result == input8); + } + + DN_UT_Test(&result, "Str16 to Str8: Null terminates string") + { + int size_required = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); + char *string = DN_Arena_NewArray(tmem.arena, char, size_required + 1, DN_ZeroMem_No); + + // Fill the string with error sentinels + DN_Memset(string, 'Z', size_required + 1); + + int size_returned = DN_Win_Str16ToStr8Buffer(input16, string, size_required + 1); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_UT_AssertF(&result, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned); + DN_UT_AssertF(&result, size_returned == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DN_ArrayCountU(EXPECTED) - 1); + DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string, sizeof(EXPECTED)) == 0); + } + + DN_UT_Test(&result, "Str16 to Str8: Arena null terminates string") + { + DN_Str8 string8 = DN_Win_Str16ToStr8(tmem.arena, input16); + int size_returned = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_UT_AssertF(&result, DN_CAST(int) string8.size == size_returned, "string_size: %d, result: %d", DN_CAST(int) string8.size, size_returned); + DN_UT_AssertF(&result, DN_CAST(int) string8.size == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_CAST(int) string8.size, DN_ArrayCountU(EXPECTED) - 1); + DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); + } + } + return result; +} +#endif // DN_PLATFORM_WIN32 + +DN_TestsResult DN_Tests_RunSuite(DN_TestsPrint print) +{ + DN_UTCore tests[] = + { + DN_Tests_Base(), + DN_Tests_Arena(), + DN_Tests_Bin(), + DN_Tests_BinarySearch(), + DN_Tests_DSMap(), + DN_Tests_FStr8(), + DN_Tests_FArray(), + DN_Tests_Intrinsics(), +#if defined(DN_UNIT_TESTS_WITH_KECCAK) + DN_Tests_Keccak(), +#endif + DN_Tests_M4(), + DN_Tests_OS(), + DN_Tests_Rect(), + DN_Tests_Str8(), + DN_Tests_TicketMutex(), + DN_Tests_VArray(), +#if defined(DN_PLATFORM_WIN32) + DN_Tests_Win(), +#endif + }; + + DN_TestsResult result = {}; + for (const DN_UTCore &test : tests) { + result.total_tests += test.num_tests_in_group; + result.total_good_tests += test.num_tests_ok_in_group; + } + result.passed = result.total_tests == result.total_good_tests; + + bool print_summary = false; + for (DN_UTCore &test : tests) { + bool do_print = print == DN_TestsPrint_Yes; + if (print == DN_TestsPrint_OnFailure && test.num_tests_ok_in_group != test.num_tests_in_group) + do_print = true; + + if (do_print) { + print_summary = true; + DN_UT_PrintTests(&test); + } + DN_UT_Deinit(&test); + } + + if (print_summary) + fprintf(stdout, "Summary: %d/%d tests succeeded\n", result.total_good_tests, result.total_tests); + + return result; +} diff --git a/Extra/dn_tests_main.cpp b/Extra/dn_tests_main.cpp new file mode 100644 index 0000000..a87ddbd --- /dev/null +++ b/Extra/dn_tests_main.cpp @@ -0,0 +1,31 @@ +#include "../dn_base_inc.h" +#include "../dn_os_inc.h" +#include "../dn_core_inc.h" + +#include "../dn_base_inc.cpp" +#include "../dn_os_inc.cpp" +#include "../dn_core_inc.cpp" + +#include "../Extra/dn_math.h" +#include "../Extra/dn_helpers.h" + +#include "../Extra/dn_math.cpp" +#include "../Extra/dn_helpers.cpp" + +#define DN_UT_IMPLEMENTATION +#include "../Standalone/dn_utest.h" +#include "../Extra/dn_tests.cpp" + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(6262) // Function uses '29804' bytes of stack. Consider moving some data to heap. +int main(int, char**) +{ + DN_Core core = {}; + DN_OSCore os = {}; + + DN_OS_Init(&os, nullptr); + DN_Core_Init(&core, DN_CoreOnInit_LogAllFeatures); + DN_Tests_RunSuite(DN_TestsPrint_Yes); + return 0; +} +DN_MSVC_WARNING_POP diff --git a/dqn_type_info.cpp b/Extra/dn_type_info.cpp similarity index 96% rename from dqn_type_info.cpp rename to Extra/dn_type_info.cpp index f261f7d..f87f4d8 100644 --- a/dqn_type_info.cpp +++ b/Extra/dn_type_info.cpp @@ -1,5 +1,4 @@ -#pragma once -#include "dqn.h" +#define DN_TYPE_INFO_CPP /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,7 +12,7 @@ // $$ | $$ | $$ | $$$$$$$$\ $$$$$$\ $$ | \$$ |$$ | $$$$$$ | // \__| \__| \__| \________| \______|\__| \__|\__| \______/ // -// dqn_type_info.cpp +// dn_type_info.cpp // //////////////////////////////////////////////////////////////////////////////////////////////////// */ diff --git a/dqn_type_info.h b/Extra/dn_type_info.h similarity index 57% rename from dqn_type_info.h rename to Extra/dn_type_info.h index 7b0da44..f6d7c10 100644 --- a/dqn_type_info.h +++ b/Extra/dn_type_info.h @@ -1,5 +1,5 @@ -#pragma once -#include "dqn.h" +#if !defined(DN_TYPE_INFO_H) +#define DN_TYPE_INFO_H /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,49 +13,52 @@ // $$ | $$ | $$ | $$$$$$$$\ $$$$$$\ $$ | \$$ |$$ | $$$$$$ | // \__| \__| \__| \________| \______|\__| \__|\__| \______/ // -// dqn_type_info.h -- C++ type introspection +// dn_type_info.h -- C++ type introspection // //////////////////////////////////////////////////////////////////////////////////////////////////// */ enum DN_TypeKind { - DN_TypeKind_Nil, - DN_TypeKind_Basic, - DN_TypeKind_Enum, - DN_TypeKind_Struct, + DN_TypeKind_Nil, + DN_TypeKind_Basic, + DN_TypeKind_Enum, + DN_TypeKind_Struct, }; struct DN_TypeField { - uint16_t index; - DN_Str8 name; - DN_Str8 label; - DN_ISize value; - DN_USize offset_of; - DN_USize size_of; - DN_USize align_of; - DN_Str8 type_decl; - uint32_t type_enum; - bool is_pointer; - uint16_t array_size; - DN_TypeField const * array_size_field; + uint16_t index; + DN_Str8 name; + DN_Str8 label; + DN_ISize value; + DN_USize offset_of; + DN_USize size_of; + DN_USize align_of; + DN_Str8 type_decl; + uint32_t type_enum; + bool is_pointer; + uint16_t array_size; + DN_TypeField const *array_size_field; }; struct DN_TypeInfo { - DN_Str8 name; - DN_TypeKind kind; - DN_USize size_of; - DN_TypeField const *fields; - uint16_t fields_count; + DN_Str8 name; + DN_TypeKind kind; + DN_USize size_of; + DN_TypeField const *fields; + uint16_t fields_count; }; struct DN_TypeGetField { - bool success; - DN_USize index; - DN_TypeField *field; + bool success; + DN_USize index; + DN_TypeField *field; }; DN_TypeGetField DN_Type_GetField(DN_TypeInfo const *type_info, DN_Str8 name); + +#endif // !defined(DN_TYPE_INFO_H) + diff --git a/OS/dn_os.cpp b/OS/dn_os.cpp new file mode 100644 index 0000000..10d6e4a --- /dev/null +++ b/OS/dn_os.cpp @@ -0,0 +1,734 @@ +#define DN_OS_CPP + +static DN_OSCore *g_dn_os_core_; + +static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Assert(user_data); + DN_OSCore *core = DN_CAST(DN_OSCore *)user_data; + + // NOTE: Open log file for appending if requested //////////////////////////////////////////////// + DN_TicketMutex_Begin(&core->log_file_mutex); + if (core->log_to_file && !core->log_file.handle && !core->log_file.error) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_OS_PathF' Actual type: 'struct DN_Str8'. + DN_Str8 log_path = DN_OS_PathF(tmem.arena, "%S/dn.log", DN_OS_EXEDir(tmem.arena)); + DN_MSVC_WARNING_POP + core->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr); + } + DN_TicketMutex_End(&core->log_file_mutex); + + DN_LOGStyle style = {}; + if (!core->log_no_colour) { + style.colour = true; + style.bold = DN_LOGBold_Yes; + if (type.is_u32_enum) { + switch (type.u32) { + case DN_LOGType_Debug: { + style.colour = false; + style.bold = DN_LOGBold_No; + } break; + + case DN_LOGType_Info: { + style.g = 0x87; + style.b = 0xff; + } break; + + case DN_LOGType_Warning: { + style.r = 0xff; + style.g = 0xff; + } break; + + case DN_LOGType_Error: { + style.r = 0xff; + } break; + } + } + } + + DN_OSDateTime os_date = DN_OS_DateLocalTimeNow(); + DN_LOGDate log_date = {}; + log_date.year = os_date.year; + log_date.month = os_date.month; + log_date.day = os_date.day; + log_date.hour = os_date.hour; + log_date.minute = os_date.minutes; + log_date.second = os_date.seconds; + + char prefix_buffer[128] = {}; + DN_LOGPrefixSize prefix_size = DN_LOG_MakePrefix(style, type, call_site, log_date, prefix_buffer, sizeof(prefix_buffer)); + + va_list args_copy; + va_copy(args_copy, args); + DN_TicketMutex_Begin(&core->log_file_mutex); + { + DN_OS_FileWrite(&core->log_file, DN_Str8_Init(prefix_buffer, prefix_size.size), nullptr); + DN_OS_FileWriteF(&core->log_file, nullptr, "%*s ", DN_CAST(int)prefix_size.padding, ""); + DN_OS_FileWriteFV(&core->log_file, nullptr, fmt, args_copy); + DN_OS_FileWrite(&core->log_file, DN_STR8("\n"), nullptr); + } + DN_TicketMutex_End(&core->log_file_mutex); + va_end(args_copy); + + DN_OSPrintDest dest = (type.is_u32_enum && type.u32 == DN_LOGType_Error) ? DN_OSPrintDest_Err : DN_OSPrintDest_Out; + DN_OS_Print(dest, DN_Str8_Init(prefix_buffer, prefix_size.size)); + DN_OS_PrintF(dest, "%*s ", DN_CAST(int)prefix_size.padding, ""); + DN_OS_PrintLnFV(dest, fmt, args); +} + +DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) +{ + g_dn_os_core_ = os; + + // NOTE: OS + { + #if defined(DN_PLATFORM_WIN32) + SYSTEM_INFO system_info = {}; + GetSystemInfo(&system_info); + os->page_size = system_info.dwPageSize; + os->alloc_granularity = system_info.dwAllocationGranularity; + QueryPerformanceFrequency(&os->win32_qpc_frequency); + + HMODULE module = LoadLibraryA("kernel32.dll"); + os->win32_set_thread_description = DN_CAST(DN_WinSetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + #else + // TODO(doyle): Get the proper page size from the OS. + os->page_size = DN_Kilobytes(4); + os->alloc_granularity = DN_Kilobytes(64); + #endif + } + + // NOTE: Setup logging + DN_OS_EmitLogsWithOSPrintFunctions(os); + + #if defined(DN_PLATFORM_WIN32) + // NOTE: win32 bcrypt + { + wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&os->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (os->win32_bcrypt_rng_handle && init_status == 0) + os->win32_bcrypt_init_success = true; + else + DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + } + #endif + + // NOTE: Initialise tmem arenas which allocate memory and will be + // recorded to the now initialised allocation table. The initialisation + // of tmem memory may request tmem memory itself in leak tracing mode. + // This is supported as the tmem arenas defer allocation tracking until + // initialisation is done. + DN_OSTLSInitArgs tls_init_args = {}; + if (args) { + tls_init_args.commit = args->tls_commit; + tls_init_args.reserve = args->tls_reserve; + tls_init_args.err_sink_reserve = args->tls_err_sink_reserve; + tls_init_args.err_sink_commit = args->tls_err_sink_commit; + } + + DN_OS_TLSInit(&os->tls, tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&os->tls); + os->cpu_report = DN_CPU_Report(); + + #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)}; + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + + DN_Assert(g_dn_os_core_); +} + +DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os) +{ + DN_Assert(os); + DN_LOG_SetEmitFromTypeFVFunc(DN_OS_LOGEmitFromTypeTypeFV_, os); +} + +DN_API void DN_OS_DumpThreadContextArenaStat(DN_Str8 file_path) +{ +#if defined(DN_DEBUG_THREAD_CONTEXT) + // NOTE: Open a file to write the arena stats to + FILE *file = nullptr; + fopen_s(&file, file_path.data, "a+b"); + if (file) { + DN_LOG_ErrorF("Failed to dump thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); + return; + } + + // NOTE: Copy the stats from library book-keeping + // NOTE: Extremely short critical section, copy the stats then do our + // work on it. + DN_ArenaStat stats[DN_CArray_CountI(g_dn_core->thread_context_arena_stats)]; + int stats_size = 0; + + DN_TicketMutex_Begin(&g_dn_core->thread_context_mutex); + stats_size = g_dn_core->thread_context_arena_stats_count; + DN_Memcpy(stats, g_dn_core->thread_context_arena_stats, sizeof(stats[0]) * stats_size); + DN_TicketMutex_End(&g_dn_core->thread_context_mutex); + + // NOTE: Print the cumulative stat + DN_DateHMSTimeStr now = DN_Date_HMSLocalTimeStrNow(); + fprintf(file, + "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", + now.date_size, + now.date, + now.hms_size, + now.hms, + g_dn_core->thread_context_arena_stats_count); + + // NOTE: Write the cumulative thread arena data + { + DN_ArenaStat stat = {}; + for (DN_USize index = 0; index < stats_size; index++) { + DN_ArenaStat const *current = stats + index; + stat.capacity += current->capacity; + stat.used += current->used; + stat.wasted += current->wasted; + stat.blocks += current->blocks; + + stat.capacity_hwm = DN_Max(stat.capacity_hwm, current->capacity_hwm); + stat.used_hwm = DN_Max(stat.used_hwm, current->used_hwm); + stat.wasted_hwm = DN_Max(stat.wasted_hwm, current->wasted_hwm); + stat.blocks_hwm = DN_Max(stat.blocks_hwm, current->blocks_hwm); + } + + DN_ArenaStatStr stats_string = DN_Arena_StatStr(&stat); + fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data); + } + + // NOTE: Print individual thread arena data + for (DN_USize index = 0; index < stats_size; index++) { + DN_ArenaStat const *current = stats + index; + DN_ArenaStatStr current_string = DN_Arena_StatStr(current); + fprintf(file, " [%03d] CURR %.*s\n", DN_CAST(int) index, current_string.size, current_string.data); + } + + fclose(file); + DN_LOG_InfoF("Dumped thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); +#else + (void)file_path; +#endif // #if defined(DN_DEBUG_THREAD_CONTEXT) +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8(DN_OSDateTime time, char date_separator, char hms_separator) +{ + DN_OSDateTimeStr8 result = {}; + result.hms_size = DN_CAST(uint8_t) DN_SNPrintF(result.hms, + DN_ArrayCountI(result.hms), + "%02hhu%c%02hhu%c%02hhu", + time.hour, + hms_separator, + time.minutes, + hms_separator, + time.seconds); + + result.date_size = DN_CAST(uint8_t) DN_SNPrintF(result.date, + DN_ArrayCountI(result.date), + "%hu%c%02hhu%c%02hhu", + time.year, + date_separator, + time.month, + date_separator, + time.day); + + DN_Assert(result.hms_size < DN_ArrayCountU(result.hms)); + DN_Assert(result.date_size < DN_ArrayCountU(result.date)); + return result; +} + +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) +{ + DN_OSDateTime time = DN_OS_DateLocalTimeNow(); + DN_OSDateTimeStr8 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator); + return result; +} + +DN_API uint64_t DN_OS_DateUnixTimeS() +{ + uint64_t result = DN_OS_DateUnixTimeNs() / (1'000 /*us*/ * 1'000 /*ms*/ * 1'000 /*s*/); + return result; +} + +DN_API bool DN_OS_DateIsValid(DN_OSDateTime date) +{ + if (date.year < 1970) + return false; + if (date.month <= 0 || date.month >= 13) + return false; + if (date.day <= 0 || date.day >= 32) + return false; + if (date.hour >= 24) + return false; + if (date.minutes >= 60) + return false; + if (date.seconds >= 60) + return false; + return true; +} + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8 exe_path = DN_OS_EXEPath(tmem.arena); + DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; + DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(exe_path, separators, DN_ArrayCountU(separators)); + result = DN_Str8_Copy(arena, split.lhs); + return result; +} + +// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = ticks / DN_CAST(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000) / DN_CAST(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000'000) / DN_CAST(DN_F64) frequency; + return result; +} + +DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end) +{ + uint64_t frequency = DN_OS_PerfCounterFrequency(); + uint64_t ticks = end - begin; + DN_F64 result = (ticks * 1'000'000'000) / DN_CAST(DN_F64) frequency; + return result; +} + +DN_API DN_OSTimer DN_OS_TimerBegin() +{ + DN_OSTimer result = {}; + result.start = DN_OS_PerfCounterNow(); + return result; +} + +DN_API void DN_OS_TimerEnd(DN_OSTimer *timer) +{ + timer->end = DN_OS_PerfCounterNow(); +} + +DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end); + return result; +} + +DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer) +{ + DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end); + return result; +} + +DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) +{ + uint64_t os_frequency = DN_OS_PerfCounterFrequency(); + uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; + uint64_t tsc_begin = DN_CPU_TSC(); + uint64_t result = 0; + if (tsc_begin) { + uint64_t os_elapsed = 0; + for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed;) + os_elapsed = DN_OS_PerfCounterNow() - os_begin; + uint64_t tsc_end = DN_CPU_TSC(); + uint64_t tsc_elapsed = tsc_end - tsc_begin; + result = tsc_elapsed / os_elapsed * os_frequency; + } + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +// NOTE: DN_OSPathInfo/File //////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against) +{ + DN_OSPathInfo file_info = DN_OS_PathInfo(file); + DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against); + bool result = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s; + return result; +} + +DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_OSErrSink *error) +{ + bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); + return result; +} + +struct DN_OSFileWriteChunker_ +{ + DN_OSErrSink *err; + DN_OSFile *file; + bool success; +}; + +static char *DN_OS_FileWriteChunker_(const char *buf, void *user, int len) +{ + DN_OSFileWriteChunker_ *chunker = DN_CAST(DN_OSFileWriteChunker_ *)user; + chunker->success = DN_OS_FileWritePtr(chunker->file, buf, len, chunker->err); + char *result = chunker->success ? DN_CAST(char *) buf : nullptr; + return result; +} + +DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + bool result = false; + if (!file || !fmt) + return result; + + DN_OSFileWriteChunker_ chunker = {}; + chunker.err = error; + chunker.file = file; + char buffer[STB_SPRINTF_MIN]; + STB_SPRINTF_DECORATE(vsprintfcb)(DN_OS_FileWriteChunker_, &chunker, buffer, fmt, args); + + result = chunker.success; + return result; +} + +DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_FileWriteFV(file, error, fmt, args); + va_end(args); + return result; +} + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_ReadAll(DN_Arena *arena, DN_Str8 path, DN_OSErrSink *error) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + // NOTE: Query file size + allocate buffer ///////////////////////////////////////////////////// + DN_OSPathInfo path_info = DN_OS_PathInfo(path); + if (!path_info.exists) { + DN_OS_ErrSinkAppendF(error, 1, "File does not exist/could not be queried for reading '%.*s'", DN_STR_FMT(path)); + return result; + } + + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); + result = DN_Str8_Alloc(arena, path_info.size, DN_ZeroMem_No); + if (!DN_Str8_HasData(result)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, path_info.size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(error, 1 /*error_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(path)); + DN_Arena_TempMemEnd(temp_mem); + result = {}; + return result; + } + + // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// + DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, error); + DN_OSFileRead read = DN_OS_FileRead(&file, result.data, result.size, error); + if (file.error || !read.success) { + DN_Arena_TempMemEnd(temp_mem); + result = {}; + } + DN_OS_FileClose(&file); + + return result; +} + +DN_API bool DN_OS_WriteAll(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *error) +{ + DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error); + bool result = DN_OS_FileWrite(&file, buffer, error); + DN_OS_FileClose(&file); + return result; +} + +DN_API bool DN_OS_WriteAllFV(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_WriteAll(file_path, buffer, error); + return result; +} + +DN_API bool DN_OS_WriteAllF(DN_Str8 file_path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_WriteAllFV(file_path, error, fmt, args); + va_end(args); + return result; +} + +DN_API bool DN_OS_WriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *error) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 tmp_path = DN_Str8_InitF(tmem.arena, "%.*s.tmp", DN_STR_FMT(path)); + if (!DN_OS_WriteAll(tmp_path, buffer, error)) + return false; + if (!DN_OS_CopyFile(tmp_path, path, true /*overwrite*/, error)) + return false; + if (!DN_OS_PathDelete(tmp_path)) + return false; + return true; +} + +DN_API bool DN_OS_WriteAllSafeFV(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_WriteAllSafe(path, buffer, error); + return result; +} + +DN_API bool DN_OS_WriteAllSafeF(DN_Str8 path, DN_OSErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool result = DN_OS_WriteAllSafeFV(path, error, fmt, args); + return result; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSPath ///////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) +{ + if (!arena || !fs_path || !DN_Str8_HasData(path)) + return false; + + if (path.size <= 0) + return true; + + DN_Str8 const delimiter_array[] = { + DN_STR8("\\"), + DN_STR8("/")}; + + if (fs_path->links_size == 0) + fs_path->has_prefix_path_separator = (path.data[0] == '/'); + + for (;;) { + DN_Str8BinarySplitResult delimiter = DN_Str8_BinarySplitArray(path, delimiter_array, DN_ArrayCountU(delimiter_array)); + for (; delimiter.lhs.data; delimiter = DN_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DN_ArrayCountU(delimiter_array))) { + if (delimiter.lhs.size <= 0) + continue; + + DN_OSPathLink *link = DN_Arena_New(arena, DN_OSPathLink, DN_ZeroMem_Yes); + if (!link) + return false; + + link->string = delimiter.lhs; + link->prev = fs_path->tail; + if (fs_path->tail) + fs_path->tail->next = link; + else + fs_path->head = link; + fs_path->tail = link; + fs_path->links_size += 1; + fs_path->string_size += delimiter.lhs.size; + } + + if (!delimiter.lhs.data) + break; + } + + return true; +} + +DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) +{ + DN_Str8 copy = DN_Str8_Copy(arena, path); + bool result = DN_Str8_HasData(copy) ? true : DN_OS_PathAddRef(arena, fs_path, copy); + return result; +} + +DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8_InitFV(arena, fmt, args); + va_end(args); + bool result = DN_OS_PathAddRef(arena, fs_path, path); + return result; +} + +DN_API bool DN_OS_PathPop(DN_OSPath *fs_path) +{ + if (!fs_path) + return false; + + if (fs_path->tail) { + DN_Assert(fs_path->head); + fs_path->links_size -= 1; + fs_path->string_size -= fs_path->tail->string.size; + fs_path->tail = fs_path->tail->prev; + if (fs_path->tail) + fs_path->tail->next = nullptr; + else + fs_path->head = nullptr; + } else { + DN_Assert(!fs_path->head); + } + + return true; +} + +DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator) +{ + DN_OSPath fs_path = {}; + DN_OS_PathAddRef(arena, &fs_path, path); + DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator); + return result; +} + +DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_PathTo(arena, path, path_separator); + return result; +} + +DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path) +{ + DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString); + return result; +} + +DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + va_list args; + va_start(args, fmt); + DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); + va_end(args); + DN_Str8 result = DN_OS_Path(arena, path); + return result; +} + +DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator) +{ + DN_Str8 result = {}; + if (!fs_path || fs_path->links_size <= 0) + return result; + + // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' + DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); + result = DN_Str8_Alloc(arena, string_size, DN_ZeroMem_No); + if (result.data) { + char *dest = result.data; + if (fs_path->has_prefix_path_separator) { + DN_Memcpy(dest, path_separator.data, path_separator.size); + dest += path_separator.size; + } + + for (DN_OSPathLink *link = fs_path->head; link; link = link->next) { + DN_Str8 string = link->string; + DN_Memcpy(dest, string.data, string.size); + dest += string.size; + + if (link != fs_path->tail) { + DN_Memcpy(dest, path_separator.data, path_separator.size); + dest += path_separator.size; + } + } + } + + result.data[string_size] = 0; + return result; +} + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSExecResult DN_OS_Exec(DN_Slice cmd_line, + DN_OSExecArgs *args, + DN_Arena *arena, + DN_OSErrSink *error) +{ + DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error); + DN_OSExecResult result = DN_OS_ExecWait(async_handle, arena, error); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena) +{ + DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); + DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error); + if (result.os_error_code) + DN_OS_ErrSinkEndAndExitIfErrorF(error, result.os_error_code, "OS failed to execute the requested command returning the error code %u", result.os_error_code); + + if (result.exit_code) + DN_OS_ErrSinkEndAndExitIfErrorF(error, result.exit_code, "OS executed command and returned non-zero exit code %u", result.exit_code); + DN_OS_ErrSinkEndAndIgnore(error); + return result; +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static void DN_OS_ThreadExecute_(void *user_context) +{ + DN_OSThread *thread = DN_CAST(DN_OSThread *) user_context; + DN_OS_TLSInit(&thread->tls, thread->tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&thread->tls); + DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); + thread->func(thread); +} + +DN_API void DN_OS_ThreadSetName(DN_Str8 name) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + tls->name_size = DN_CAST(uint8_t) DN_Min(name.size, sizeof(tls->name) - 1); + DN_Memcpy(tls->name, name.data, tls->name_size); + tls->name[tls->name_size] = 0; + +#if defined(DN_PLATFORM_WIN32) + DN_Win_ThreadSetName(name); +#else + DN_Posix_ThreadSetName(name); +#endif +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_HttpRequestWait(DN_OSHttpResponse *response) +{ + if (response && DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) + DN_OS_SemaphoreWait(&response->on_complete_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); +} + +DN_API DN_OSHttpResponse DN_OS_HttpRequest(DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers) +{ + // TODO(doyle): Revise the memory allocation and its lifetime + DN_OSHttpResponse result = {}; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + result.tmem_arena = tmem.arena; + + DN_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers); + DN_OS_HttpRequestWait(&result); + return result; +} diff --git a/OS/dn_os.h b/OS/dn_os.h new file mode 100644 index 0000000..d6ed998 --- /dev/null +++ b/OS/dn_os.h @@ -0,0 +1,483 @@ +#if !defined(DN_OS_H) +#define DN_OS_H + +#if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_ARM64) + #include "dn_os_posix.h" +#elif defined(DN_PLATFORM_WIN32) + #include "dn_os_windows.h" + #include "dn_os_win32.h" +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif + + +#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) + #include + #include +#endif + +#if defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include // errno + #include // O_RDONLY ... etc + #include // ioctl + #include // mmap + #include // getrandom + #include // stat + #include // pid_t + #include // waitpid + #include // clock_gettime, nanosleep + #include // access, gettid, write + + #if !defined(DN_PLATFORM_EMSCRIPTEN) + #include // FICLONE + #include // sendfile + #endif +#endif + +#if defined(DN_PLATFORM_EMSCRIPTEN) + #include // emscripten_fetch (for DN_OSHttpResponse) +#endif + +#if defined(DN_PLATFORM_WIN32) +typedef HRESULT DN_WinSetThreadDescriptionFunc(HANDLE hThread, PWSTR const lpThreadDescription); +#endif + +// NOTE: DN_OSDate ///////////////////////////////////////////////////////////////////////////////// +struct DN_OSDateTimeStr8 +{ + char date[DN_ArrayCountU("YYYY-MM-SS")]; + DN_U8 date_size; + char hms[DN_ArrayCountU("HH:MM:SS")]; + DN_U8 hms_size; +}; + +struct DN_OSDateTime +{ + DN_U8 day; + DN_U8 month; + DN_U16 year; + DN_U8 hour; + DN_U8 minutes; + DN_U8 seconds; +}; + +struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. +{ + DN_U64 start; + DN_U64 end; +}; + +#if !defined(DN_NO_OS_FILE_API) +// NOTE: DN_OSFile ///////////////////////////////////////////////////////////////////////////////// +enum DN_OSPathInfoType +{ + DN_OSPathInfoType_Unknown, + DN_OSPathInfoType_Directory, + DN_OSPathInfoType_File, +}; + +struct DN_OSPathInfo +{ + bool exists; + DN_OSPathInfoType type; + DN_U64 create_time_in_s; + DN_U64 last_write_time_in_s; + DN_U64 last_access_time_in_s; + DN_U64 size; +}; + +struct DN_OSDirIterator +{ + void *handle; + DN_Str8 file_name; + char buffer[512]; +}; + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +struct DN_OSFileRead +{ + bool success; + DN_USize bytes_read; +}; + +struct DN_OSFile +{ + bool error; + void *handle; +}; + +enum DN_OSFileOpen +{ + DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open + DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists + DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist +}; + +typedef DN_U32 DN_OSFileAccess; + +enum DN_OSFileAccess_ +{ + DN_OSFileAccess_Read = 1 << 0, + DN_OSFileAccess_Write = 1 << 1, + DN_OSFileAccess_Execute = 1 << 2, + DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode + DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, + DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, +}; +#endif // DN_NO_OS_FILE_API + +// NOTE: DN_OSPath //////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_OSPathSeperator) + #if defined(DN_OS_WIN32) + #define DN_OSPathSeperator "\\" + #else + #define DN_OSPathSeperator "/" + #endif + #define DN_OSPathSeperatorString DN_STR8(DN_OSPathSeperator) +#endif + +struct DN_OSPathLink +{ + DN_Str8 string; + DN_OSPathLink *next; + DN_OSPathLink *prev; +}; + +struct DN_OSPath +{ + bool has_prefix_path_separator; + DN_OSPathLink *head; + DN_OSPathLink *tail; + DN_USize string_size; + DN_U16 links_size; +}; + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +typedef DN_U32 DN_OSExecFlags; + +enum DN_OSExecFlags_ +{ + DN_OSExecFlags_Nil = 0, + DN_OSExecFlags_SaveStdout = 1 << 0, + DN_OSExecFlags_SaveStderr = 1 << 1, + DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, + DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, +}; + +struct DN_OSExecAsyncHandle +{ + DN_OSExecFlags exec_flags; + DN_U32 os_error_code; + DN_U32 exit_code; + void *process; + void *stdout_read; + void *stdout_write; + void *stderr_read; + void *stderr_write; +}; + +struct DN_OSExecResult +{ + bool finished; + DN_Str8 stdout_text; + DN_Str8 stderr_text; + DN_U32 os_error_code; + DN_U32 exit_code; +}; + +struct DN_OSExecArgs +{ + DN_OSExecFlags flags; + DN_Str8 working_dir; + DN_Slice environment; +}; + +#if !defined(DN_NO_SEMAPHORE) +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_U32 const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; + +struct DN_OSSemaphore +{ + #if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) + void *win32_handle; + #else + sem_t posix_handle; + bool posix_init; + #endif +}; + +enum DN_OSSemaphoreWaitResult +{ + DN_OSSemaphoreWaitResult_Failed, + DN_OSSemaphoreWaitResult_Success, + DN_OSSemaphoreWaitResult_Timeout, +}; +#endif // !defined(DN_NO_SEMAPHORE) + +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +struct DN_OSMutex +{ +#if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) + char win32_handle[48]; +#else + pthread_mutex_t posix_handle; + pthread_mutexattr_t posix_attribs; +#endif +}; + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) +typedef int32_t(DN_OSThreadFunc)(struct DN_OSThread *); + +struct DN_OSThread +{ + DN_FStr8<64> name; + DN_OSTLS tls; + DN_OSTLSInitArgs tls_init_args; + void *handle; + DN_U64 thread_id; + void *user_context; + DN_OSThreadFunc *func; + DN_OSSemaphore init_semaphore; +}; +#endif // !defined(DN_NO_THREAD) + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +enum DN_OSHttpRequestSecure +{ + DN_OSHttpRequestSecure_No, + DN_OSHttpRequestSecure_Yes, +}; + +struct DN_OSHttpResponse +{ + // NOTE: Response data + DN_U32 error_code; + DN_Str8 error_msg; + DN_U16 http_status; + DN_Str8 body; + DN_B32 done; + + // NOTE: Book-keeping + DN_Arena *arena; // Allocates memory for the response + + // NOTE: Async book-keeping + // Synchronous HTTP response uses the TLS scratch arena whereas async + // calls use their own dedicated arena. + DN_Arena tmp_arena; + DN_Arena *tmem_arena; + DN_Str8Builder builder; + DN_OSSemaphore on_complete_semaphore; + + #if defined(DN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_t *em_handle; + #elif defined(DN_PLATFORM_WIN32) + HINTERNET win32_request_session; + HINTERNET win32_request_connection; + HINTERNET win32_request_handle; + #endif +}; + +struct DN_OSInitArgs +{ + DN_U64 tls_reserve; + DN_U64 tls_commit; + DN_U64 tls_err_sink_reserve; + DN_U64 tls_err_sink_commit; +}; + +struct DN_OSCore +{ + DN_CPUReport cpu_report; + DN_OSTLS tls; // Thread local storage state for the main thread. + + // NOTE: Logging /////////////////////////////////////////////////////////////////////////////// + DN_LOGEmitFromTypeFVFunc * log_callback; // Set this pointer to override the logging routine + void * log_user_data; // User pointer passed into 'log_callback' + bool log_to_file; // Output logs to file as well as standard out + DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? + DN_TicketMutex log_file_mutex; // Is locked when instantiating the log_file for the first time + bool log_no_colour; // Disable colours in the logging output + + // NOTE: OS ////////////////////////////////////////////////////////////////////////////////////// + DN_U32 page_size; + DN_U32 alloc_granularity; + + // NOTE: Memory //////////////////////////////////////////////////////////////////////////////// + // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This + // only includes allocations routed through the library such as the growing nature of arenas or + // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. + DN_U64 vmem_allocs_total; + DN_U64 vmem_allocs_frame; // Total OS virtual memory allocs since the last 'DN_Core_FrameBegin' was invoked + DN_U64 mem_allocs_total; + DN_U64 mem_allocs_frame; // Total OS heap allocs since the last 'DN_Core_FrameBegin' was invoked + + // NOTE: Win32 ///////////////////////////////////////////////////////////////////////////////// + #if defined(DN_PLATFORM_WIN32) + DN_WinSetThreadDescriptionFunc *win32_set_thread_description; + LARGE_INTEGER win32_qpc_frequency; + void * win32_bcrypt_rng_handle; + bool win32_bcrypt_init_success; + bool win32_sym_initialised; + #endif +}; + +struct DN_OSDiskSpace +{ + bool success; + DN_U64 avail; + DN_U64 size; +}; + +DN_API void DN_OS_Init (DN_OSCore *os, DN_OSInitArgs *args); +DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os); +DN_API void DN_OS_DumpThreadContextArenaStat (DN_Str8 file_path); + +// NOTE: Memory //////////////////////////////////////////////////////////////////////////////////// +DN_API void * DN_OS_MemReserve (DN_USize size, DN_MemCommit commit, DN_MemPage page_flags); +DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, DN_U32 page_flags); +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size); +DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); +DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, DN_U32 page_flags); + +// NOTE: Heap +DN_API void * DN_OS_MemAlloc (DN_USize size, DN_ZeroMem zero_mem); +DN_API void DN_OS_MemDealloc (void *ptr); + +// NOTE: DN_OSDate ///////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow (); +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator = '-', char hms_separator = ':'); +DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8 (DN_OSDateTime time, char date_separator = '-', char hms_separator = ':'); +DN_API DN_U64 DN_OS_DateUnixTimeNs (); +DN_API DN_U64 DN_OS_DateUnixTimeS (); +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate (DN_U64 time); +DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date); +DN_API DN_U64 DN_OS_DateToUnixTimeS (DN_OSDateTime date); +DN_API bool DN_OS_DateIsValid (DN_OSDateTime date); + +// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_SecureRNGBytes (void *buffer, DN_U32 size); +DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); +DN_API DN_OSDiskSpace DN_OS_DiskSpace (DN_Str8 path); +DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); +DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); +#define DN_OS_EXEDirFromTLS() DN_OS_EXEDir(DN_OS_TLSTopArena()) +DN_API void DN_OS_SleepMs (DN_UInt milliseconds); + +// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +DN_API DN_U64 DN_OS_PerfCounterNow (); +DN_API DN_U64 DN_OS_PerfCounterFrequency(); +DN_API DN_F64 DN_OS_PerfCounterS (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterMs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterUs (DN_U64 begin, uint64_t end); +DN_API DN_F64 DN_OS_PerfCounterNs (DN_U64 begin, uint64_t end); +DN_API DN_OSTimer DN_OS_TimerBegin (); +DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); +DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); +DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); +DN_API DN_U64 DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency); +#if !defined(DN_NO_OS_FILE_API) +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); +DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against); +DN_API bool DN_OS_PathDelete (DN_Str8 path); +DN_API bool DN_OS_FileExists (DN_Str8 path); +DN_API bool DN_OS_CopyFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err); +DN_API bool DN_OS_MoveFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err); +DN_API bool DN_OS_MakeDir (DN_Str8 path); +DN_API bool DN_OS_DirExists (DN_Str8 path); +DN_API bool DN_OS_DirIterate (DN_Str8 path, DN_OSDirIterator *it); + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_OSErrSink *err); +DN_API DN_OSFileRead DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err); +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *data, DN_USize size, DN_OSErrSink *err); +DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_OSErrSink *err); +DN_API void DN_OS_FileClose (DN_OSFile *file); + +// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DN_API DN_Str8 DN_OS_ReadAll (DN_Arena *arena, DN_Str8 path, DN_OSErrSink *err); +#define DN_OS_ReadAllFromTLS(...) DN_OS_ReadAll(DN_OS_TLSTopArena(), ##__VA_ARGS__) +DN_API bool DN_OS_WriteAll (DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_WriteAllFV (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_WriteAllF (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +DN_API bool DN_OS_WriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_OSErrSink *err); +DN_API bool DN_OS_WriteAllSafeFV (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_WriteAllSafeF (DN_Str8 path, DN_OSErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// +DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +#define DN_OS_PathAddRefFromTLS(...) DN_OS_PathAddRef(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddRefFromFrame(...) DN_OS_PathAddRef(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); +#define DN_OS_PathAddFromTLS(...) DN_OS_PathAdd(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddFromFrame(...) DN_OS_PathAdd(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathAddFFromTLS(...) DN_OS_PathAddF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathAddFFromFrame(...) DN_OS_PathAddF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); +DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); +#define DN_OS_PathBuildWithSeperatorFromTLS(...) DN_OS_PathBuildWithSeperator(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathBuildWithSeperatorFromFrame(...) DN_OS_PathBuildWithSeperator(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); +#define DN_OS_PathToFromTLS(...) DN_OS_PathTo(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathToFromFrame(...) DN_OS_PathTo(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathToFFromTLS(...) DN_OS_PathToF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathToFFromFrame(...) DN_OS_PathToF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); +#define DN_OS_PathFromTLS(...) DN_OS_Path(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathFromFrame(...) DN_OS_Path(DN_OS_TLSFrameArena(), ##__VA_ARGS__) +DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_PathFFromTLS(...) DN_OS_PathF(DN_OS_TLSTopArena(), ##__VA_ARGS__) +#define DN_OS_PathFFromFrame(...) DN_OS_PathF(DN_OS_TLSFrameArena(), ##__VA_ARGS__) + +#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("/")) +#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("\\")) +#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit (int32_t exit_code); +DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, DN_U32 timeout_ms, DN_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Slice cmd_line, DN_OSExecArgs *args, DN_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_Exec (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); +#define DN_OS_ExecOrAbortFromTLS(...) DN_OS_ExecOrAbort(__VA_ARGS__, DN_OS_TLSTopArena()) + +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_SEMAPHORE) +DN_API DN_OSSemaphore DN_OS_SemaphoreInit (DN_U32 initial_count); +DN_API bool DN_OS_SemaphoreIsValid (DN_OSSemaphore *semaphore); +DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, DN_U32 amount); +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, DN_U32 timeout_ms); +#endif // !defined(DN_NO_SEMAPHORE) + +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit (); +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex); +DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex); +#define DN_OS_Mutex(mutex) DN_DEFER_LOOP(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) +DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context); +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread); +DN_API DN_U32 DN_OS_ThreadID (); +DN_API void DN_OS_ThreadSetName(DN_Str8 name); +#endif // !defined(DN_NO_THREAD) + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); +DN_API void DN_OS_HttpRequestWait (DN_OSHttpResponse *response); +DN_API void DN_OS_HttpRequestFree (DN_OSHttpResponse *response); +DN_API DN_OSHttpResponse DN_OS_HttpRequest (DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); +#endif // !defined(DN_OS_H) diff --git a/OS/dn_os_allocator.cpp b/OS/dn_os_allocator.cpp new file mode 100644 index 0000000..b0a2ed0 --- /dev/null +++ b/OS/dn_os_allocator.cpp @@ -0,0 +1,30 @@ +#define DN_OS_ALLOCATOR_CPP + +static void *DN_Arena_BasicAllocFromOSHeap(DN_USize size) +{ + void *result = DN_OS_MemAlloc(size, DN_ZeroMem_Yes); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromOSHeap(DN_U64 size, DN_ArenaFlags flags) +{ + DN_ArenaMemFuncs mem_funcs = {}; + mem_funcs.type = DN_ArenaMemFuncType_Basic; + mem_funcs.basic_alloc = DN_Arena_BasicAllocFromOSHeap; + mem_funcs.basic_dealloc = DN_OS_MemDealloc; + DN_Arena result = DN_Arena_InitFromMemFuncs(size, size, flags, mem_funcs); + return result; +} + +DN_API DN_Arena DN_Arena_InitFromOSVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags) +{ + DN_ArenaMemFuncs mem_funcs = {}; + mem_funcs.type = DN_ArenaMemFuncType_VMem; + mem_funcs.vmem_page_size = g_dn_os_core_->page_size; + mem_funcs.vmem_reserve = DN_OS_MemReserve; + mem_funcs.vmem_commit = DN_OS_MemCommit; + mem_funcs.vmem_release = DN_OS_MemRelease; + DN_Arena result = DN_Arena_InitFromMemFuncs(reserve, commit, flags, mem_funcs); + return result; +} + diff --git a/OS/dn_os_allocator.h b/OS/dn_os_allocator.h new file mode 100644 index 0000000..8686fb2 --- /dev/null +++ b/OS/dn_os_allocator.h @@ -0,0 +1,7 @@ +#if !defined(DN_OS_ALLOCATOR_H) +#define DN_OS_ALLOCATOR_H + +DN_API DN_Arena DN_Arena_InitFromOSHeap(DN_U64 size, DN_ArenaFlags flags); +DN_API DN_Arena DN_Arena_InitFromOSVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags); + +#endif // !defined(DN_OS_ALLOCATOR_H) diff --git a/OS/dn_os_containers.cpp b/OS/dn_os_containers.cpp new file mode 100644 index 0000000..2110e01 --- /dev/null +++ b/OS/dn_os_containers.cpp @@ -0,0 +1,202 @@ +#define DN_OS_CONTAINERS_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ +// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| +// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ +// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ +// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | +// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | +// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ +// +// dn_containers.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_VArray ///////////////////////////////////////////////////////////////////////////////// +template +DN_VArray DN_VArray_InitByteSize(DN_USize byte_size) +{ + DN_VArray result = {}; + result.data = DN_CAST(T *) DN_OS_MemReserve(byte_size, DN_MemCommit_No, DN_MemPage_ReadWrite); + if (result.data) + result.max = byte_size / sizeof(T); + return result; +} + +template +DN_VArray DN_VArray_Init(DN_USize max) +{ + DN_VArray result = DN_VArray_InitByteSize(max * sizeof(T)); + DN_Assert(result.max >= max); + return result; +} + +template +DN_VArray DN_VArray_InitSlice(DN_Slice slice, DN_USize max) +{ + DN_USize real_max = DN_Max(slice.size, max); + DN_VArray result = DN_VArray_Init(real_max); + if (DN_VArray_IsValid(&result)) + DN_VArray_AddArray(&result, slice.data, slice.size); + return result; +} + +template +DN_VArray DN_VArray_InitCArray(T const (&items)[N], DN_USize max) +{ + DN_USize real_max = DN_Max(N, max); + DN_VArray result = DN_VArray_InitSlice(DN_Slice_Init(items, N), real_max); + return result; +} + +template +void DN_VArray_Deinit(DN_VArray *array) +{ + DN_OS_MemRelease(array->data, array->max * sizeof(T)); + *array = {}; +} + +template +bool DN_VArray_IsValid(DN_VArray const *array) +{ + bool result = array->data && array->size <= array->max; + return result; +} + +template +DN_Slice DN_VArray_Slice(DN_VArray const *array) +{ + DN_Slice result = {}; + if (array) + result = DN_Slice_Init(array->data, array->size); + return result; +} + +template +T *DN_VArray_AddArray(DN_VArray *array, T const *items, DN_USize count) +{ + T *result = DN_VArray_MakeArray(array, count, DN_ZeroMem_No); + if (result) + DN_Memcpy(result, items, count * sizeof(T)); + return result; +} + +template +T *DN_VArray_AddCArray(DN_VArray *array, T const (&items)[N]) +{ + T *result = DN_VArray_AddArray(array, items, N); + return result; +} + +template +T *DN_VArray_Add(DN_VArray *array, T const &item) +{ + T *result = DN_VArray_AddArray(array, &item, 1); + return result; +} + +template +T *DN_VArray_MakeArray(DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem) +{ + if (!DN_VArray_IsValid(array)) + return nullptr; + + if (!DN_CheckF((array->size + count) < array->max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, array->size, array->max)) + return nullptr; + + if (!DN_VArray_Reserve(array, count)) + return nullptr; + + // TODO: Use placement new + T *result = array->data + array->size; + array->size += count; + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(result, 0, count * sizeof(T)); + return result; +} + +template +T *DN_VArray_Make(DN_VArray *array, DN_ZeroMem zero_mem) +{ + T *result = DN_VArray_MakeArray(array, 1, zero_mem); + return result; +} + +template +T *DN_VArray_InsertArray(DN_VArray *array, DN_USize index, T const *items, DN_USize count) +{ + T *result = nullptr; + if (!DN_VArray_IsValid(array)) + return result; + if (DN_VArray_Reserve(array, array->size + count)) + result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); + return result; +} + +template +T *DN_VArray_InsertCArray(DN_VArray *array, DN_USize index, T const (&items)[N]) +{ + T *result = DN_VArray_InsertArray(array, index, items, N); + return result; +} + +template +T *DN_VArray_Insert(DN_VArray *array, DN_USize index, T const &item) +{ + T *result = DN_VArray_InsertArray(array, index, &item, 1); + return result; +} + +template +T *DN_VArray_PopFront(DN_VArray *array, DN_USize count) +{ + T *result = DN_CArray_PopFront(array->data, &array->size, count); + return result; +} + +template +T *DN_VArray_PopBack(DN_VArray *array, DN_USize count) +{ + T *result = DN_CArray_PopBack(array->data, &array->size, count); + return result; +} + +template +DN_ArrayEraseResult DN_VArray_EraseRange(DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) +{ + DN_ArrayEraseResult result = {}; + if (!DN_VArray_IsValid(array)) + return result; + result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); + return result; +} + +template +void DN_VArray_Clear(DN_VArray *array, DN_ZeroMem zero_mem) +{ + if (array) { + if (zero_mem == DN_ZeroMem_Yes) + DN_Memset(array->data, 0, array->size * sizeof(T)); + array->size = 0; + } +} + +template +bool DN_VArray_Reserve(DN_VArray *array, DN_USize count) +{ + if (!DN_VArray_IsValid(array) || count == 0) + return false; + + DN_USize real_commit = (array->size + count) * sizeof(T); + DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_os_core_->page_size); + if (array->commit >= aligned_commit) + return true; + bool result = DN_OS_MemCommit(array->data, aligned_commit, DN_MemPage_ReadWrite); + array->commit = aligned_commit; + return result; +} diff --git a/OS/dn_os_containers.h b/OS/dn_os_containers.h new file mode 100644 index 0000000..17ad48a --- /dev/null +++ b/OS/dn_os_containers.h @@ -0,0 +1,47 @@ +#if !defined(DN_OS_CONTAINERS_H) +#define DN_OS_CONTAINERS_H + +// NOTE: DN_VArray ///////////////////////////////////////////////////////////////////////////////// +// TODO(doyle): Add an API for shrinking the array by decomitting pages back to the OS. +template struct DN_VArray +{ + T *data; // Pointer to the start of the array items in the block of memory + DN_USize size; // Number of items currently in the array + DN_USize max; // Maximum number of items this array can store + DN_USize commit; // Bytes committed + + T *begin() { return data; } + T *end () { return data + size; } + T const *begin() const { return data; } + T const *end () const { return data + size; } +}; + +template DN_VArray DN_VArray_InitByteSize (DN_USize byte_size); +template DN_VArray DN_VArray_Init (DN_USize max); +template DN_VArray DN_VArray_InitSlice (DN_Slice slice, DN_USize max); +template DN_VArray DN_VArray_InitCArray (T const (&items)[N], DN_USize max); +template void DN_VArray_Deinit (DN_VArray *array); +template bool DN_VArray_IsValid (DN_VArray const *array); +template DN_Slice DN_VArray_Slice (DN_VArray const *array); +template bool DN_VArray_Reserve (DN_VArray *array, DN_USize count); +template T * DN_VArray_AddArray (DN_VArray *array, T const *items, DN_USize count); +template T * DN_VArray_AddCArray (DN_VArray *array, T const (&items)[N]); +template T * DN_VArray_Add (DN_VArray *array, T const &item); +#define DN_VArray_AddArrayAssert(...) DN_HardAssert(DN_VArray_AddArray(__VA_ARGS__)) +#define DN_VArray_AddCArrayAssert(...) DN_HardAssert(DN_VArray_AddCArray(__VA_ARGS__)) +#define DN_VArray_AddAssert(...) DN_HardAssert(DN_VArray_Add(__VA_ARGS__)) +template T * DN_VArray_MakeArray (DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem); +template T * DN_VArray_Make (DN_VArray *array, DN_ZeroMem zero_mem); +#define DN_VArray_MakeArrayAssert(...) DN_HardAssert(DN_VArray_MakeArray(__VA_ARGS__)) +#define DN_VArray_MakeAssert(...) DN_HardAssert(DN_VArray_Make(__VA_ARGS__)) +template T * DN_VArray_InsertArray (DN_VArray *array, DN_USize index, T const *items, DN_USize count); +template T * DN_VArray_InsertCArray (DN_VArray *array, DN_USize index, T const (&items)[N]); +template T * DN_VArray_Insert (DN_VArray *array, DN_USize index, T const &item); +#define DN_VArray_InsertArrayAssert(...) DN_HardAssert(DN_VArray_InsertArray(__VA_ARGS__)) +#define DN_VArray_InsertCArrayAssert(...) DN_HardAssert(DN_VArray_InsertCArray(__VA_ARGS__)) +#define DN_VArray_InsertAssert(...) DN_HardAssert(DN_VArray_Insert(__VA_ARGS__)) +template T DN_VArray_PopFront (DN_VArray *array, DN_USize count); +template T DN_VArray_PopBack (DN_VArray *array, DN_USize count); +template DN_ArrayEraseResult DN_VArray_EraseRange (DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); +template void DN_VArray_Clear (DN_VArray *array, DN_ZeroMem zero_mem); +#endif // !defined(DN_OS_CONTAINERS_H) diff --git a/OS/dn_os_posix.cpp b/OS/dn_os_posix.cpp new file mode 100644 index 0000000..8c23150 --- /dev/null +++ b/OS/dn_os_posix.cpp @@ -0,0 +1,1415 @@ +#define DN_OS_POSIX_CPP + +#include // readdir, opendir, closedir +#include + +// NOTE: DN_OSMem ////////////////////////////////////////////////////////////////////////////////// +static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) +{ + DN_Assert((protect & ~DN_MemPage_All) == 0); + DN_Assert(protect != 0); + uint32_t result = 0; + + if (protect & (DN_MemPage_NoAccess | DN_MemPage_Guard)) { + result = PROT_NONE; + } else { + if (protect & DN_MemPage_Read) + result = PROT_READ; + if (protect & DN_MemPage_Write) + result = PROT_WRITE; + } + return result; +} + +DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, uint32_t page_flags) +{ + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + + if (commit == DN_MemCommit_Yes) + os_page_flags |= (PROT_READ | PROT_WRITE); + + void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + if (result == MAP_FAILED) + result = nullptr; + return result; +} + +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, uint32_t page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + result = mprotect(ptr, size, os_page_flags) == 0; + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) +{ + mprotect(ptr, size, PROT_NONE); + madvise(ptr, size, MADV_FREE); +} + +DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) +{ + munmap(ptr, size); +} + +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, uint32_t page_flags) +{ + if (!ptr || size == 0) + return 0; + + static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_STR8( + "Page protection requires pointers to be page aligned because we " + "can only guard memory at a multiple of the page boundary."); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_os_core_->page_size), + "%s", + ALIGNMENT_ERROR_MSG.data); + DN_AssertF( + DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + int result = mprotect(ptr, size, os_page_flags); + DN_AssertF(result == 0, "mprotect failed (%d)", errno); + return result; +} + +DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) +{ + void *result = zero_mem == DN_ZeroMem_Yes ? calloc(1, size) : malloc(size); + return result; +} + +DN_API void DN_OS_MemDealloc(void *ptr) +{ + free(ptr); +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() +{ + DN_OSDateTime result = {}; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + + // NOTE: localtime_r is used because it is thread safe + // See: https://linux.die.net/man/3/localtime + // According to POSIX.1-2004, localtime() is required to behave as though + // tzset(3) was called, while localtime_r() does not have this requirement. + // For portable code tzset(3) should be called before localtime_r(). + for (static bool once = true; once; once = false) + tzset(); + + struct tm time = {}; + localtime_r(&ts.tv_sec, &time); + + result.hour = time.tm_hour; + result.minutes = time.tm_min; + result.seconds = time.tm_sec; + + result.day = DN_CAST(uint8_t) time.tm_mday; + result.month = DN_CAST(uint8_t) time.tm_mon + 1; + result.year = 1900 + DN_CAST(int16_t) time.tm_year; + return result; +} + +DN_API uint64_t DN_OS_DateUnixTimeNs() +{ + struct timespec ts = {}; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t result = (ts.tv_sec * 1000 /*ms*/ * 1000 /*us*/ * 1000 /*ns*/) + ts.tv_nsec; + return result; +} + +DN_API uint64_t DN_OS_DateLocalToUnixTimeS(DN_OSDateTime) +{ + DN_AssertOnce(!"Unimplemented"); + uint64_t result = 0; + return result; +} + +DN_API uint64_t DN_OS_DateToUnixTimeS(DN_OSDateTime date) +{ + DN_Assert(DN_OS_DateIsValid(date)); + struct tm timeinfo = {}; + timeinfo.tm_year = date.year - 1900; + timeinfo.tm_mon = date.month - 1; + timeinfo.tm_mday = date.day; + timeinfo.tm_hour = date.hour; + timeinfo.tm_min = date.minutes; + timeinfo.tm_sec = date.seconds; + uint64_t result = mktime(&timeinfo); + return result; +} + +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(uint64_t time) +{ + time_t posix_time = DN_CAST(time_t) time; + struct tm posix_date = *gmtime(&posix_time); + DN_OSDateTime result = {}; + result.year = posix_date.tm_year + 1900; + result.month = posix_date.tm_mon + 1; + result.day = posix_date.tm_mday; + result.hour = posix_date.tm_hour; + result.minutes = posix_date.tm_min; + result.seconds = posix_date.tm_sec; + return result; +} + +DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) +{ +#if defined(DN_PLATFORM_EMSCRIPTEN) + (void)buffer; + (void)size; + return false; +#else + if (!buffer || size < 0) + return false; + + if (size == 0) + return true; + + DN_AssertF(size <= 32, + "We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix " + "guarantees 32 " + "bytes can always be fulfilled by this system at a time"); + // TODO(doyle): + // https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c + // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html + uint32_t read_bytes = 0; + do { + read_bytes = + getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes + } while (read_bytes != size || errno == EAGAIN); + return true; +#endif +} + +DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) +{ + DN_AssertFOnce(false, "Unimplemented"); + (void)name; + (void)value; + bool result = false; + return result; +} + +DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSDiskSpace result = {}; + DN_Str8 path_z_terminated = DN_Str8_Copy(tmem.arena, path); + + struct statvfs info = {}; + if (statvfs(path_z_terminated.data, &info) != 0) + return result; + + result.success = true; + result.free = info.f_bavail * info.f_frsize; + result.size = info.f_blocks * info.f_frsize; + return result; +} + +DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + + int required_size_wo_null_terminator = 0; + for (int try_size = 128;; try_size *= 2) { + auto scoped_arena = DN_ArenaTempMemScope(arena); + char *try_buf = DN_Arena_NewArray(arena, char, try_size, DN_ZeroMem_No); + int bytes_written = readlink("/proc/self/exe", try_buf, try_size); + if (bytes_written == -1) { + // Failed, we're unable to determine the executable directory + break; + } else if (bytes_written == try_size) { + // Try again, if returned size was equal- we may of prematurely + // truncated according to the man pages + continue; + } else { + // readlink will give us the path to the executable. Once we + // determine the correct buffer size required to get the full file + // path, we do some post-processing on said string and extract just + // the directory. + + // TODO(dn): It'd be nice if there's some way of keeping this + // try_buf around, memcopy the byte and trash the try_buf from the + // arena. Instead we just get the size and redo the call one last + // time after this "calculate" step. + DN_AssertF(bytes_written < try_size, + "bytes_written can never be greater than the try size, function writes at " + "most try_size"); + required_size_wo_null_terminator = bytes_written; + break; + } + } + + if (required_size_wo_null_terminator) { + DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); + char *exe_path = + DN_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, DN_ZeroMem_No); + exe_path[required_size_wo_null_terminator] = 0; + + int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator); + if (bytes_written == -1) { + // Note that if read-link fails again can be because there's + // a potential race condition here, our exe or directory could have + // been deleted since the last call, so we need to be careful. + DN_Arena_TempMemEnd(temp_mem); + } else { + result = DN_Str8_Init(exe_path, required_size_wo_null_terminator); + } + } + return result; +} + +DN_API void DN_OS_SleepMs(DN_UInt milliseconds) +{ + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1000000; // Convert remaining milliseconds to nanoseconds + // nanosleep can fail if interrupted by a signal, so we loop until the full sleep time has passed + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) + ; +} + +DN_API uint64_t DN_OS_PerfCounterFrequency() +{ + // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which + // increments at nanosecond granularity. + uint64_t result = 1'000'000'000; + return result; +} + +DN_API uint64_t DN_OS_PerfCounterNow() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + uint64_t result = DN_CAST(uint64_t) ts.tv_sec * 1'000'000'000 + DN_CAST(uint64_t) ts.tv_nsec; + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) +{ + DN_OSPathInfo result = {}; + if (!DN_Str8_HasData(path)) + return result; + + struct stat file_stat; + if (lstat(path.data, &file_stat) != -1) { + result.exists = true; + result.size = file_stat.st_size; + result.last_access_time_in_s = file_stat.st_atime; + result.last_write_time_in_s = file_stat.st_mtime; + // TODO(dn): Seems linux does not support creation time via stat. We + // shoddily deal with this. + result.create_time_in_s = DN_Min(result.last_access_time_in_s, result.last_write_time_in_s); + + if (S_ISDIR(file_stat.st_mode)) + result.type = DN_OSPathInfoType_Directory; + else if (S_ISREG(file_stat.st_mode)) + result.type = DN_OSPathInfoType_File; + } + return result; +} + +DN_API bool DN_OS_PathDelete(DN_Str8 path) +{ + bool result = false; + if (DN_Str8_HasData(path)) + result = remove(path.data) == 0; + return result; +} + +DN_API bool DN_OS_FileExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); + return result; +} + +DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *error) +{ + bool result = false; + #if defined(DN_PLATFORM_EMSCRIPTEN) + DN_OS_ErrSinkAppendF(error, 1, "Unsupported on Emscripten because of their VFS model"); + #else + int src_fd = open(src.data, O_RDONLY); + if (src_fd == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to open file '%.*s' for copying: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DN_DEFER + { + close(src_fd); + }; + + // NOTE: File permission is set to read/write by owner, read by others + int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0), 0644); + if (dest_fd == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to open file destination '%.*s' for copying to: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DN_DEFER + { + close(dest_fd); + }; + + struct stat stat_existing; + int fstat_result = fstat(src_fd, &stat_existing); + if (fstat_result == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to query file size of '%.*s' for copying: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + + ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); + result = (bytes_written == stat_existing.st_size); + if (!result) { + int error_code = errno; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 file_size_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, stat_existing.st_size, DN_CVTU64ByteSizeType_Auto); + DN_Str8 bytes_written_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, bytes_written, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(error, + error_code, + "Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file " + "size is %.*s: (%d) %s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + DN_STR_FMT(bytes_written_str8), + DN_STR_FMT(file_size_str8), + error_code, + strerror(error_code)); + } + + #endif + return result; +} + +DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *error) +{ + // See: https://github.com/gingerBill/gb/blob/master/gb.h + bool result = false; + bool file_moved = true; + if (link(src.data, dest.data) == -1) { + // NOTE: Link can fail if we're trying to link across different volumes + // so we fall back to a binary directory. + file_moved |= DN_OS_CopyFile(src, dest, overwrite, error); + } + + if (file_moved) { + result = true; + int unlink_result = unlink(src.data); + if (unlink_result == -1) { + int error_code = errno; + DN_OS_ErrSinkAppendF( + error, + error_code, + "File '%.*s' was moved but failed to be unlinked from old location: (%d) %s", + DN_STR_FMT(src), + error_code, + strerror(error_code)); + } + } + return result; +} + +DN_API bool DN_OS_MakeDir(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + bool result = true; + + // TODO(doyle): Implement this without using the path indexes, it's not + // necessary. See Windows implementation. + DN_USize path_indexes_size = 0; + uint16_t path_indexes[64] = {}; + + DN_Str8 copy = DN_Str8_Copy(tmem.arena, path); + for (DN_USize index = copy.size - 1; index < copy.size; index--) { + bool first_char = index == (copy.size - 1); + char ch = copy.data[index]; + if (ch == '/' || first_char) { + char temp = copy.data[index]; + + if (!first_char) + copy.data[index] = 0; // Temporarily null terminate it + + bool is_file = DN_OS_FileExists(copy); + + if (!first_char) + copy.data[index] = temp; // Undo null termination + + if (is_file) { + // NOTE: There's something that exists in at this path, but + // it's not a directory. This request to make a directory is + // invalid. + return false; + } else if (DN_OS_DirExists(copy)) { + // NOTE: We found a directory, we can stop here and start + // building up all the directories that didn't exist up to + // this point. + break; + } else { + // NOTE: There's nothing that exists at this path, we can + // create a directory here + path_indexes[path_indexes_size++] = DN_CAST(uint16_t) index; + } + } + } + + for (DN_USize index = path_indexes_size - 1; result && index < path_indexes_size; index--) { + uint16_t path_index = path_indexes[index]; + char temp = copy.data[path_index]; + + if (index != 0) + copy.data[path_index] = 0; + result |= mkdir(copy.data, 0774) == 0; + if (index != 0) + copy.data[path_index] = temp; + } + return result; +} + +DN_API bool DN_OS_DirExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + struct stat stat_result; + if (lstat(path.data, &stat_result) != -1) + result = S_ISDIR(stat_result.st_mode); + return result; +} + +DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) +{ + if (!it->handle) { + it->handle = opendir(path.data); + if (!it->handle) + return false; + } + + struct dirent *entry; + for (;;) { + entry = readdir(DN_CAST(DIR *) it->handle); + if (entry == NULL) + break; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + DN_USize name_size = DN_CStr8_Size(entry->d_name); + DN_USize clamped_size = DN_Min(sizeof(it->buffer) - 1, name_size); + DN_AssertF(name_size == clamped_size, "name: %s, name_size: %zu, clamped_size: %zu", entry->d_name, name_size, clamped_size); + DN_Memcpy(it->buffer, entry->d_name, clamped_size); + it->buffer[clamped_size] = 0; + it->file_name = DN_Str8_Init(it->buffer, clamped_size); + return true; + } + + closedir(DN_CAST(DIR *) it->handle); + it->handle = NULL; + it->file_name = {}; + it->buffer[0] = 0; + return false; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, + DN_OSFileOpen open_mode, + DN_OSFileAccess access, + DN_OSErrSink *error) +{ + DN_OSFile result = {}; + if (!DN_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~(DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0))) { + DN_InvalidCodePath; + return result; + } + + if (access & DN_OSFileAccess_Execute) { + result.error = true; + DN_OS_ErrSinkAppendF( + error, + 1, + "Failed to open file '%.*s': File access flag 'execute' is not supported", + DN_STR_FMT(path)); + DN_InvalidCodePath; // TODO: Not supported via fopen + return result; + } + + // NOTE: fopen interface is not as expressive as the Win32 + // We will fopen the file beforehand to setup the state/check for validity + // before closing and reopening it with the correct request access + // permissions. + { + FILE *handle = nullptr; + switch (open_mode) { + case DN_OSFileOpen_CreateAlways: handle = fopen(path.data, "w"); break; + case DN_OSFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break; + case DN_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; + default: DN_InvalidCodePath; break; + } + + if (!handle) { // TODO(doyle): FileOpen flag to string + result.error = true; + DN_OS_ErrSinkAppendF(error, + 1, + "Failed to open file '%.*s': File could not be opened in requested " + "mode 'DN_OSFileOpen' flag %d", + DN_STR_FMT(path), + open_mode); + return result; + } + fclose(handle); + } + + char const *fopen_mode = nullptr; + if (access & DN_OSFileAccess_AppendOnly) + fopen_mode = "a+"; + else if (access & DN_OSFileAccess_Write) + fopen_mode = "w+"; + else if (access & DN_OSFileAccess_Read) + fopen_mode = "r"; + + FILE *handle = fopen(path.data, fopen_mode); + if (!handle) { + result.error = true; + DN_OS_ErrSinkAppendF(error, + 1, + "Failed to open file '%S': File could not be opened with requested " + "access mode 'DN_OSFileAccess' %d", + path, + fopen_mode); + return result; + } + result.handle = handle; + return result; +} + +DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err) +{ + DN_OSFileRead result = {}; + if (!file || !file->handle || file->error || !buffer || size <= 0) + return result; + + result.bytes_read = fread(buffer, 1, size, DN_CAST(FILE *) file->handle); + if (feof(DN_CAST(FILE*)file->handle)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(err, 1, "Failed to read %S from file", buffer_size_str8); + return result; + } + + result.success = true; + return result; +} + +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + bool result = + fwrite(buffer, DN_CAST(DN_USize) size, 1 /*count*/, DN_CAST(FILE *) file->handle) == + 1 /*count*/; + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 buffer_size_str8 = + DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF( + err, 1, "Failed to write buffer (%s) to file handle", DN_STR_FMT(buffer_size_str8)); + } + return result; +} + +DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_OSErrSink *err) +{ + // TODO: errno is not thread safe + int fd = fileno(DN_CAST(FILE *) file->handle); + if (fd == -1) { + DN_OS_ErrSinkAppendF(err, errno, "Failed to flush file buffer to disk, file handle could not be converted to descriptor (%d): %s", fd, strerror(errno)); + return false; + } + + int fsync_result = fsync(fd); + if (fsync_result == -1) { + DN_OS_ErrSinkAppendF(err, errno, "Failed to flush file buffer to disk (%d): %s", fsync_result, strerror(errno)); + return false; + } + return true; +} + +DN_API void DN_OS_FileClose(DN_OSFile *file) +{ + if (!file || !file->handle || file->error) + return; + fclose(DN_CAST(FILE *) file->handle); + *file = {}; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit(int32_t exit_code) +{ + exit(DN_CAST(int) exit_code); +} + +enum DN_OSPipeType_ +{ + DN_OSPipeType__Read, + DN_OSPipeType__Write, + DN_OSPipeType__Count, +}; + +DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, + DN_Arena *arena, + DN_OSErrSink *error) +{ + DN_OSExecResult result = {}; + if (!handle.process || handle.os_error_code || handle.exit_code) { + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + return result; + } + +#if defined(DN_PLATFORM_EMSCRIPTEN) + DN_InvalidCodePathF("Unsupported operation"); +#endif + + static_assert(sizeof(pid_t) <= sizeof(handle.process), + "We store the PID opaquely in a register sized pointer"); + pid_t process = {}; + DN_Memcpy(&process, &handle.process, sizeof(process)); + for (;;) { + int status = 0; + if (waitpid(process, &status, 0) < 0) { + result.os_error_code = errno; + break; + } + + if (WIFEXITED(status)) { + result.exit_code = WEXITSTATUS(status); + break; + } + + if (WIFSIGNALED(status)) { + result.os_error_code = WTERMSIG(status); + break; + } + } + + int stdout_pipe[DN_OSPipeType__Count] = {}; + int stderr_pipe[DN_OSPipeType__Count] = {}; + DN_Memcpy(&stdout_pipe[DN_OSPipeType__Read], + &handle.stdout_read, + sizeof(stdout_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&stdout_pipe[DN_OSPipeType__Write], + &handle.stdout_write, + sizeof(stdout_pipe[DN_OSPipeType__Write])); + DN_Memcpy(&stderr_pipe[DN_OSPipeType__Read], + &handle.stderr_read, + sizeof(stderr_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&stderr_pipe[DN_OSPipeType__Write], + &handle.stderr_write, + sizeof(stderr_pipe[DN_OSPipeType__Write])); + + // NOTE: Process has finished, stop the write end of the pipe + close(stdout_pipe[DN_OSPipeType__Write]); + close(stderr_pipe[DN_OSPipeType__Write]); + + // NOTE: Read the data from the read end of the pipe + if (result.os_error_code == 0) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + if (arena && handle.stdout_read) { + char buffer[4096]; + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + for (;;) { + ssize_t bytes_read = + read(stdout_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); + } + + result.stdout_text = DN_Str8Builder_Build(&builder, arena); + } + + if (arena && handle.stderr_read) { + char buffer[4096]; + DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); + for (;;) { + ssize_t bytes_read = + read(stderr_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); + if (bytes_read <= 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); + } + + result.stderr_text = DN_Str8Builder_Build(&builder, arena); + } + } + + close(stdout_pipe[DN_OSPipeType__Read]); + close(stderr_pipe[DN_OSPipeType__Read]); + return result; +} + +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, + DN_OSExecArgs *args, + DN_OSErrSink *error) +{ +#if defined(DN_PLATFORM_EMSCRIPTEN) + DN_InvalidCodePathF("Unsupported operation"); +#endif + DN_AssertFOnce(args->environment.size == 0, "Unimplemented in POSIX"); + + DN_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); + int stdout_pipe[DN_OSPipeType__Count] = {}; + int stderr_pipe[DN_OSPipeType__Count] = {}; + + // NOTE: Open stdout pipe ////////////////////////////////////////////////////////////////////// + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { + if (pipe(stdout_pipe) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create stdout pipe to redirect the output of the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + DN_Assert(stdout_pipe[DN_OSPipeType__Read] != 0); + DN_Assert(stdout_pipe[DN_OSPipeType__Write] != 0); + } + + DN_DEFER + { + if (result.os_error_code == 0 && result.exit_code == 0) + return; + close(stdout_pipe[DN_OSPipeType__Read]); + close(stdout_pipe[DN_OSPipeType__Write]); + }; + + // NOTE: Open stderr pipe ////////////////////////////////////////////////////////////////////// + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + stderr_pipe[DN_OSPipeType__Read] = stdout_pipe[DN_OSPipeType__Read]; + stderr_pipe[DN_OSPipeType__Write] = stdout_pipe[DN_OSPipeType__Write]; + } else if (pipe(stderr_pipe) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create stderr pipe to redirect the output of the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + DN_Assert(stderr_pipe[DN_OSPipeType__Read] != 0); + DN_Assert(stderr_pipe[DN_OSPipeType__Write] != 0); + } + + DN_DEFER + { + if (result.os_error_code == 0 && result.exit_code == 0) + return; + close(stderr_pipe[DN_OSPipeType__Read]); + close(stderr_pipe[DN_OSPipeType__Write]); + }; + + pid_t child_pid = fork(); + if (child_pid < 0) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to fork process to execute the command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + if (child_pid == 0) { // Child process + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout) && + (dup2(stdout_pipe[DN_OSPipeType__Write], STDOUT_FILENO) == -1)) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to redirect stdout 'write' pipe for output of command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && + (dup2(stderr_pipe[DN_OSPipeType__Write], STDERR_FILENO) == -1)) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to redirect stderr 'read' pipe for output of command '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + + // NOTE: Convert the command into something suitable for execvp + char **argv = + DN_Arena_NewArray(tmem.arena, char *, cmd_line.size + 1 /*null*/, DN_ZeroMem_Yes); + if (!argv) { + result.exit_code = -1; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create argument values from command line '%.*s': Out of memory", + DN_STR_FMT(cmd_rendered)); + return result; + } + + DN_ForIndexU(arg_index, cmd_line.size) + { + DN_Str8 arg = cmd_line.data[arg_index]; + argv[arg_index] = DN_Str8_Copy(tmem.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated + } + + // NOTE: Change the working directory if there is one + char *prev_working_dir = nullptr; + DN_DEFER + { + if (!prev_working_dir) + return; + if (result.os_error_code == 0) { + int chdir_result = chdir(prev_working_dir); + (void)chdir_result; + } + free(prev_working_dir); + }; + + if (args->working_dir.size) { + prev_working_dir = get_current_dir_name(); + DN_Str8 working_dir = DN_Str8_Copy(tmem.arena, args->working_dir); + if (chdir(working_dir.data) == -1) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to create argument values from command line '%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + } + + // NOTE: Execute the command. We reuse argv because the first arg, the + // binary to execute is guaranteed to be null-terminated. + if (execvp(argv[0], argv) < 0) { + result.os_error_code = errno; + DN_OS_ErrSinkAppendF( + error, + result.os_error_code, + "Failed to execute command'%.*s': %s", + DN_STR_FMT(cmd_rendered), + strerror(result.os_error_code)); + return result; + } + } + + DN_Assert(result.os_error_code == 0); + DN_Memcpy(&result.stdout_read, + &stdout_pipe[DN_OSPipeType__Read], + sizeof(stdout_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&result.stdout_write, + &stdout_pipe[DN_OSPipeType__Write], + sizeof(stdout_pipe[DN_OSPipeType__Write])); + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + DN_Memcpy(&result.stderr_read, + &stderr_pipe[DN_OSPipeType__Read], + sizeof(stderr_pipe[DN_OSPipeType__Read])); + DN_Memcpy(&result.stderr_write, + &stderr_pipe[DN_OSPipeType__Write], + sizeof(stderr_pipe[DN_OSPipeType__Write])); + } + result.exec_flags = args->flags; + DN_Memcpy(&result.process, &child_pid, sizeof(child_pid)); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, + char *stdout_buffer, + size_t *stdout_size, + char *stderr_buffer, + size_t *stderr_size, + uint32_t timeout_ms, + DN_OSErrSink *err) +{ + DN_InvalidCodePath; + DN_OSExecResult result = {}; + return result; +} + +#if !defined(DN_NO_SEMAPHORE) +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(uint32_t initial_count) +{ + DN_OSSemaphore result = {}; + int pshared = 0; // Share the semaphore across all threads in the process + if (sem_init(&result.posix_handle, pshared, initial_count) == 0) + result.posix_init = true; + return result; +} + +DN_API bool DN_OS_SemaphoreIsValid(DN_OSSemaphore *semaphore) +{ + bool result = false; + if (semaphore) + result = semaphore->posix_init; + return result; +} + +DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) +{ + if (!DN_OS_SemaphoreIsValid(semaphore)) + return; + // TODO(doyle): Error handling? + if (semaphore->posix_init) + sem_destroy(&semaphore->posix_handle); + *semaphore = {}; +} + +// NOTE: These functions don't need semaphore to be passed by pointer, **BUT** +// the POSIX implementation disallows copies of sem_t. In particular: +// +// Source: The Open Group Base Specifications Issue 7, 2018 edition +// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 +// +// 2.9.9 Synchronization Object Copies and Alternative Mappings +// +// For barriers, condition variables, mutexes, and read-write locks, [TSH] +// [Option Start] if the process-shared attribute is set to +// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the +// address used to initialize it can be used for performing synchronization. The +// effect of referring to another mapping of the same object when locking, +// unlocking, or destroying the object is undefined. [...] The effect of +// referring to a copy of the object when locking, unlocking, or destroying it +// is undefined. + +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount) +{ + if (!DN_OS_SemaphoreIsValid(semaphore)) + return; + #if defined(DN_OS_WIN32) + sem_post_multiple(&semaphore->posix_handle, amount); // mingw extension + #else + DN_ForIndexU(index, amount) + sem_post(&semaphore->posix_handle); + #endif // !defined(DN_OS_WIN32) +} + +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, + uint32_t timeout_ms) +{ + DN_OSSemaphoreWaitResult result = {}; + if (!DN_OS_SemaphoreIsValid(semaphore)) + return result; + + if (timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT) { + int wait_result = 0; + do { + wait_result = sem_wait(&semaphore->posix_handle); + } while (wait_result == -1 && errno == EINTR); + + if (wait_result == 0) + result = DN_OSSemaphoreWaitResult_Success; + } else { + struct timespec abs_timeout = {}; + abs_timeout.tv_sec = timeout_ms / 1000; + abs_timeout.tv_nsec = (timeout_ms % 1000) * 1'000'000; + if (sem_timedwait(&semaphore->posix_handle, &abs_timeout) == 0) + result = DN_OSSemaphoreWaitResult_Success; + else if (errno == ETIMEDOUT) + result = DN_OSSemaphoreWaitResult_Timeout; + } + return result; +} +#endif // !defined(DN_NO_SEMAPHORE) + +#if !defined(DN_NO_THREAD) +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit() +{ + DN_OSMutex result = {}; + if (pthread_mutexattr_init(&result.posix_attribs) != 0) + return result; + if (pthread_mutex_init(&result.posix_handle, &result.posix_attribs) != 0) + return result; + return result; +} + +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutexattr_destroy(&mutex->posix_attribs); + pthread_mutex_destroy(&mutex->posix_handle); +} + +DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutex_lock(&mutex->posix_handle); +} + +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) +{ + if (!mutex) + return; + pthread_mutex_unlock(&mutex->posix_handle); +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// +static void *DN_OS_ThreadFunc_(void *user_context) +{ + DN_OS_ThreadExecute_(user_context); + return nullptr; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and + // the ID are different things. For pthreads then we just duplicate the + // thread ID to both variables + pthread_t p_thread = {}; + static_assert(sizeof(p_thread) <= sizeof(thread->handle), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + static_assert(sizeof(p_thread) <= sizeof(thread->thread_id), + "We store the thread handle opaquely in our abstraction, " + "there must be enough bytes to store pthread's structure"); + + pthread_attr_t attribs = {}; + pthread_attr_init(&attribs); + result = pthread_create(&p_thread, &attribs, DN_OS_ThreadFunc_, thread) == 0; + pthread_attr_destroy(&attribs); + + if (result) { + DN_Memcpy(&thread->handle, &p_thread, sizeof(p_thread)); + DN_Memcpy(&thread->thread_id, &p_thread, sizeof(p_thread)); + } + + if (result) { + DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + DN_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + pthread_t thread_id = {}; + DN_Memcpy(&thread_id, &thread->thread_id, sizeof(thread_id)); + + void *return_val = nullptr; + pthread_join(thread_id, &return_val); + thread->handle = {}; + thread->thread_id = {}; +} + +DN_API uint32_t DN_OS_ThreadID() +{ + pid_t result = gettid(); + DN_Assert(gettid() >= 0); + return DN_CAST(uint32_t) result; +} + +DN_API void DN_Posix_ThreadSetName(DN_Str8 name) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); + pthread_t thread = pthread_self(); + pthread_setname_np(thread, (char *)copy.data); +} +#endif // !defined(DN_NO_THREAD) + +DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus() +{ + DN_POSIXProcSelfStatus result = {}; + + // NOTE: Example + // + // ... + // VmPeak: 3352 kB + // VmSize: 3352 kB + // VmLck: 0 kB + // ... + // + // VmSize is the total virtual memory used + DN_OSFile file = DN_OS_FileOpen(DN_STR8("/proc/self/status"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, nullptr); + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + + if (!file.error) { + char buf[256]; + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + for (;;) { + DN_OSFileRead read = DN_OS_FileRead(&file, buf, sizeof(buf), nullptr); + if (!read.success || read.bytes_read == 0) + break; + DN_Str8Builder_AppendF(&builder, "%.*s", DN_CAST(int)read.bytes_read, buf); + } + + DN_Str8 const NAME = DN_STR8("Name:"); + DN_Str8 const PID = DN_STR8("Pid:"); + DN_Str8 const VM_PEAK = DN_STR8("VmPeak:"); + DN_Str8 const VM_SIZE = DN_STR8("VmSize:"); + DN_Str8 status_buf = DN_Str8Builder_BuildFromTLS(&builder); + DN_Slice lines = DN_Str8_SplitAllocFromTLS(status_buf, DN_STR8("\n"), DN_Str8SplitIncludeEmptyStrings_No); + + DN_ForIt(line_it, DN_Str8, &lines) { + DN_Str8 line = DN_Str8_TrimWhitespaceAround(*line_it.data); + if (DN_Str8_StartsWith(line, NAME, DN_Str8EqCase_Insensitive)) { + DN_Str8 str8 = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, NAME.size, line.size)); + result.name_size = DN_Min(str8.size, sizeof(result.name)); + DN_Memcpy(result.name, str8.data, result.name_size); + } else if (DN_Str8_StartsWith(line, PID, DN_Str8EqCase_Insensitive)) { + DN_Str8 str8 = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, PID.size, line.size)); + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(str8, 0); + result.pid = to_u64.value; + DN_Assert(to_u64.success); + } else if (DN_Str8_StartsWith(line, VM_SIZE, DN_Str8EqCase_Insensitive)) { + DN_Str8 size_with_kb = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, VM_SIZE.size, line.size)); + DN_Assert(DN_Str8_EndsWith(size_with_kb, DN_STR8("kB"))); + DN_Str8 vm_size = DN_Str8_BinarySplit(size_with_kb, DN_STR8(" ")).lhs; + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(vm_size, 0); + result.vm_size = DN_Kilobytes(to_u64.value); + DN_Assert(to_u64.success); + } else if (DN_Str8_StartsWith(line, VM_PEAK, DN_Str8EqCase_Insensitive)) { + DN_Str8 size_with_kb = DN_Str8_TrimWhitespaceAround(DN_Str8_Slice(line, VM_PEAK.size, line.size)); + DN_Assert(DN_Str8_EndsWith(size_with_kb, DN_STR8("kB"))); + DN_Str8 vm_size = DN_Str8_BinarySplit(size_with_kb, DN_STR8(" ")).lhs; + DN_Str8ToU64Result to_u64 = DN_Str8_ToU64(vm_size, 0); + result.vm_peak = DN_Kilobytes(to_u64.value); + DN_Assert(to_u64.success); + } + } + } + DN_OS_FileClose(&file); + return result; +} + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +#if 0 // TODO(doyle): Implement websockets for Windows and Emscripten +static EM_BOOL EMWebSocketOnOpenCallback(int type, const EmscriptenWebSocketOpenEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + // EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(event->socket, R"({"jsonrpc":"2.0","id":1,"method": "eth_subscribe","params":["newHeads"]})"); + // if (result) + // DN_LOG_InfoF("Failed to emscripten_websocket_send_utf8_text(): %d\n", result); + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnMsgCallback(int type, const EmscriptenWebSocketMessageEvent *event __attribute__((nonnull)), void *user_context) +{ + (void)type; + (void)user_context; + (void)event; + if (event->isText) { + DN_LOG_InfoF("Received: %.*s", event->numBytes, event->data); + } else { + DN_LOG_InfoF("Received: %d bytes", event->numBytes); + } + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnErrorCallback(int type, const EmscriptenWebSocketErrorEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} + +static EM_BOOL EMWebSocketOnCloseCallback(int type, const EmscriptenWebSocketCloseEvent *event, void *user_context) +{ + (void)user_context; + (void)type; + (void)event; + return EM_TRUE; +} +#endif + +#if defined(DN_PLATFORM_EMSCRIPTEN) +static void DN_OS_HttpRequestEMFetchOnSuccessCallback(emscripten_fetch_t *fetch) +{ + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; + if (!DN_Check(response)) + return; + + response->http_status = DN_CAST(uint32_t) fetch->status; + response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); + if (response->body.data) + DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); + + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); +} + +static void DN_OS_HttpRequestEMFetchOnErrorCallback(emscripten_fetch_t *fetch) +{ + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; + if (!DN_Check(response)) + return; + + response->http_status = DN_CAST(uint32_t) fetch->status; + response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); + if (response->body.size) + DN_Memcpy(response->body.data, fetch->data, fetch->numBytes); + + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); +} +#endif + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, + DN_Arena *arena, + DN_Str8 host, + DN_Str8 path, + DN_OSHttpRequestSecure secure, + DN_Str8 method, + DN_Str8 body, + DN_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = + response->tmem_arena ? response->tmem_arena : &response->tmp_arena; + + DN_Arena *tmem = response->tmem_arena; + DN_OSTLSTMem tmem_ = DN_OS_TLSTMem(arena); + if (!tmem) + tmem = tmem_.arena; + +#if defined(DN_PLATFORM_EMSCRIPTEN) + emscripten_fetch_attr_t fetch_attribs = {}; + emscripten_fetch_attr_init(&fetch_attribs); + + if (method.size >= sizeof(fetch_attribs.requestMethod)) { + response->error_msg = + DN_Str8_InitF(arena, + "Request method in EM has a size limit of 31 characters, method was " + "'%.*s' which is %zu characters long", + DN_STR_FMT(method), + method.size); + DN_CheckF(method.size < sizeof(fetch_attribs.requestMethod), + "%.*s", + DN_STR_FMT(response->error_msg)); + response->error_code = DN_CAST(uint32_t) - 1; + DN_Atomic_AddU32(&response->done, 1); + return; + } + + DN_Memcpy(fetch_attribs.requestMethod, method.data, method.size); + + fetch_attribs.requestData = body.data; + fetch_attribs.requestDataSize = body.size; + fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + fetch_attribs.onsuccess = DN_OS_HttpRequestEMFetchOnSuccessCallback; + fetch_attribs.onerror = DN_OS_HttpRequestEMFetchOnErrorCallback; + fetch_attribs.userData = response; + + DN_Str8 url = DN_Str8_InitF(scratch_arena, "%.*s%.*s", DN_STR_FMT(host), DN_STR_FMT(path)); + DN_LOG_InfoF("Initiating HTTP '%s' request to '%.*s' with payload '%.*s'", + fetch_attribs.requestMethod, + DN_STR_FMT(url), + DN_STR_FMT(body)); + response->on_complete_semaphore = DN_OS_SemaphoreInit(0); + response->em_handle = emscripten_fetch(&fetch_attribs, url.data); +#else // #elif defined(DN_OS_WIN32) + DN_InvalidCodePathF("Unimplemented function"); +#endif +} + +DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) +{ +// NOTE: Cleanup +#if defined(DN_PLATFORM_EMSCRIPTEN) + if (response->em_handle) { + emscripten_fetch_close(response->em_handle); + response->em_handle = nullptr; + } +#endif // #elif defined(DN_OS_WIN32) + + DN_Arena_Deinit(&response->tmp_arena); + if (DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + *response = {}; +} diff --git a/dqn_os_posix.h b/OS/dn_os_posix.h similarity index 76% rename from dqn_os_posix.h rename to OS/dn_os_posix.h index 823f2cb..c9bacf6 100644 --- a/dqn_os_posix.h +++ b/OS/dn_os_posix.h @@ -1,5 +1,4 @@ #pragma once -#include "dqn.h" /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,9 +12,19 @@ // $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ | // \______/ \______/ \__| \______/ \______/ \______|\__| \__| // -// dqn_os_posix.h +// dn_os_posix.h // //////////////////////////////////////////////////////////////////////////////////////////////////// */ -DN_API void DN_Posix_ThreadSetName(DN_Str8 name); +struct DN_POSIXProcSelfStatus +{ + char name[64]; + DN_U8 name_size; + DN_U32 pid; + DN_U64 vm_peak; + DN_U32 vm_size; +}; + +DN_API void DN_Posix_ThreadSetName(DN_Str8 name); +DN_API DN_POSIXProcSelfStatus DN_Posix_ProcSelfStatus(); diff --git a/OS/dn_os_print.cpp b/OS/dn_os_print.cpp new file mode 100644 index 0000000..6050902 --- /dev/null +++ b/OS/dn_os_print.cpp @@ -0,0 +1,170 @@ +#define DN_OS_PRINT_CPP + +DN_API DN_LOGStyle DN_OS_PrintStyleColour(uint8_t r, uint8_t g, uint8_t b, DN_LOGBold bold) +{ + DN_LOGStyle result = {}; + result.bold = bold; + result.colour = true; + result.r = r; + result.g = g; + result.b = b; + return result; +} + +DN_API DN_LOGStyle DN_OS_PrintStyleColourU32(uint32_t rgb, DN_LOGBold bold) +{ + uint8_t r = (rgb >> 24) & 0xFF; + uint8_t g = (rgb >> 16) & 0xFF; + uint8_t b = (rgb >> 8) & 0xFF; + DN_LOGStyle result = DN_OS_PrintStyleColour(r, g, b, bold); + return result; +} + +DN_API DN_LOGStyle DN_OS_PrintStyleBold() +{ + DN_LOGStyle result = {}; + result.bold = DN_LOGBold_Yes; + return result; +} + +DN_API void DN_OS_Print(DN_OSPrintDest dest, DN_Str8 string) +{ + DN_Assert(dest == DN_OSPrintDest_Out || dest == DN_OSPrintDest_Err); + +#if defined(DN_PLATFORM_WIN32) + // NOTE: Get the output handles from kernel //////////////////////////////////////////////////// + DN_THREAD_LOCAL void *std_out_print_handle = nullptr; + DN_THREAD_LOCAL void *std_err_print_handle = nullptr; + DN_THREAD_LOCAL bool std_out_print_to_console = false; + DN_THREAD_LOCAL bool std_err_print_to_console = false; + + if (!std_out_print_handle) { + unsigned long mode = 0; + (void)mode; + std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE); + std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0; + + std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE); + std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; + } + + // NOTE: Select the output handle ////////////////////////////////////////////////////////////// + void *print_handle = std_out_print_handle; + bool print_to_console = std_out_print_to_console; + if (dest == DN_OSPrintDest_Err) { + print_handle = std_err_print_handle; + print_to_console = std_err_print_to_console; + } + + // NOTE: Write the string ////////////////////////////////////////////////////////////////////// + DN_Assert(string.size < DN_CAST(unsigned long) - 1); + unsigned long bytes_written = 0; + (void)bytes_written; + if (print_to_console) + WriteConsoleA(print_handle, string.data, DN_CAST(unsigned long) string.size, &bytes_written, nullptr); + else + WriteFile(print_handle, string.data, DN_CAST(unsigned long) string.size, &bytes_written, nullptr); +#else + fprintf(dest == DN_OSPrintDest_Out ? stdout : stderr, "%.*s", DN_STR_FMT(string)); +#endif +} + +DN_API void DN_OS_PrintF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintFV(dest, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintFStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintFVStyle(dest, style, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_Str8 string) +{ + if (string.data && string.size) { + if (style.colour) + DN_OS_Print(dest, DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b)); + if (style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_BoldEscapeCode)); + DN_OS_Print(dest, string); + if (style.colour || style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_ResetEscapeCode)); + } +} + +static char *DN_OS_PrintVSPrintfChunker_(const char *buf, void *user, int len) +{ + DN_Str8 string = {}; + string.data = DN_CAST(char *) buf; + string.size = len; + + DN_OSPrintDest dest = DN_CAST(DN_OSPrintDest) DN_CAST(uintptr_t) user; + DN_OS_Print(dest, string); + return (char *)buf; +} + +DN_API void DN_OS_PrintFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + char buffer[STB_SPRINTF_MIN]; + STB_SPRINTF_DECORATE(vsprintfcb) + (DN_OS_PrintVSPrintfChunker_, DN_CAST(void *) DN_CAST(uintptr_t) dest, buffer, fmt, args); +} + +DN_API void DN_OS_PrintFVStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (fmt) { + if (style.colour) + DN_OS_Print(dest, DN_LOG_ColourEscapeCodeStr8FromRGB(DN_LOGColourType_Fg, style.r, style.g, style.b)); + if (style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_BoldEscapeCode)); + DN_OS_PrintFV(dest, fmt, args); + if (style.colour || style.bold == DN_LOGBold_Yes) + DN_OS_Print(dest, DN_STR8(DN_LOG_ResetEscapeCode)); + } +} + +DN_API void DN_OS_PrintLn(DN_OSPrintDest dest, DN_Str8 string) +{ + DN_OS_Print(dest, string); + DN_OS_Print(dest, DN_STR8("\n")); +} + +DN_API void DN_OS_PrintLnF(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintLnFV(dest, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintLnFV(DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OS_PrintFV(dest, fmt, args); + DN_OS_Print(dest, DN_STR8("\n")); +} + +DN_API void DN_OS_PrintLnStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_Str8 string) +{ + DN_OS_PrintStyle(dest, style, string); + DN_OS_Print(dest, DN_STR8("\n")); +} + +DN_API void DN_OS_PrintLnFStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_PrintLnFVStyle(dest, style, fmt, args); + va_end(args); +} + +DN_API void DN_OS_PrintLnFVStyle(DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OS_PrintFVStyle(dest, style, fmt, args); + DN_OS_Print(dest, DN_STR8("\n")); +} diff --git a/OS/dn_os_print.h b/OS/dn_os_print.h new file mode 100644 index 0000000..b983440 --- /dev/null +++ b/OS/dn_os_print.h @@ -0,0 +1,59 @@ +#if !defined(DN_OS_PRINT_H) +#define DN_OS_PRINT_H + +enum DN_OSPrintDest +{ + DN_OSPrintDest_Out, + DN_OSPrintDest_Err, +}; + +// NOTE: Print Macros ////////////////////////////////////////////////////////////////////////////// +#define DN_OS_PrintOut(string) DN_OS_Print(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Out, style, string) +#define DN_OS_PrintOutFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Out, style, fmt, args) + +#define DN_OS_PrintOutLn(string) DN_OS_PrintLn(DN_OSPrintDest_Out, string) +#define DN_OS_PrintOutLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Out, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Out, fmt, args) + +#define DN_OS_PrintOutLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Out, style, string); +#define DN_OS_PrintOutLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Out, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintOutLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Out, style, fmt, args); + +#define DN_OS_PrintErr(string) DN_OS_Print(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrF(fmt, ...) DN_OS_PrintF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFV(fmt, args) DN_OS_PrintFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrStyle(style, string) DN_OS_PrintStyle(DN_OSPrintDest_Err, style, string) +#define DN_OS_PrintErrFStyle(style, fmt, ...) DN_OS_PrintFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrFVStyle(style, fmt, args, ...) DN_OS_PrintFVStyle(DN_OSPrintDest_Err, style, fmt, args) + +#define DN_OS_PrintErrLn(string) DN_OS_PrintLn(DN_OSPrintDest_Err, string) +#define DN_OS_PrintErrLnF(fmt, ...) DN_OS_PrintLnF(DN_OSPrintDest_Err, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFV(fmt, args) DN_OS_PrintLnFV(DN_OSPrintDest_Err, fmt, args) + +#define DN_OS_PrintErrLnStyle(style, string) DN_OS_PrintLnStyle(DN_OSPrintDest_Err, style, string); +#define DN_OS_PrintErrLnFStyle(style, fmt, ...) DN_OS_PrintLnFStyle(DN_OSPrintDest_Err, style, fmt, ##__VA_ARGS__) +#define DN_OS_PrintErrLnFVStyle(style, fmt, args) DN_OS_PrintLnFVStyle(DN_OSPrintDest_Err, style, fmt, args); + +// NOTE: Print ///////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Print (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_Str8 string); +DN_API void DN_OS_PrintFStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintFVStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLn (DN_OSPrintDest dest, DN_Str8 string); +DN_API void DN_OS_PrintLnF (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFV (DN_OSPrintDest dest, DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API void DN_OS_PrintLnStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_Str8 string); +DN_API void DN_OS_PrintLnFStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_PrintLnFVStyle (DN_OSPrintDest dest, DN_LOGStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); +#endif // !defined(DN_OS_PRINT_H) diff --git a/OS/dn_os_string.cpp b/OS/dn_os_string.cpp new file mode 100644 index 0000000..a7c7823 --- /dev/null +++ b/OS/dn_os_string.cpp @@ -0,0 +1,304 @@ +#define DN_OS_STRING_CPP + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8_InitFFromFrame(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSGet()->frame_arena, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFFromOSHeap(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + DN_Str8 result = {}; + DN_USize size = DN_CStr8_FVSize(fmt, args); + if (size) { + result = DN_Str8_AllocFromOSHeap(size, DN_ZeroMem_No); + if (DN_Str8_HasData(result)) + DN_VSNPrintF(result.data, DN_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); + } + + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFFromTLS(DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSTopArena(), fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFVFromFrame(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSGet()->frame_arena, fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8_InitFVFromTLS(DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_Str8 result = DN_Str8_InitFV(DN_OS_TLSTopArena(), fmt, args); + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromFrame(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = DN_Str8_Alloc(DN_OS_TLSGet()->frame_arena, size, zero_mem); + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromOSHeap(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = {}; + result.data = DN_CAST(char *)DN_OS_MemAlloc(size + 1, zero_mem); + if (result.data) + result.size = size; + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_Str8_AllocFromTLS(DN_USize size, DN_ZeroMem zero_mem) +{ + DN_Str8 result = DN_Str8_Alloc(DN_OS_TLSTopArena(), size, zero_mem); + return result; +} + +DN_API DN_Str8 DN_Str8_CopyFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_CopyFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Copy(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Slice DN_Str8_SplitAllocFromFrame(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = DN_Str8_SplitAlloc(DN_OS_TLSGet()->frame_arena, string, delimiter, mode); + return result; +} + +DN_API DN_Slice DN_Str8_SplitAllocFromTLS(DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = DN_Str8_SplitAlloc(DN_OS_TLSTopArena(), string, delimiter, mode); + return result; +} + +DN_API DN_Str8 DN_Str8_SegmentFromFrame(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_Segment(DN_OS_TLSGet()->frame_arena, src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_SegmentFromTLS(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_Segment(DN_OS_TLSTopArena(), src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromFrame(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_ReverseSegment(DN_OS_TLSGet()->frame_arena, src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromTLS(DN_Str8 src, DN_USize segment_size, char segment_char) +{ + DN_Str8 result = DN_Str8_ReverseSegment(DN_OS_TLSTopArena(), src, segment_size, segment_char); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFFromFrame(DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(DN_OS_TLSGet()->frame_arena, string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_AppendFFromTLS(DN_Str8 string, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_AppendFV(DN_OS_TLSTopArena(), string, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFFromFrame(DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(DN_OS_TLSGet()->frame_arena, count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8 DN_Str8_FillFFromTLS(DN_USize count, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_Str8 result = DN_Str8_FillFV(DN_OS_TLSTopArena(), count, fmt, args); + va_end(args); + return result; +} + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromFrame(DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = DN_Str8_DotTruncateMiddle(DN_OS_TLSGet()->frame_arena, str8, side_size, truncator); + return result; +} + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromTLS(DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) +{ + DN_Str8DotTruncateResult result = DN_Str8_DotTruncateMiddle(DN_OS_TLSTopArena(), str8, side_size, truncator); + return result; +} + + +DN_API DN_Str8 DN_Str8_PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) +{ + // TODO: Implement this without requiring TLS so it can go into base strings + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + + DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(src, DN_STR8("\n")); + while (split.lhs.size) { + DN_Str8Builder_AppendRef(&builder, pad); + DN_Str8Builder_AppendRef(&builder, split.lhs); + split = DN_Str8_BinarySplit(split.rhs, DN_STR8("\n")); + if (split.lhs.size) + DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); + } + + DN_Str8 result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_Str8_PadNewLinesFromFrame(DN_Str8 src, DN_Str8 pad) +{ + DN_Str8 result = DN_Str8_PadNewLines(DN_OS_TLSGet()->frame_arena, src, pad); + return result; +} + +DN_API DN_Str8 DN_Str8_PadNewLinesFromTLS(DN_Str8 src, DN_Str8 pad) +{ + DN_Str8 result = DN_Str8_PadNewLines(DN_OS_TLSTopArena(), src, pad); + return result; +} + +DN_API DN_Str8 DN_Str8_UpperFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Upper(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_UpperFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Upper(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Str8 DN_Str8_LowerFromFrame(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Lower(DN_OS_TLSGet()->frame_arena, string); + return result; +} + +DN_API DN_Str8 DN_Str8_LowerFromTLS(DN_Str8 string) +{ + DN_Str8 result = DN_Str8_Lower(DN_OS_TLSTopArena(), string); + return result; +} + +DN_API DN_Str8 DN_Str8_Replace(DN_Str8 string, + DN_Str8 find, + DN_Str8 replace, + DN_USize start_index, + DN_Arena *arena, + DN_Str8EqCase eq_case) +{ + // TODO: Implement this without requiring TLS so it can go into base strings + DN_Str8 result = {}; + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size || find.size == 0 || string.size == 0) { + result = DN_Str8_Copy(arena, string); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder string_builder = DN_Str8Builder_Init(tmem.arena); + DN_USize max = string.size - find.size; + DN_USize head = start_index; + + for (DN_USize tail = head; tail <= max; tail++) { + DN_Str8 check = DN_Str8_Slice(string, tail, find.size); + if (!DN_Str8_Eq(check, find, eq_case)) + continue; + + if (start_index > 0 && string_builder.string_size == 0) { + // User provided a hint in the string to start searching from, we + // need to add the string up to the hint. We only do this if there's + // a replacement action, otherwise we have a special case for no + // replacements, where the entire string gets copied. + DN_Str8 slice = DN_Str8_Init(string.data, head); + DN_Str8Builder_AppendRef(&string_builder, slice); + } + + DN_Str8 range = DN_Str8_Slice(string, head, (tail - head)); + DN_Str8Builder_AppendRef(&string_builder, range); + DN_Str8Builder_AppendRef(&string_builder, replace); + head = tail + find.size; + tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string + } + + if (string_builder.string_size == 0) { + // NOTE: No replacement possible, so we just do a full-copy + result = DN_Str8_Copy(arena, string); + } else { + DN_Str8 remainder = DN_Str8_Init(string.data + head, string.size - head); + DN_Str8Builder_AppendRef(&string_builder, remainder); + result = DN_Str8Builder_Build(&string_builder, arena); + } + + return result; +} + +DN_API DN_Str8 DN_Str8_ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) +{ + DN_Str8 result = DN_Str8_Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive); + return result; +} + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8Builder_BuildFromOSHeap(DN_Str8Builder const *builder) +{ + DN_Str8 result = DN_ZeroInit; + if (!builder || builder->string_size <= 0 || builder->count <= 0) + return result; + + result.data = DN_CAST(char *) DN_OS_MemAlloc(builder->string_size + 1, DN_ZeroMem_No); + if (!result.data) + return result; + + for (DN_Str8Link *link = builder->head; link; link = link->next) { + DN_Memcpy(result.data + result.size, link->string.data, link->string.size); + result.size += link->string.size; + } + + result.data[result.size] = 0; + DN_Assert(result.size == builder->string_size); + return result; +} diff --git a/OS/dn_os_string.h b/OS/dn_os_string.h new file mode 100644 index 0000000..e463bc2 --- /dev/null +++ b/OS/dn_os_string.h @@ -0,0 +1,74 @@ +#if !defined(DN_OS_STRING_H) +#define DN_OS_STRING_H + +// NOTE: DN_Str8 /////////////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8 DN_Str8_InitFFromFrame (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8_InitFFromOSHeap (DN_FMT_ATTRIB char const *fmt, ...); +DN_API DN_Str8 DN_Str8_InitFFromTLS (DN_FMT_ATTRIB char const *fmt, ...); + +DN_API DN_Str8 DN_Str8_InitFVFromFrame (DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API DN_Str8 DN_Str8_InitFVFromTLS (DN_FMT_ATTRIB char const *fmt, va_list args); + +DN_API DN_Str8 DN_Str8_AllocFromFrame (DN_USize size, DN_ZeroMem zero_mem); +DN_API DN_Str8 DN_Str8_AllocFromOSHeap (DN_USize size, DN_ZeroMem zero_mem); +DN_API DN_Str8 DN_Str8_AllocFromTLS (DN_USize size, DN_ZeroMem zero_mem); + +DN_API DN_Str8 DN_Str8_CopyFromFrame (DN_Arena *arena, DN_Str8 string); +DN_API DN_Str8 DN_Str8_CopyFromTLS (DN_Arena *arena, DN_Str8 string); + +DN_API DN_Slice DN_Str8_SplitAllocFromFrame (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); +DN_API DN_Slice DN_Str8_SplitAllocFromTLS (DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); + +DN_API DN_Str8 DN_Str8_SegmentFromFrame (DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8_SegmentFromTLS (DN_Str8 src, DN_USize segment_size, char segment_char); + +DN_API DN_Str8 DN_Str8_ReverseSegmentFromFrame (DN_Str8 src, DN_USize segment_size, char segment_char); +DN_API DN_Str8 DN_Str8_ReverseSegmentFromTLS (DN_Str8 src, DN_USize segment_size, char segment_char); + +DN_API DN_Str8 DN_Str8_AppendFFromFrame (DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_AppendFFromTLS (DN_Str8 string, char const *fmt, ...); + +DN_API DN_Str8 DN_Str8_FillFFromFrame (DN_Str8 string, char const *fmt, ...); +DN_API DN_Str8 DN_Str8_FillFFromTLS (DN_Str8 string, char const *fmt, ...); + +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromFrame (DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); +DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddleFromTLS (DN_Str8 str8, uint32_t side_size, DN_Str8 truncator); + +DN_API DN_Str8 DN_Str8_PadNewLines (DN_Arena *arena, DN_Str8 src, DN_Str8 pad); +DN_API DN_Str8 DN_Str8_PadNewLinesFromFrame (DN_Str8 src, DN_Str8 pad); +DN_API DN_Str8 DN_Str8_PadNewLinesFromTLS (DN_Str8 src, DN_Str8 pad); + +DN_API DN_Str8 DN_Str8_UpperFromFrame (DN_Str8 string); +DN_API DN_Str8 DN_Str8_UpperFromTLS (DN_Str8 string); + +DN_API DN_Str8 DN_Str8_LowerFromFrame (DN_Str8 string); +DN_API DN_Str8 DN_Str8_LowerFromTLS (DN_Str8 string); + +DN_API DN_Str8 DN_Str8_Replace (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8_ReplaceInsensitive (DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena); + +// NOTE: DN_Str8Builder //////////////////////////////////////////////////////////////////////////// + +DN_API DN_Str8Builder DN_Str8Builder_InitFromFrame () { return DN_Str8Builder_Init(DN_OS_TLSGet()->frame_arena); } +DN_API DN_Str8Builder DN_Str8Builder_InitFromTLS () { return DN_Str8Builder_Init(DN_OS_TLSTopArena()); } + +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRefFromFrame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayRef(DN_OS_TLSGet()->frame_arena, strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayRefFromTLS (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayRef(DN_OS_TLSTopArena(), strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopyFromFrame (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayCopy(DN_OS_TLSGet()->frame_arena, strings, size); } +DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopyFromTLS (DN_Str8 const *strings, DN_USize size) { return DN_Str8Builder_InitArrayCopy(DN_OS_TLSTopArena(), strings, size); } + +DN_API DN_Str8Builder DN_Str8Builder_CopyFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_Copy(DN_OS_TLSGet()->frame_arena, builder); } +DN_API DN_Str8Builder DN_Str8Builder_CopyFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_Copy(DN_OS_TLSTopArena(), builder); } + +DN_API DN_Str8 DN_Str8Builder_BuildFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_Build(builder, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Slice DN_Str8Builder_BuildFromOSHeap (DN_Str8Builder const *builder, DN_Arena *arena); +DN_API DN_Str8 DN_Str8Builder_BuildFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_Build(builder, DN_OS_TLSTopArena()); } + +DN_API DN_Str8 DN_Str8Builder_BuildDelimitedFromFrame(DN_Str8Builder const *builder, DN_Str8 delimiter) { return DN_Str8Builder_BuildDelimited(builder, delimiter, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Str8 DN_Str8Builder_BuildDelimitedFromTLS (DN_Str8Builder const *builder, DN_Str8 delimiter) { return DN_Str8Builder_BuildDelimited(builder, delimiter, DN_OS_TLSTopArena()); } + +DN_API DN_Slice DN_Str8Builder_BuildSliceFromFrame (DN_Str8Builder const *builder) { return DN_Str8Builder_BuildSlice(builder, DN_OS_TLSGet()->frame_arena); } +DN_API DN_Slice DN_Str8Builder_BuildSliceFromTLS (DN_Str8Builder const *builder) { return DN_Str8Builder_BuildSlice(builder, DN_OS_TLSTopArena()); } + +#endif // !defined(DN_OS_STRING_H) diff --git a/OS/dn_os_tls.cpp b/OS/dn_os_tls.cpp new file mode 100644 index 0000000..510f97d --- /dev/null +++ b/OS/dn_os_tls.cpp @@ -0,0 +1,399 @@ +#define DN_OS_TLSCPP + +// NOTE: DN_OSTLS //////////////////////////////////////////////////////////////////////////////////// +DN_OSTLSTMem::DN_OSTLSTMem(DN_OSTLS *tls, DN_U8 arena_index, DN_OSTLSPushTMem push_tmem) +{ + DN_Assert(arena_index == DN_OSTLSArena_TMem0 || arena_index == DN_OSTLSArena_TMem1); + arena = tls->arenas + arena_index; + temp_mem = DN_Arena_TempMemBegin(arena); + destructed = false; + push_arena = push_tmem; + if (push_arena) + DN_OS_TLSPushArena(arena); +} + +DN_OSTLSTMem::~DN_OSTLSTMem() +{ + DN_Assert(destructed == false); + DN_Arena_TempMemEnd(temp_mem); + destructed = true; + if (push_arena) + DN_OS_TLSPopArena(); +} + +DN_API void DN_OS_TLSInit(DN_OSTLS *tls, DN_OSTLSInitArgs args) +{ + DN_Check(tls); + if (tls->init) + return; + + DN_U64 reserve = args.reserve ? args.reserve : DN_Kilobytes(64); + DN_U64 commit = args.commit ? args.commit : DN_Kilobytes(4); + DN_U64 err_sink_reserve = args.err_sink_reserve ? args.err_sink_reserve : DN_Kilobytes(64); + DN_U64 err_sink_commit = args.err_sink_commit ? args.err_sink_commit : DN_Kilobytes(4); + + // TODO: We shouldn't have the no alloc track flag here but the initial TLS + // init on OS init happens before CORE init. CORE init is the one responsible + // for setting up the alloc tracking data structures. + DN_ForIndexU(index, DN_OSTLSArena_Count) { + DN_Arena *arena = tls->arenas + index; + switch (DN_CAST(DN_OSTLSArena) index) { + default: *arena = DN_Arena_InitFromOSVMem(reserve, commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; + case DN_OSTLSArena_ErrorSink: *arena = DN_Arena_InitFromOSVMem(err_sink_reserve, err_sink_commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break; + case DN_OSTLSArena_Count: DN_InvalidCodePath; break; + } + } + + tls->thread_id = DN_OS_ThreadID(); + tls->err_sink.arena = tls->arenas + DN_OSTLSArena_ErrorSink; + tls->init = true; +} + +DN_API void DN_OS_TLSDeinit(DN_OSTLS *tls) +{ + tls->init = false; + tls->err_sink = {}; + tls->arena_stack_index = {}; + DN_ForIndexU(index, DN_OSTLSArena_Count) { + DN_Arena *arena = tls->arenas + index; + DN_Arena_Deinit(arena); + } +} + +DN_THREAD_LOCAL DN_OSTLS *g_dn_curr_thread_tls; +DN_API void DN_OS_TLSSetCurrentThreadTLS(DN_OSTLS *tls) +{ + g_dn_curr_thread_tls = tls; +} + +DN_API DN_OSTLS *DN_OS_TLSGet() +{ + DN_Assert(g_dn_curr_thread_tls && + "DN must be initialised (via DN_Core_Init) before calling any functions depending on " + "TLS if this is the main thread, OR, the created thread has not called " + "SetCurrentThreadTLS yet so the TLS data structure hasn't been assigned yet"); + return g_dn_curr_thread_tls; +} + +DN_API DN_Arena *DN_OS_TLSArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = tls->arenas + DN_OSTLSArena_Main; + return result; +} + +// TODO: Is there a way to handle conflict arenas without the user needing to +// manually pass it in? +DN_API DN_OSTLSTMem DN_OS_TLSGetTMem(void const *conflict_arena, DN_OSTLSPushTMem push_tmem) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_U8 tls_index = (DN_U8)-1; + for (DN_U8 index = DN_OSTLSArena_TMem0; index <= DN_OSTLSArena_TMem1; index++) { + DN_Arena *arena = tls->arenas + index; + if (!conflict_arena || arena != conflict_arena) { + tls_index = index; + break; + } + } + + DN_Assert(tls_index != (DN_U8)-1); + return DN_OSTLSTMem(tls, tls_index, push_tmem); +} + +DN_API void DN_OS_TLSPushArena(DN_Arena *arena) +{ + DN_Assert(arena); + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Assert(tls->arena_stack_index < DN_ArrayCountU(tls->arena_stack)); + tls->arena_stack[tls->arena_stack_index++] = arena; +} + +DN_API void DN_OS_TLSPopArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Assert(tls->arena_stack_index > 0); + tls->arena_stack_index--; +} + +DN_API DN_Arena *DN_OS_TLSTopArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = nullptr; + if (tls->arena_stack_index) + result = tls->arena_stack[tls->arena_stack_index - 1]; + return result; +} + +DN_API void DN_OS_TLSBeginFrame(DN_Arena *frame_arena) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + tls->frame_arena = frame_arena; +} + +DN_API DN_Arena *DN_OS_TLSFrameArena() +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_Arena *result = tls->frame_arena; + DN_AssertF(result, "Frame arena must be set by calling DN_OS_TLSBeginFrame at the beginning of the frame"); + return result; +} + +// NOTE: DN_OSErrSink //////////////////////////////////////////////////////////////////////////////// +static void DN_OS_ErrSinkCheck_(DN_OSErrSink const *err) +{ + DN_AssertF(err->arena, "Arena should be assigned in TLS init"); + if (err->stack_size == 0) + return; + + DN_OSErrSinkNode const *node = err->stack + (err->stack_size - 1); + DN_Assert(node->mode >= DN_OSErrSinkMode_Nil && node->mode <= DN_OSErrSinkMode_ExitOnError); + DN_Assert(node->msg_sentinel); + + // NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a + // well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the + // walk limit or dereference a null pointer and assert) + size_t WALK_LIMIT = 99'999; + size_t walk = 0; + for (DN_OSErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) { + DN_AssertF(it, "Encountered null pointer which should not happen in a sentinel DLL"); + DN_Assert(walk < WALK_LIMIT); + } +} + +DN_API DN_OSErrSink *DN_OS_ErrSinkBegin_(DN_OSErrSinkMode mode, DN_CallSite call_site) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_OSErrSink *err = &tls->err_sink; + DN_OSErrSink *result = err; + DN_USize arena_pos = DN_Arena_Pos(result->arena); + + if (tls->err_sink.stack_size == DN_ArrayCountU(err->stack)) { + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + DN_USize counter = 0; + DN_ForItSize(it, DN_OSErrSinkNode, err->stack, err->stack_size) { + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(4) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'. + DN_Str8Builder_AppendF(&builder, " [%04zu] %S:%u %S\n", counter++, it.data->call_site.file, it.data->call_site.line, it.data->call_site.function); + DN_MSVC_WARNING_POP + } + + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(6) when a string is required in call to 'DN_LOG_EmitFromType' Actual type: 'struct DN_Str8'. + DN_AssertF(tls->err_sink.stack_size < DN_ArrayCountU(err->stack), + "Error sink has run out of error scopes, potential leak. Scopes were\n%S", DN_Str8Builder_BuildFromTLS(&builder)); + DN_MSVC_WARNING_POP + } + + DN_OSErrSinkNode *node = tls->err_sink.stack + tls->err_sink.stack_size++; + node->arena_pos = arena_pos; + node->mode = mode; + node->call_site = call_site; + DN_DLList_InitArena(node->msg_sentinel, DN_OSErrSinkMsg, result->arena); + + // NOTE: Handle allocation error + if (!DN_Check(node && node->msg_sentinel)) { + DN_Arena_PopTo(result->arena, arena_pos); + node->msg_sentinel = nullptr; + tls->err_sink.stack_size--; + } + + return result; +} + +DN_API bool DN_OS_ErrSinkHasError(DN_OSErrSink *err) +{ + bool result = false; + if (err && err->stack_size) { + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + result = DN_DLList_HasItems(node->msg_sentinel); + } + return result; +} + +DN_API DN_OSErrSinkMsg *DN_OS_ErrSinkEnd(DN_Arena *arena, DN_OSErrSink *err) +{ + DN_OSErrSinkMsg *result = nullptr; + DN_OS_ErrSinkCheck_(err); + if (!err || err->stack_size == 0) + return result; + + DN_AssertF(arena != err->arena, + "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); + // NOTE: Walk the list and allocate it onto the user's arena + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_OSErrSinkMsg *prev = nullptr; + for (DN_OSErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) { + DN_OSErrSinkMsg *entry = DN_Arena_New(arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes); + entry->msg = DN_Str8_Copy(arena, it->msg); + entry->call_site = it->call_site; + entry->error_code = it->error_code; + if (!result) + result = entry; // Assign first entry if we haven't yet + if (prev) + prev->next = entry; // Link the prev message to the current one + prev = entry; // Update prev to latest + } + + // NOTE: Deallocate all the memory for this scope + err->stack_size--; + DN_Arena_PopTo(err->arena, node->arena_pos); + return result; +} + +static void DN_OS_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_OSErrSinkMsg *msg, DN_OSErrSinkMsg *end) +{ + if (msg == end) // NOTE: No error messages to add + return; + + if (msg->next == end) { + DN_OSErrSinkMsg *it = msg; + DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); + DN_Str8Builder_AppendF(builder, + "%.*s:%05I32u:%.*s %.*s", + DN_STR_FMT(file_name), + it->call_site.line, + DN_STR_FMT(it->call_site.function), + DN_STR_FMT(it->msg)); + } else { + // NOTE: More than one message + for (DN_OSErrSinkMsg *it = msg; it != end; it = it->next) { + DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); + DN_Str8Builder_AppendF(builder, + "%s - %.*s:%05I32u:%.*s%s%.*s", + it == msg ? "" : "\n", + DN_STR_FMT(file_name), + it->call_site.line, + DN_STR_FMT(it->call_site.function), + DN_Str8_HasData(it->msg) ? " " : "", + DN_STR_FMT(it->msg)); + } + } +} + +DN_API DN_Str8 DN_OS_ErrSinkEndStr8(DN_Arena *arena, DN_OSErrSink *err) +{ + DN_Str8 result = {}; + DN_OS_ErrSinkCheck_(err); + if (!err || err->stack_size == 0) + return result; + + DN_AssertF(arena != err->arena, + "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); + + // NOTE: Walk the list and allocate it onto the user's arena + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel); + + // NOTE: Deallocate all the memory for this scope + err->stack_size--; + DN_U64 arena_pos = node->arena_pos; + DN_Arena_PopTo(err->arena, arena_pos); + + result = DN_Str8Builder_Build(&builder, arena); + return result; +} + +DN_API void DN_OS_ErrSinkEndAndIgnore(DN_OSErrSink *err) +{ + DN_OS_ErrSinkEnd(nullptr, err); +} + +DN_API bool DN_OS_ErrSinkEndAndLogError_(DN_OSErrSink *err, DN_CallSite call_site, DN_Str8 err_msg) +{ + DN_AssertF(err->stack_size, "Begin must be called before calling end"); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(node->msg_sentinel, "Begin must be called before calling end"); + + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSErrSinkMode mode = node->mode; + DN_OSErrSinkMsg *msg = DN_OS_ErrSinkEnd(tmem.arena, err); + if (!msg) + return false; + + DN_Str8Builder builder = DN_Str8Builder_InitFromTLS(); + if (DN_Str8_HasData(err_msg)) { + DN_Str8Builder_AppendRef(&builder, err_msg); + DN_Str8Builder_AppendRef(&builder, DN_STR8(":")); + } else { + DN_Str8Builder_AppendRef(&builder, DN_STR8("Error(s) encountered:")); + } + + if (msg->next) // NOTE: More than 1 message + DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); + DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, msg, nullptr); + + DN_Str8 log = DN_Str8Builder_BuildFromTLS(&builder); + DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Error), call_site, "%.*s", DN_STR_FMT(log)); + + if (mode == DN_OSErrSinkMode_DebugBreakOnEndAndLog) + DN_DebugBreak; + return true; +} + +DN_API bool DN_OS_ErrSinkEndAndLogErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log); + return result; +} + +DN_API bool DN_OS_ErrSinkEndAndLogErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); + bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log); + va_end(args); + return result; +} + +DN_API void DN_OS_ErrSinkEndAndExitIfErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (DN_OS_ErrSinkEndAndLogErrorFV_(err, call_site, fmt, args)) { + DN_DebugBreak; + DN_OS_Exit(exit_val); + } +} + +DN_API void DN_OS_ErrSinkEndAndExitIfErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_OS_ErrSinkEndAndExitIfErrorFV_(err, call_site, exit_val, fmt, args); + va_end(args); +} + +DN_API void DN_OS_ErrSinkAppendFV_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, va_list args) +{ + if (!err) + return; + DN_Assert(err && err->stack_size); + DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1); + DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function."); + + DN_OSErrSinkMsg *msg = DN_Arena_New(err->arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes); + if (DN_Check(msg)) { + msg->msg = DN_Str8_InitFV(err->arena, fmt, args); + msg->error_code = error_code; + msg->call_site = DN_OS_TLSGet()->call_site; + DN_DLList_Prepend(node->msg_sentinel, msg); + + if (node->mode == DN_OSErrSinkMode_ExitOnError) + DN_OS_ErrSinkEndAndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code); + } +} + +DN_API void DN_OS_ErrSinkAppendF_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, ...) +{ + if (!err) + return; + va_list args; + va_start(args, fmt); + DN_OS_ErrSinkAppendFV_(err, error_code, fmt, args); + va_end(args); +} + diff --git a/OS/dn_os_tls.h b/OS/dn_os_tls.h new file mode 100644 index 0000000..01cba83 --- /dev/null +++ b/OS/dn_os_tls.h @@ -0,0 +1,139 @@ +#if !defined(DN_OS_TLS_H) +#define DN_OS_TLS_H + +// NOTE: DN_OSErrSink ///////////////////////////////////////////////////////////////////////////// +enum DN_OSErrSinkMode +{ + DN_OSErrSinkMode_Nil, // Default behaviour to accumulate errors into the sink + DN_OSErrSinkMode_DebugBreakOnEndAndLog, // Debug break (int3) when error is encountered and the sink is ended by the 'end and log' functions. + DN_OSErrSinkMode_ExitOnError, // When an error is encountered, exit the program with the error code of the error that was caught. +}; + +struct DN_OSErrSinkMsg +{ + DN_I32 error_code; + DN_Str8 msg; + DN_CallSite call_site; + DN_OSErrSinkMsg *next; + DN_OSErrSinkMsg *prev; +}; + +struct DN_OSErrSinkNode +{ + DN_CallSite call_site; // Call site that the node was created + DN_OSErrSinkMode mode; // Controls how the sink behaves when an error is registered onto the sink. + DN_OSErrSinkMsg *msg_sentinel; // List of error messages accumulated for the current scope + DN_U64 arena_pos; // Position to reset the arena when the scope is ended +}; + +struct DN_OSErrSink +{ + DN_Arena * arena; // Dedicated allocator from the thread's local storage + DN_OSErrSinkNode stack[128]; // Each entry contains errors accumulated between a [begin, end] region of the active sink. + DN_USize stack_size; +}; + +enum DN_OSTLSArena +{ + DN_OSTLSArena_Main, // NOTE: Arena for permanent allocations + DN_OSTLSArena_ErrorSink, // NOTE: Arena for logging error information for this thread + + // NOTE: Per-thread scratch arenas (2 to prevent aliasing) + DN_OSTLSArena_TMem0, + DN_OSTLSArena_TMem1, + + DN_OSTLSArena_Count, +}; + +struct DN_OSTLS +{ + DN_B32 init; // Flag to track if TLS has been initialised + DN_U64 thread_id; + DN_CallSite call_site; // Stores call-site information when requested by thread + DN_OSErrSink err_sink; // Error handling state + DN_Arena arenas[DN_OSTLSArena_Count]; // Default arenas that the thread has access to implicitly + DN_Arena * arena_stack[8]; // Active stack of arenas push/popped arenas on into the TLS + DN_USize arena_stack_index; + + DN_Arena * frame_arena; + char name[64]; + DN_U8 name_size; +}; + +// Push the temporary memory arena when retrieved, popped when the arena goes +// out of scope. Pushed arenas are used automatically as the allocator in TLS +// suffixed function. +enum DN_OSTLSPushTMem +{ + DN_OSTLSPushTMem_No, + DN_OSTLSPushTMem_Yes, +}; + +struct DN_OSTLSTMem +{ + DN_OSTLSTMem(DN_OSTLS *context, uint8_t context_index, DN_OSTLSPushTMem push_scratch); + ~DN_OSTLSTMem(); + DN_Arena *arena; + DN_B32 destructed; + DN_OSTLSPushTMem push_arena; + DN_ArenaTempMem temp_mem; +}; + +struct DN_OSTLSInitArgs +{ + DN_U64 reserve; + DN_U64 commit; + DN_U64 err_sink_reserve; + DN_U64 err_sink_commit; +}; + +// NOTE: DN_OSTLS //////////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_TLSInit (DN_OSTLS *tls, DN_OSTLSInitArgs args); +DN_API void DN_OS_TLSDeinit (DN_OSTLS *tls); +DN_API DN_OSTLS * DN_OS_TLSGet (); +DN_API void DN_OS_TLSSetCurrentThreadTLS (DN_OSTLS *tls); +DN_API DN_Arena * DN_OS_TLSArena (); +DN_API DN_OSTLSTMem DN_OS_TLSGetTMem (void const *conflict_arena, DN_OSTLSPushTMem push_tmp_mem); +DN_API void DN_OS_TLSPushArena (DN_Arena *arena); +DN_API void DN_OS_TLSPopArena (); +DN_API DN_Arena * DN_OS_TLSTopArena (); +DN_API void DN_OS_TLSBeginFrame (DN_Arena *frame_arena); +DN_API DN_Arena * DN_OS_TLSFrameArena (); +#define DN_OS_TLSSaveCallSite do { DN_OS_TLSGet()->call_site = DN_CALL_SITE; } while (0) +#define DN_OS_TLSTMem(...) DN_OS_TLSGetTMem(__VA_ARGS__, DN_OSTLSPushTMem_No) +#define DN_OS_TLSPushTMem(...) DN_OS_TLSGetTMem(__VA_ARGS__, DN_OSTLSPushTMem_Yes) + +// NOTE: DN_OS_ErrSink //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSErrSink * DN_OS_ErrSinkBegin_ (DN_OSErrSinkMode mode, DN_CallSite call_site); +#define DN_OS_ErrSinkBegin(mode) DN_OS_ErrSinkBegin_(mode, DN_CALL_SITE) +#define DN_OS_ErrSinkBeginDefault() DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil) +DN_API bool DN_OS_ErrSinkHasError (DN_OSErrSink *err); +DN_API DN_OSErrSinkMsg *DN_OS_ErrSinkEnd (DN_Arena *arena, DN_OSErrSink *err); +DN_API DN_Str8 DN_OS_ErrSinkEndStr8 (DN_Arena *arena, DN_OSErrSink *err); +DN_API void DN_OS_ErrSinkEndAndIgnore (DN_OSErrSink *err); +DN_API bool DN_OS_ErrSinkEndAndLogError_ (DN_OSErrSink *err, DN_CallSite call_site, DN_Str8 msg); +DN_API bool DN_OS_ErrSinkEndAndLogErrorFV_ (DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API bool DN_OS_ErrSinkEndAndLogErrorF_ (DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_ErrSinkEndAndExitIfErrorF_ (DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...); +DN_API void DN_OS_ErrSinkEndAndExitIfErrorFV_ (DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_ErrSinkAppendFV_ (DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, va_list args); +DN_API void DN_OS_ErrSinkAppendF_ (DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, ...); +#define DN_OS_ErrSinkEndAndLogError(err, err_msg) DN_OS_ErrSinkEndAndLogError_(err, DN_CALL_SITE, err_msg) +#define DN_OS_ErrSinkEndAndLogErrorFV(err, fmt, args) DN_OS_ErrSinkEndAndLogErrorFV_(err, DN_CALL_SITE, fmt, args) +#define DN_OS_ErrSinkEndAndLogErrorF(err, fmt, ...) DN_OS_ErrSinkEndAndLogErrorF_(err, DN_CALL_SITE, fmt, ##__VA_ARGS__) +#define DN_OS_ErrSinkEndAndExitIfErrorFV(err, exit_val, fmt, args) DN_OS_ErrSinkEndAndExitIfErrorFV_(err, DN_CALL_SITE, exit_val, fmt, args) +#define DN_OS_ErrSinkEndAndExitIfErrorF(err, exit_val, fmt, ...) DN_OS_ErrSinkEndAndExitIfErrorF_(err, DN_CALL_SITE, exit_val, fmt, ##__VA_ARGS__) + +#define DN_OS_ErrSinkAppendFV(error, error_code, fmt, args) \ + do { \ + DN_OS_TLSSaveCallSite; \ + DN_OS_ErrSinkAppendFV_(error, error_code, fmt, args); \ + } while (0) + +#define DN_OS_ErrSinkAppendF(error, error_code, fmt, ...) \ + do { \ + DN_OS_TLSSaveCallSite; \ + DN_OS_ErrSinkAppendF_(error, error_code, fmt, ##__VA_ARGS__); \ + } while (0) + +#endif // defined(DN_OS_TLS_H) diff --git a/OS/dn_os_win32.cpp b/OS/dn_os_win32.cpp new file mode 100644 index 0000000..453197e --- /dev/null +++ b/OS/dn_os_win32.cpp @@ -0,0 +1,1736 @@ +#define DN_OS_WIN32_CPP + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ +// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ +// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | +// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | +// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ +// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | +// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ +// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| +// +// dn_os_win32.cpp +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: DN_Mem /////////////////////////////////////////////////////////////////////////// +static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) +{ + DN_Assert((protect & ~DN_MemPage_All) == 0); + DN_Assert(protect != 0); + uint32_t result = 0; + + if (protect & DN_MemPage_NoAccess) { + result = PAGE_NOACCESS; + } else if (protect & DN_MemPage_ReadWrite) { + result = PAGE_READWRITE; + } else if (protect & DN_MemPage_Read) { + result = PAGE_READONLY; + } else if (protect & DN_MemPage_Write) { + DN_LOG_WarningF("Windows does not support write-only pages, granting read+write access"); + result = PAGE_READWRITE; + } + + if (protect & DN_MemPage_Guard) + result |= PAGE_GUARD; + + DN_AssertF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write"); + return result; +} + +DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, uint32_t page_flags) +{ + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long flags = MEM_RESERVE; + if (commit == DN_MemCommit_Yes) + flags |= MEM_COMMIT; + + void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); + if (flags & MEM_COMMIT) { + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + } + return result; +} + +DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, uint32_t page_flags) +{ + bool result = false; + if (!ptr || size == 0) + return false; + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) +{ + // NOTE: This is a decommit call, which is explicitly saying to free the + // pages but not the address space, you would use OS_MemRelease to release + // everything. + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. + VirtualFree(ptr, size, MEM_DECOMMIT); + DN_MSVC_WARNING_POP +} + +DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) +{ + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +} + +DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, uint32_t page_flags) +{ + if (!ptr || size == 0) + return 0; + + static DN_Str8 const ALIGNMENT_ERROR_MSG = + DN_STR8("Page protection requires pointers to be page aligned because we can only guard memory at a multiple of the page boundary."); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + + unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); + unsigned long prev_flags = 0; + int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); + + (void)prev_flags; + if (result == 0) + DN_AssertF(result, "VirtualProtect failed"); + return result; +} + +DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) +{ + uint32_t flags = zero_mem == DN_ZeroMem_Yes ? HEAP_ZERO_MEMORY : 0; + DN_Assert(size <= DN_CAST(DWORD)(-1)); + void *result = HeapAlloc(GetProcessHeap(), flags, DN_CAST(DWORD) size); + DN_Assert(g_dn_os_core_); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_total, 1); + DN_Atomic_AddU64(&g_dn_os_core_->mem_allocs_frame, 1); + return result; +} + +DN_API void DN_OS_MemDealloc(void *ptr) +{ + HeapFree(GetProcessHeap(), 0, ptr); +} + +// NOTE: Date ////////////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() +{ + SYSTEMTIME sys_time; + GetLocalTime(&sys_time); + + DN_OSDateTime result = {}; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.year = DN_CAST(int16_t) sys_time.wYear; + return result; +} + +const DN_U64 DN_OS_WIN32_UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks" +const DN_U64 DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds + +DN_API DN_U64 DN_OS_DateUnixTimeNs() +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + // NOTE: Filetime returned is in intervals of 100 nanoeseconds so we + // multiply by 100 to get nanoseconds. + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) * 100; + return result; +} + +static SYSTEMTIME DN_OS_DateToSystemTime_(DN_OSDateTime date) +{ + SYSTEMTIME result = {}; + result.wYear = date.year; + result.wMonth = date.month; + result.wDay = date.day; + result.wHour = date.hour; + result.wMinute = date.minutes; + result.wSecond = date.seconds; + return result; +} + +static DN_U64 DN_OS_SystemTimeToUnixTimeS_(SYSTEMTIME *sys_time) +{ + FILETIME file_time = {}; + SystemTimeToFileTime(sys_time, &file_time); + + LARGE_INTEGER date_time; + date_time.u.LowPart = file_time.dwLowDateTime; + date_time.u.HighPart = file_time.dwHighDateTime; + DN_U64 result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) / DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND; + return result; +} + +DN_API DN_U64 DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date) +{ + SYSTEMTIME local_time = DN_OS_DateToSystemTime_(date); + SYSTEMTIME sys_time = {}; + TzSpecificLocalTimeToSystemTime(nullptr, &local_time, &sys_time); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_U64 DN_OS_DateToUnixTimeS(DN_OSDateTime date) +{ + DN_Assert(DN_OS_DateIsValid(date)); + + SYSTEMTIME sys_time = DN_OS_DateToSystemTime_(date); + DN_U64 result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); + return result; +} + +DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(DN_U64 time) +{ + // NOTE: Windows epoch time starts from Jan 1, 1601 and counts in + // 100-nanoseconds intervals. + // + // See: https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913 + + DN_U64 win32_time = 116'444'736'000'000'000 + (time * 10'000'000); + SYSTEMTIME sys_time = {}; + FILETIME file_time = {}; + file_time.dwLowDateTime = (DWORD)win32_time; + file_time.dwHighDateTime = win32_time >> 32; + FileTimeToSystemTime(&file_time, &sys_time); + + DN_OSDateTime result = {}; + result.year = DN_CAST(uint16_t) sys_time.wYear; + result.month = DN_CAST(uint8_t) sys_time.wMonth; + result.day = DN_CAST(uint8_t) sys_time.wDay; + result.hour = DN_CAST(uint8_t) sys_time.wHour; + result.minutes = DN_CAST(uint8_t) sys_time.wMinute; + result.seconds = DN_CAST(uint8_t) sys_time.wSecond; + return result; +} + +DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) +{ + DN_Assert(g_dn_os_core_); + if (!buffer || size < 0 || !g_dn_os_core_->win32_bcrypt_init_success) + return false; + + if (size == 0) + return true; + + long gen_status = BCryptGenRandom(g_dn_os_core_->win32_bcrypt_rng_handle, DN_CAST(unsigned char *) buffer, size, 0 /*flags*/); + if (gen_status != 0) { + DN_LOG_ErrorF("Failed to generate random bytes: %d", gen_status); + return false; + } + + return true; +} + +DN_API DN_OSDiskSpace DN_OS_DiskSpace(DN_Str8 path) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_OSDiskSpace result = {}; + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + + ULARGE_INTEGER free_bytes_avail_to_caller; + ULARGE_INTEGER total_number_of_bytes; + ULARGE_INTEGER total_number_of_free_bytes; + if (!GetDiskFreeSpaceExW(path16.data, + &free_bytes_avail_to_caller, + &total_number_of_bytes, + &total_number_of_free_bytes)) + return result; + + result.success = true; + result.avail = free_bytes_avail_to_caller.QuadPart; + result.size = total_number_of_bytes.QuadPart; + return result; +} + +DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str16 name16 = DN_Win_Str8ToStr16(tmem.arena, name); + DN_Str16 value16 = DN_Win_Str8ToStr16(tmem.arena, value); + bool result = SetEnvironmentVariableW(name16.data, value16.data) != 0; + return result; +} + +DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) +{ + DN_Str8 result = {}; + if (!arena) + return result; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 exe_dir16 = DN_Win_EXEPathW(tmem.arena); + result = DN_Win_Str16ToStr8(arena, exe_dir16); + return result; +} + +DN_API void DN_OS_SleepMs(DN_UInt milliseconds) +{ + Sleep(milliseconds); +} + +DN_API DN_U64 DN_OS_PerfCounterFrequency() +{ + DN_Assert(g_dn_os_core_ && g_dn_os_core_->win32_qpc_frequency.QuadPart); + DN_U64 result = g_dn_os_core_->win32_qpc_frequency.QuadPart; + return result; +} + +DN_API DN_U64 DN_OS_PerfCounterNow() +{ + LARGE_INTEGER integer = {}; + QueryPerformanceCounter(&integer); + DN_U64 result = integer.QuadPart; + return result; +} + +#if !defined(DN_NO_OS_FILE_API) +static DN_U64 DN_Win_FileTimeToSeconds_(FILETIME const *time) +{ + ULARGE_INTEGER time_large_int = {}; + time_large_int.u.LowPart = time->dwLowDateTime; + time_large_int.u.HighPart = time->dwHighDateTime; + DN_U64 result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; + return result; +} + +DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) +{ + DN_OSPathInfo result = {}; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + return result; + + result.exists = true; + result.create_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftCreationTime); + result.last_access_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftLastAccessTime); + result.last_write_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftLastWriteTime); + + LARGE_INTEGER large_int = {}; + large_int.u.HighPart = DN_CAST(int32_t) attrib_data.nFileSizeHigh; + large_int.u.LowPart = attrib_data.nFileSizeLow; + result.size = (DN_U64)large_int.QuadPart; + + if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + result.type = DN_OSPathInfoType_Directory; + else + result.type = DN_OSPathInfoType_File; + } + + return result; +} + +DN_API bool DN_OS_PathDelete(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + if (path16.size) { + result = DeleteFileW(path16.data); + if (!result) + result = RemoveDirectoryW(path16.data); + } + return result; +} + +DN_API bool DN_OS_FileExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + return result; +} + +DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_Win_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_Win_Str8ToStr16(tmem.arena, dest); + + int fail_if_exists = overwrite == false; + result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; + + if (!result) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to copy file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_OSErrSink *err) +{ + bool result = false; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 src16 = DN_Win_Str8ToStr16(tmem.arena, src); + DN_Str16 dest16 = DN_Win_Str8ToStr16(tmem.arena, dest); + + unsigned long flags = MOVEFILE_COPY_ALLOWED; + if (overwrite) + flags |= MOVEFILE_REPLACE_EXISTING; + + result = MoveFileExW(src16.data, dest16.data, flags) != 0; + if (!result) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, + win_error.code, + "Failed to move file '%.*s' to '%.*s': (%u) %.*s", + DN_STR_FMT(src), + DN_STR_FMT(dest), + win_error.code, + DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_MakeDir(DN_Str8 path) +{ + bool result = true; + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + + // NOTE: Go back from the end of the string to all the directories in the + // string, and try to create them. Since Win32 API cannot create + // intermediate directories that don't exist in a path we need to go back + // and record all the directories until we encounter one that exists. + // + // From that point onwards go forwards and make all the directories + // inbetween by null-terminating the string temporarily, creating the + // directory and so forth until we reach the end. + // + // If we find a file at some point in the path we fail out because the + // series of directories can not be made if a file exists with the same + // name. + for (DN_USize index = 0; index < path16.size; index++) { + bool first_char = index == (path16.size - 1); + wchar_t ch = path16.data[index]; + if (ch == '/' || ch == '\\' || first_char) { + wchar_t temp = path16.data[index]; + if (!first_char) + path16.data[index] = 0; // Temporarily null terminate it + + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check + + if (successful) { + if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // NOTE: The directory exists, continue iterating the path + } else { + // NOTE: There's some kind of file that exists at the path + // but it's not a directory. This request to make a + // directory is invalid. + return false; + } + } else { + // NOTE: There's nothing that exists at this path, we can create + // a directory here + result |= (CreateDirectoryW(path16.data, nullptr) == 0); + } + + if (!first_char) + path16.data[index] = temp; // Undo null termination + } + } + return result; +} + +DN_API bool DN_OS_DirExists(DN_Str8 path) +{ + bool result = false; + if (!DN_Str8_HasData(path)) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + if (path16.size) { + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + + return result; +} + +DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) +{ + if (!DN_Str8_HasData(path) || !it || path.size <= 0) + return false; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Win_FolderIteratorW wide_it = {}; + DN_Str16 path16 = {}; + if (it->handle) { + wide_it.handle = it->handle; + } else { + bool needs_asterisks = DN_Str8_EndsWith(path, DN_STR8("\\")) || + DN_Str8_EndsWith(path, DN_STR8("/")); + bool has_glob = DN_Str8_EndsWith(path, DN_STR8("\\*")) || + DN_Str8_EndsWith(path, DN_STR8("/*")); + + DN_Str8 adjusted_path = path; + if (!has_glob) { + // NOTE: We are missing the glob for enumerating the files, we will + // add those characters in this branch, so overwrite the null + // character, add the glob and re-null terminate the buffer. + if (needs_asterisks) + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s*", DN_STR_FMT(path)); + else + adjusted_path = DN_OS_PathF(tmem.arena, "%.*s/*", DN_STR_FMT(path)); + } + + path16 = DN_Win_Str8ToStr16(tmem.arena, adjusted_path); + if (path16.size <= 0) // Conversion error + return false; + } + + bool result = DN_Win_DirWIterate(path16, &wide_it); + it->handle = wide_it.handle; + if (result) { + int size = DN_Win_Str16ToStr8Buffer(wide_it.file_name, it->buffer, DN_ArrayCountU(it->buffer)); + it->file_name = DN_Str8_Init(it->buffer, size); + } + + return result; +} + +// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, DN_OSFileOpen open_mode, uint32_t access, DN_OSErrSink *err) +{ + DN_OSFile result = {}; + if (!DN_Str8_HasData(path) || path.size <= 0) + return result; + + if ((access & ~DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0)) { + DN_InvalidCodePath; + return result; + } + + unsigned long create_flag = 0; + switch (open_mode) { + case DN_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; + case DN_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; + case DN_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; + default: DN_InvalidCodePath; return result; + } + + unsigned long access_mode = 0; + if (access & DN_OSFileAccess_AppendOnly) { + DN_AssertF((access & ~DN_OSFileAccess_AppendOnly) == 0, + "Append can only be applied exclusively to the file, other access modes not permitted"); + access_mode = FILE_APPEND_DATA; + } else { + if (access & DN_OSFileAccess_Read) + access_mode |= GENERIC_READ; + if (access & DN_OSFileAccess_Write) + access_mode |= GENERIC_WRITE; + if (access & DN_OSFileAccess_Execute) + access_mode |= GENERIC_EXECUTE; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); + void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, + /*DWORD dwDesiredAccess*/ access_mode, + /*DWORD dwShareMode*/ FILE_SHARE_READ | FILE_SHARE_WRITE, + /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, + /*DWORD dwCreationDisposition*/ create_flag, + /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, + /*HANDLE hTemplateFile*/ nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.error = true; + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to open file at '%.*s': '%.*s'", DN_STR_FMT(path), DN_STR_FMT(win_error.msg)); + return result; + } + + result.handle = handle; + return result; +} + +DN_API DN_OSFileRead DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_OSErrSink *err) +{ + DN_OSFileRead result = {}; + if (!file || !file->handle || file->error || !buffer || size <= 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + if (!DN_Check(size <= (unsigned long)-1)) { + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF( + err, + 1 /*error_code*/, + "Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO", + DN_STR_FMT(buffer_size_str8)); + return result; + } + + unsigned long bytes_read = 0; + unsigned long read_result = ReadFile(/*HANDLE hFile*/ file->handle, + /*LPVOID lpBuffer*/ buffer, + /*DWORD nNumberOfBytesToRead*/ DN_CAST(unsigned long) size, + /*LPDWORD lpNumberOfByesRead*/ &bytes_read, + /*LPOVERLAPPED lpOverlapped*/ nullptr); + if (read_result == 0) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DN_STR_FMT(win_error.msg)); + return result; + } + + if (bytes_read != size) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_OS_ErrSinkAppendF( + err, + win_error.code, + "Failed to read the desired number of bytes from file, we read %uB but we expected %uB: (%u) %.*s", + bytes_read, + DN_CAST(unsigned long) size, + win_error.code, + DN_STR_FMT(win_error.msg)); + return result; + } + + result.bytes_read = bytes_read; + result.success = true; + return result; +} + +DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + + bool result = true; + char const *end = DN_CAST(char *) buffer + size; + for (char const *ptr = DN_CAST(char const *) buffer; result && ptr != end;) { + unsigned long write_size = DN_CAST(unsigned long) DN_Min((unsigned long)-1, end - ptr); + unsigned long bytes_written = 0; + result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; + ptr += bytes_written; + } + + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_Str8 buffer_size_str8 = DN_CVT_U64ToByteSizeStr8(tmem.arena, size, DN_CVTU64ByteSizeType_Auto); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(win_error.msg)); + } + return result; +} + +DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_OSErrSink *err) +{ + if (!file || !file->handle || file->error) + return false; + + BOOL result = FlushFileBuffers(DN_CAST(HANDLE) file->handle); + if (!result) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_WinError win_error = DN_Win_LastError(tmem.arena); + DN_OS_ErrSinkAppendF(err, win_error.code, "Failed to flush file buffer to disk: %.*s", DN_STR_FMT(win_error.msg)); + } + + return DN_CAST(bool) result; +} + +DN_API void DN_OS_FileClose(DN_OSFile *file) +{ + if (!file || !file->handle || file->error) + return; + CloseHandle(file->handle); + *file = {}; +} +#endif // !defined(DN_NO_OS_FILE_API) + +// NOTE: DN_OSExec ///////////////////////////////////////////////////////////////////////////////// +DN_API void DN_OS_Exit(int32_t exit_code) +{ + ExitProcess(DN_CAST(UINT) exit_code); +} + +DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, + char *stdout_buffer, + size_t *stdout_size, + char *stderr_buffer, + size_t *stderr_size, + uint32_t timeout_ms, + DN_OSErrSink *err) +{ + DN_OSExecResult result = {}; + size_t stdout_buffer_size = 0; + size_t stderr_buffer_size = 0; + if (stdout_size) { + stdout_buffer_size = *stdout_size; + *stdout_size = 0; + } + + if (stderr_size) { + stderr_buffer_size = *stderr_size; + *stderr_size = 0; + } + + if (!handle.process || handle.os_error_code || handle.exit_code) { + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DWORD stdout_bytes_available = 0; + DWORD stderr_bytes_available = 0; + PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr); + PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr); + + DWORD exec_result = WAIT_TIMEOUT; + if (stdout_bytes_available == 0 && stderr_bytes_available == 0) + exec_result = WaitForSingleObject(handle.process, timeout_ms); + + if (exec_result == WAIT_FAILED) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_STR_FMT(win_error.msg)); + } else if (DN_Check(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { + // NOTE: Read stdout from process ////////////////////////////////////////////////////// + // If the pipes are full, the process will block. We periodically + // flush the pipes to make sure this doesn't happen + char sink[DN_Kilobytes(8)]; + stdout_bytes_available = 0; + if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { + if (stdout_bytes_available) { + DWORD bytes_read = 0; + char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; + size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ArrayCountU(sink); + BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stdout_size) + *stdout_size = bytes_read; + } + } + + // NOTE: Read stderr from process ////////////////////////////////////////////////////// + stderr_bytes_available = 0; + if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { + if (stderr_bytes_available) { + char *dest_buffer = handle.stderr_write && stderr_buffer ? stderr_buffer : sink; + size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ArrayCountU(sink); + DWORD bytes_read = 0; + BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_CAST(DWORD) dest_size, &bytes_read, NULL); + (void)success; // TODO: + if (stderr_size) + *stderr_size = bytes_read; + } + } + } + + result.finished = exec_result == WAIT_OBJECT_0 || exec_result == WAIT_FAILED; + if (exec_result == WAIT_OBJECT_0) { + DWORD exit_status; + if (GetExitCodeProcess(handle.process, &exit_status)) { + result.exit_code = exit_status; + } else { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to retrieve command exit code: %.*s", + DN_STR_FMT(win_error.msg)); + } + + // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// + CloseHandle(handle.stdout_write); + CloseHandle(handle.stderr_write); + CloseHandle(handle.stdout_read); + CloseHandle(handle.stderr_read); + CloseHandle(handle.process); + } + + result.stdout_text = DN_Str8_Init(stdout_buffer, stdout_size ? *stdout_size : 0); + result.stderr_text = DN_Str8_Init(stderr_buffer, stderr_size ? *stderr_size : 0); + return result; +} + +DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_OSErrSink *err) +{ + DN_OSExecResult result = {}; + if (!handle.process || handle.os_error_code || handle.exit_code) { + result.finished = true; + if (handle.os_error_code) + result.os_error_code = handle.os_error_code; + else + result.exit_code = handle.exit_code; + + DN_Assert(!handle.stdout_read); + DN_Assert(!handle.stdout_write); + DN_Assert(!handle.stderr_read); + DN_Assert(!handle.stderr_write); + DN_Assert(!handle.process); + return result; + } + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder stdout_builder = {}; + DN_Str8Builder stderr_builder = {}; + if (arena) { + stdout_builder.arena = tmem.arena; + stderr_builder.arena = tmem.arena; + } + + uint32_t const SLOW_WAIT_TIME_MS = 100; + uint32_t const FAST_WAIT_TIME_MS = 20; + uint32_t wait_ms = FAST_WAIT_TIME_MS; + while (!result.finished) { + size_t stdout_size = DN_Kilobytes(8); + size_t stderr_size = DN_Kilobytes(8); + char *stdout_buffer = DN_Arena_NewArray(tmem.arena, char, stdout_size, DN_ZeroMem_No); + char *stderr_buffer = DN_Arena_NewArray(tmem.arena, char, stderr_size, DN_ZeroMem_No); + result = DN_OS_ExecPump(handle, stdout_buffer, &stdout_size, stderr_buffer, &stderr_size, wait_ms, err); + DN_Str8Builder_AppendCopy(&stdout_builder, result.stdout_text); + DN_Str8Builder_AppendCopy(&stderr_builder, result.stderr_text); + wait_ms = (DN_Str8_HasData(result.stdout_text) || DN_Str8_HasData(result.stderr_text)) ? FAST_WAIT_TIME_MS : SLOW_WAIT_TIME_MS; + } + + // NOTE: Get stdout/stderr. If no arena is passed this is a no-op ////////////////////////////// + result.stdout_text = DN_Str8Builder_Build(&stdout_builder, arena); + result.stderr_text = DN_Str8Builder_Build(&stderr_builder, arena); + return result; +} + +DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExecArgs *args, DN_OSErrSink *err) +{ + // NOTE: Pre-amble ///////////////////////////////////////////////////////////////////////////// + DN_OSExecAsyncHandle result = {}; + if (cmd_line.size == 0) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); + DN_Str16 cmd16 = DN_Win_Str8ToStr16(tmem.arena, cmd_rendered); + DN_Str16 working_dir16 = DN_Win_Str8ToStr16(tmem.arena, args->working_dir); + + DN_Str8Builder env_builder = DN_Str8Builder_InitFromTLS(); + DN_Str8Builder_AppendArrayRef(&env_builder, args->environment.data, args->environment.size); + if (env_builder.string_size) + DN_Str8Builder_AppendRef(&env_builder, DN_STR8("\0")); + + DN_Str8 env_block8 = DN_Str8Builder_BuildDelimitedFromTLS(&env_builder, DN_STR8("\0")); + DN_Str16 env_block16 = {}; + if (env_block8.size) + env_block16 = DN_Win_Str8ToStr16(tmem.arena, env_block8); + + // NOTE: Stdout/err security attributes //////////////////////////////////////////////////////// + SECURITY_ATTRIBUTES save_std_security_attribs = {}; + save_std_security_attribs.nLength = sizeof(save_std_security_attribs); + save_std_security_attribs.bInheritHandle = true; + + // NOTE: Redirect stdout /////////////////////////////////////////////////////////////////////// + HANDLE stdout_read = {}; + HANDLE stdout_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stdout_read); + CloseHandle(stdout_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { + if (!CreatePipe(&stdout_read, &stdout_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stdout 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + + // NOTE: Redirect stderr /////////////////////////////////////////////////////////////////////// + HANDLE stderr_read = {}; + HANDLE stderr_write = {}; + DN_DEFER + { + if (result.os_error_code || result.exit_code) { + CloseHandle(stderr_read); + CloseHandle(stderr_write); + } + }; + + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + stderr_read = stdout_read; + stderr_write = stdout_write; + } else { + if (!CreatePipe(&stderr_read, &stderr_write, &save_std_security_attribs, /*nSize*/ 0)) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF( + err, + result.os_error_code, + "Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + if (!SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to make stderr 'read' pipe non-inheritable when trying to " + "execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + } + } + + // NOTE: Execute command /////////////////////////////////////////////////////////////////////// + PROCESS_INFORMATION proc_info = {}; + STARTUPINFOW startup_info = {}; + startup_info.cb = sizeof(STARTUPINFOW); + startup_info.hStdError = stderr_write ? stderr_write : GetStdHandle(STD_ERROR_HANDLE); + startup_info.hStdOutput = stdout_write ? stdout_write : GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.dwFlags |= STARTF_USESTDHANDLES; + BOOL create_result = CreateProcessW(nullptr, + cmd16.data, + nullptr, + nullptr, + true, + CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, + env_block16.data, + working_dir16.data, + &startup_info, + &proc_info); + if (!create_result) { + DN_WinError win_error = DN_Win_LastError(tmem.arena); + result.os_error_code = win_error.code; + DN_OS_ErrSinkAppendF(err, + result.os_error_code, + "Failed to execute command '%.*s': %.*s", + DN_STR_FMT(cmd_rendered), + DN_STR_FMT(win_error.msg)); + return result; + } + + // NOTE: Post-amble //////////////////////////////////////////////////////////////////////////// + CloseHandle(proc_info.hThread); + result.process = proc_info.hProcess; + result.stdout_read = stdout_read; + result.stdout_write = stdout_write; + if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { + result.stderr_read = stderr_read; + result.stderr_write = stderr_write; + } + result.exec_flags = args->flags; + return result; +} + +#if !defined(DN_NO_SEMAPHORE) +// NOTE: DN_OSSemaphore //////////////////////////////////////////////////////////////////////////// +DN_API DN_OSSemaphore DN_OS_SemaphoreInit(uint32_t initial_count) +{ + DN_OSSemaphore result = {}; + SECURITY_ATTRIBUTES security_attribs = {}; + result.win32_handle = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/); + return result; +} + +DN_API bool DN_OS_SemaphoreIsValid(DN_OSSemaphore *semaphore) +{ + bool result = false; + if (semaphore) + result = semaphore->win32_handle; + return result; +} + +DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) +{ + if (!DN_OS_SemaphoreIsValid(semaphore)) + return; + CloseHandle(semaphore->win32_handle); + *semaphore = {}; +} + +DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount) +{ + if (!DN_OS_SemaphoreIsValid(semaphore)) + return; + LONG prev_count = 0; + ReleaseSemaphore(DN_CAST(HANDLE *) semaphore->win32_handle, amount, &prev_count); +} + +DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, uint32_t timeout_ms) +{ + DN_OSSemaphoreWaitResult result = {}; + if (!DN_OS_SemaphoreIsValid(semaphore)) + return result; + + if (!semaphore->win32_handle) + return result; + + DWORD wait_result = WaitForSingleObject(semaphore->win32_handle, timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms); + if (wait_result == WAIT_TIMEOUT) + result = DN_OSSemaphoreWaitResult_Timeout; + else if (wait_result == WAIT_OBJECT_0) + result = DN_OSSemaphoreWaitResult_Success; + return result; +} +#endif // !defined(DN_NO_SEMAPHORE) + +#if !defined(DN_NO_THREAD) +// NOTE: DN_OSMutex //////////////////////////////////////////////////////////////////////////////// +DN_API DN_OSMutex DN_OS_MutexInit() +{ + DN_OSMutex result = {}; + + CRITICAL_SECTION crit_section = {}; + InitializeCriticalSection(&crit_section); + + static_assert(sizeof(CRITICAL_SECTION) <= sizeof(result.win32_handle), "Insufficient bytes to store Win32 mutex opaquely in our abstracted DN_OSMutex"); + DN_Memcpy(result.win32_handle, &crit_section, sizeof(crit_section)); + return result; +} + +DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *) mutex->win32_handle; + DeleteCriticalSection(crit_section); + DN_Memset(mutex->win32_handle, 0, DN_ArrayCountU(mutex->win32_handle)); +} + +DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *) mutex->win32_handle; + EnterCriticalSection(crit_section); +} + +DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) +{ + if (!mutex) + return; + CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *) mutex->win32_handle; + LeaveCriticalSection(crit_section); +} + +// NOTE: DN_OSThread /////////////////////////////////////////////////////////////////////////////// + +static DWORD __stdcall DN_OS_ThreadFunc_(void *user_context) +{ + DN_OS_ThreadExecute_(user_context); + return 0; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) +{ + bool result = false; + if (!thread) + return result; + + thread->func = func; + thread->user_context = user_context; + thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); + + // TODO(doyle): Check if semaphore is valid + DWORD thread_id = 0; + SECURITY_ATTRIBUTES security_attribs = {}; + thread->handle = CreateThread(&security_attribs, + 0 /*stack_size*/, + DN_OS_ThreadFunc_, + thread, + 0 /*creation_flags*/, + &thread_id); + + result = thread->handle != INVALID_HANDLE_VALUE; + if (result) + thread->thread_id = thread_id; + + // NOTE: Ensure that thread_id is set before 'thread->func' is called. + if (result) { + DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); + } else { + DN_OS_SemaphoreDeinit(&thread->init_semaphore); + *thread = {}; + } + + return result; +} + +DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) +{ + if (!thread || !thread->handle) + return; + + WaitForSingleObject(thread->handle, INFINITE); + CloseHandle(thread->handle); + thread->handle = INVALID_HANDLE_VALUE; + thread->thread_id = {}; + DN_OS_TLSDeinit(&thread->tls); +} + +DN_API uint32_t DN_OS_ThreadID() +{ + unsigned long result = GetCurrentThreadId(); + return result; +} + +DN_API void DN_Win_ThreadSetName(DN_Str8 name) +{ + DN_OSTLS *tls = DN_OS_TLSGet(); + DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(tls->arenas + DN_OSTLSArena_TMem0); + + // NOTE: SetThreadDescription is only available in + // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607 + // + // See: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription + if (g_dn_os_core_->win32_set_thread_description) { + DN_Str16 name16 = DN_Win_Str8ToStr16(tmem.arena, name); + g_dn_os_core_->win32_set_thread_description(GetCurrentThread(), (WCHAR *)name16.data); + DN_Arena_TempMemEnd(tmem); + return; + } + + // NOTE: Fallback to throw-exception method to set thread name + #pragma pack(push, 8) + + struct DN_WinThreadNameInfo + { + uint32_t dwType; + char *szName; + uint32_t dwThreadID; + uint32_t dwFlags; + }; + + #pragma pack(pop) + + DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); + DN_WinThreadNameInfo info = {}; + info.dwType = 0x1000; + info.szName = (char *)copy.data; + info.dwThreadID = DN_OS_ThreadID(); + + // TODO: Review warning 6320 + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6320) // Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. This might mask exceptions that were not intended to be handled + DN_MSVC_WARNING_DISABLE(6322) // Empty _except block + __try { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } __except (EXCEPTION_EXECUTE_HANDLER) { + } + DN_MSVC_WARNING_POP + + DN_Arena_TempMemEnd(tmem); +} +#endif // !defined(DN_NO_THREAD) + +// NOTE: DN_OSHttp ///////////////////////////////////////////////////////////////////////////////// +void DN_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength) +{ + (void)session; + (void)dwStatusInformationLength; + + DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) dwContext; + HINTERNET request = DN_CAST(HINTERNET) response->win32_request_handle; + DN_WinError error = {}; + DWORD const READ_BUFFER_SIZE = DN_Megabytes(1); + + if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) { + DWORD status = 0; + DWORD status_size = sizeof(status_size); + if (WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status, + &status_size, + WINHTTP_NO_HEADER_INDEX)) { + response->http_status = DN_CAST(uint16_t) status; + + // NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel + // will buffer the response into a single buffer and return us the full size of the + // request. + // + // or + // + // You may call WinHttpReadData directly to write the memory into our buffer directly. + // This is advantageous to avoid a copy from the kernel buffer into our buffer. If the + // end user application knows the typical payload size then they can optimise for this + // to prevent unnecessary allocation on the user side. + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_Win_LastError(&response->tmp_arena); + } else { + error = DN_Win_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) { + DWORD bytes_read = dwStatusInformationLength; + if (bytes_read) { + DN_Str8 prev_buffer = DN_Str8_Init(DN_CAST(char *) lpvStatusInformation, bytes_read); + DN_Str8Builder_AppendRef(&response->builder, prev_buffer); + + void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); + if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) + error = DN_Win_LastError(&response->tmp_arena); + } + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) { + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) { + WINHTTP_ASYNC_RESULT *async_result = DN_CAST(WINHTTP_ASYNC_RESULT *) lpvStatusInformation; + error = DN_Win_ErrorCodeToMsg(&response->tmp_arena, DN_CAST(uint32_t) async_result->dwError); + } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) { + if (!WinHttpReceiveResponse(request, 0)) + error = DN_Win_LastError(&response->tmp_arena); + } + + // NOTE: If the request handle is missing, then, the response has been freed. + // MSDN says that this callback can still be called after closing the handle + // and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR. + if (request) { + bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0; + if (read_complete) + response->body = DN_Str8Builder_Build(&response->builder, response->arena); + + if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) { + DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); + DN_Atomic_AddU32(&response->done, 1); + } + + if (error.code) { + response->error_code = error.code; + response->error_msg = error.msg; + } + } +} + +DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, + DN_Arena *arena, + DN_Str8 host, + DN_Str8 path, + DN_OSHttpRequestSecure secure, + DN_Str8 method, + DN_Str8 body, + DN_Str8 headers) +{ + if (!response || !arena) + return; + + response->arena = arena; + response->builder.arena = response->tmem_arena ? response->tmem_arena : &response->tmp_arena; + + DN_Arena *tmem_arena = response->tmem_arena; + DN_OSTLSTMem tmem_ = DN_OS_TLSTMem(arena); + if (!tmem_arena) + tmem_arena = tmem_.arena; + + DN_WinError error = {}; + DN_DEFER + { + response->error_msg = error.msg; + response->error_code = error.code; + if (error.code) { + // NOTE: 'Wait' handles failures gracefully, skipping the wait and + // cleans up the request + DN_OS_HttpRequestWait(response); + DN_Atomic_AddU32(&response->done, 1); + } + }; + + response->win32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); + if (!response->win32_request_session) { + error = DN_Win_LastError(&response->tmp_arena); + return; + } + + DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | + WINHTTP_CALLBACK_STATUS_READ_COMPLETE | + WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | + WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE; + if (WinHttpSetStatusCallback(response->win32_request_session, + DN_CAST(WINHTTP_STATUS_CALLBACK) DN_OS_HttpRequestWin32Callback, + callback_flags, + DN_CAST(DWORD_PTR) nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) { + error = DN_Win_LastError(&response->tmp_arena); + return; + } + + DN_Str16 host16 = DN_Win_Str8ToStr16(tmem_arena, host); + response->win32_request_connection = WinHttpConnect(response->win32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/); + if (!response->win32_request_connection) { + error = DN_Win_LastError(&response->tmp_arena); + return; + } + + DN_Str16 method16 = DN_Win_Str8ToStr16(tmem_arena, method); + DN_Str16 path16 = DN_Win_Str8ToStr16(tmem_arena, path); + response->win32_request_handle = WinHttpOpenRequest(response->win32_request_connection, + method16.data, + path16.data, + nullptr /*version*/, + nullptr /*referrer*/, + nullptr /*accept types*/, + secure ? WINHTTP_FLAG_SECURE : 0); + if (!response->win32_request_handle) { + error = DN_Win_LastError(&response->tmp_arena); + return; + } + + DN_Str16 headers16 = DN_Win_Str8ToStr16(tmem_arena, headers); + response->on_complete_semaphore = DN_OS_SemaphoreInit(0); + if (!WinHttpSendRequest(response->win32_request_handle, + headers16.data, + DN_CAST(DWORD) headers16.size, + body.data /*optional data*/, + DN_CAST(DWORD) body.size /*optional length*/, + DN_CAST(DWORD) body.size /*total content length*/, + DN_CAST(DWORD_PTR) response)) { + error = DN_Win_LastError(&response->tmp_arena); + return; + } +} + +DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) +{ + // NOTE: Cleanup + // NOTE: These calls are synchronous even when the HTTP request is async. + WinHttpCloseHandle(response->win32_request_handle); + WinHttpCloseHandle(response->win32_request_connection); + WinHttpCloseHandle(response->win32_request_session); + + response->win32_request_session = nullptr; + response->win32_request_connection = nullptr; + response->win32_request_handle = nullptr; + DN_Arena_Deinit(&response->tmp_arena); + if (DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) + DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); + + *response = {}; +} + +// NOTE: DN_Win //////////////////////////////////////////////////////////////////////////////////// +DN_API DN_Str16 DN_Win_ErrorCodeToMsg16Alloc(uint32_t error_code) +{ + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + void *module_to_get_errors_from = nullptr; + if (error_code >= 12000 && error_code <= 12175) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + module_to_get_errors_from = GetModuleHandleA("winhttp.dll"); + } + + wchar_t *result16 = nullptr; + DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, + /*LPCVOID lpSource */ module_to_get_errors_from, + /*DWORD dwMessageId */ error_code, + /*DWORD dwLanguageId*/ 0, + /*LPWSTR lpBuffer */ (LPWSTR)&result16, + /*DWORD nSize */ 0, + /*va_list *Arguments */ nullptr); + + DN_Str16 result = {result16, size}; + return result; +} + +DN_API DN_WinError DN_Win_ErrorCodeToMsgAlloc(uint32_t error_code) +{ + DN_WinError result = {}; + result.code = error_code; + DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_Win_Str16ToStr8FromHeap(error16); + if (error16.data) + LocalFree(error16.data); + return result; +} + +DN_API DN_WinError DN_Win_ErrorCodeToMsg(DN_Arena *arena, uint32_t error_code) +{ + DN_WinError result = {}; + result.code = error_code; + if (arena) { + DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc(error_code); + if (error16.size) + result.msg = DN_Win_Str16ToStr8(arena, error16); + if (error16.data) + LocalFree(error16.data); + } + return result; +} + +DN_API DN_WinError DN_Win_LastError(DN_Arena *arena) +{ + DN_WinError result = DN_Win_ErrorCodeToMsg(arena, GetLastError()); + return result; +} + +DN_API DN_WinError DN_Win_LastErrorAlloc() +{ + DN_WinError result = DN_Win_ErrorCodeToMsgAlloc(GetLastError()); + return result; +} + +DN_API void DN_Win_MakeProcessDPIAware() +{ + typedef bool SetProcessDpiAwareProc(void); + typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); + typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); + + // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It + // appears we can make this robust by just loading user32.dll and using + // GetProcAddress on the DPI function. If it's not there, we're on an old + // version of windows, so we can call an older version of the API. + void *lib_handle = LoadLibraryA("user32.dll"); + if (!lib_handle) + return; + + if (auto *set_process_dpi_awareness_context = DN_CAST(SetProcessDpiAwarenessContextProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwarenessContext")) + set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + else if (auto *set_process_dpi_awareness = DN_CAST(SetProcessDpiAwarenessProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAwareness")) + set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); + else if (auto *set_process_dpi_aware = DN_CAST(SetProcessDpiAwareProc *) GetProcAddress(DN_CAST(HMODULE) lib_handle, "SetProcessDpiAware")) + set_process_dpi_aware(); +} + +// NOTE: Windows UTF8 to Str16 ////////////////////////////////////////////// +DN_API DN_Str16 DN_Win_Str8ToStr16(DN_Arena *arena, DN_Str8 src) +{ + DN_Str16 result = {}; + if (!arena || !DN_Str8_HasData(src)) + return result; + + int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (required_size <= 0) + return result; + + wchar_t *buffer = DN_Arena_NewArray(arena, wchar_t, required_size + 1, DN_ZeroMem_No); + if (!buffer) + return result; + + int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, buffer, required_size); + if (DN_Check(chars_written == required_size)) { + result.data = buffer; + result.size = chars_written; + result.data[result.size] = 0; + } + return result; +} + +DN_API int DN_Win_Str8ToStr16Buffer(DN_Str8 src, wchar_t *dest, int dest_size) +{ + int result = 0; + if (!DN_Str8_HasData(src)) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, nullptr /*dest*/, 0 /*dest size*/); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int) src.size, dest, DN_CAST(int) dest_size); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +// NOTE: Windows Str16 To UTF8 ////////////////////////////////////////////////////////////////// +DN_API int DN_Win_Str16ToStr8Buffer(DN_Str16 src, char *dest, int dest_size) +{ + int result = 0; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (result <= 0 || result > dest_size || !dest) + return result; + + result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DN_CAST(int) dest_size, nullptr, nullptr); + dest[DN_Min(result, dest_size - 1)] = 0; + return result; +} + +DN_API DN_Str8 DN_Win_Str16ToStr8(DN_Arena *arena, DN_Str16 src) +{ + DN_Str8 result = {}; + if (!arena || !DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_ArenaTempMemScope temp_mem = DN_ArenaTempMemScope(arena); + DN_Str8 buffer = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + temp_mem.mem = {}; + } + + return result; +} + +DN_API DN_Str8 DN_Win_Str16ToStr8FromHeap(DN_Str16 src) +{ + DN_Str8 result = {}; + if (!DN_Str16_HasData(src)) + return result; + + int src_size = DN_SaturateCastISizeToInt(src.size); + if (src_size <= 0) + return result; + + int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); + if (required_size <= 0) + return result; + + // NOTE: Str8 allocate ensures there's one extra byte for + // null-termination already so no-need to +1 the required size + DN_Str8 buffer = DN_Str8_AllocFromOSHeap(required_size, DN_ZeroMem_No); + if (!DN_Str8_HasData(buffer)) + return result; + + int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int) buffer.size, nullptr, nullptr); + if (DN_Check(chars_written == required_size)) { + result = buffer; + result.data[result.size] = 0; + } else { + DN_OS_MemDealloc(buffer.data); + buffer = {}; + } + + return result; +} + +// NOTE: Windows Executable Directory ////////////////////////////////////////// +DN_API DN_Str16 DN_Win_EXEPathW(DN_Arena *arena) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, module_size + 1, DN_ZeroMem_No); + result.size = module_size; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str16 DN_Win_EXEDirW(DN_Arena *arena) +{ + // TODO(doyle): Implement a DN_Str16_BinarySearchReverse + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 result = {}; + DN_USize module_size = 0; + wchar_t *module_path = nullptr; + do { + module_size += 256; + module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); + if (!module_path) + return result; + module_size = DN_CAST(DN_USize) GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int) module_size); + } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + DN_USize index_of_last_slash = 0; + for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) + index_of_last_slash = module_path[index] == '\\' ? index : 0; + + result.data = DN_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, DN_ZeroMem_No); + result.size = index_of_last_slash; + DN_Memcpy(result.data, module_path, sizeof(wchar_t) * result.size); + result.data[result.size] = 0; + return result; +} + +DN_API DN_Str8 DN_Win_WorkingDir(DN_Arena *arena, DN_Str8 suffix) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str16 suffix16 = DN_Win_Str8ToStr16(tmem.arena, suffix); + DN_Str16 dir16 = DN_Win_WorkingDirW(tmem.arena, suffix16); + DN_Str8 result = DN_Win_Str16ToStr8(arena, dir16); + return result; +} + +DN_API DN_Str16 DN_Win_WorkingDirW(DN_Arena *arena, DN_Str16 suffix) +{ + DN_Assert(suffix.size >= 0); + DN_Str16 result = {}; + + // NOTE: required_size is the size required *including* the null-terminator + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + unsigned long required_size = GetCurrentDirectoryW(0, nullptr); + unsigned long desired_size = required_size + DN_CAST(unsigned long) suffix.size; + + wchar_t *tmem_w_path = DN_Arena_NewArray(tmem.arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!tmem_w_path) + return result; + + unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, tmem_w_path); + if ((bytes_written_wo_null_terminator + 1) != required_size) { + // TODO(dn): Error + return result; + } + + wchar_t *w_path = DN_Arena_NewArray(arena, wchar_t, desired_size, DN_ZeroMem_No); + if (!w_path) + return result; + + if (suffix.size) { + DN_Memcpy(w_path, tmem_w_path, sizeof(*tmem_w_path) * bytes_written_wo_null_terminator); + DN_Memcpy(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); + w_path[desired_size] = 0; + } + + result = DN_Str16{w_path, DN_CAST(DN_USize)(desired_size - 1)}; + return result; +} + +DN_API bool DN_Win_DirWIterate(DN_Str16 path, DN_Win_FolderIteratorW *it) +{ + WIN32_FIND_DATAW find_data = {}; + if (it->handle) { + if (FindNextFileW(it->handle, &find_data) == 0) { + FindClose(it->handle); + return false; + } + } else { + it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ + FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ + &find_data, /*LPVOID lpFindFileData,*/ + FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ + nullptr, /*LPVOID lpSearchFilter,*/ + FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); + + if (it->handle == INVALID_HANDLE_VALUE) + return false; + } + + it->file_name_buf[0] = 0; + it->file_name = DN_Str16{it->file_name_buf, 0}; + + do { + if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) + continue; + + it->file_name.size = DN_CStr16_Size(find_data.cFileName); + DN_Assert(it->file_name.size < (DN_ArrayCountU(it->file_name_buf) - 1)); + DN_Memcpy(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); + it->file_name_buf[it->file_name.size] = 0; + break; + } while (FindNextFileW(it->handle, &find_data) != 0); + + bool result = it->file_name.size > 0; + if (!result) + FindClose(it->handle); + return result; +} diff --git a/dqn_os_win32.h b/OS/dn_os_win32.h similarity index 55% rename from dqn_os_win32.h rename to OS/dn_os_win32.h index 5e60e18..36e21fe 100644 --- a/dqn_os_win32.h +++ b/OS/dn_os_win32.h @@ -1,40 +1,23 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ -// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | -// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | -// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ -// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | -// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ -// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| -// -// dqn_os_win32.h -- Windows only functions, and, implementation of the OS layer -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ +#if !defined(DN_OS_WIN32_H) +#define DN_OS_WIN32_H struct DN_WinError { - unsigned long code; - DN_Str8 msg; + unsigned long code; + DN_Str8 msg; }; // NOTE: Windows Str8 <-> Str16 /////////////////////////////////////////// struct DN_Win_FolderIteratorW { - void *handle; - DN_Str16 file_name; - wchar_t file_name_buf[512]; + void *handle; + DN_Str16 file_name; + wchar_t file_name_buf[512]; }; DN_API void DN_Win_ThreadSetName (DN_Str8 name); -// NOTE: [$WIND] DN_Win /////////////////////////////////////////////////////////////////////////// +// NOTE: DN_Win //////////////////////////////////////////////////////////////////////////////////// DN_API DN_Str16 DN_Win_ErrorCodeToMsg16Alloc(uint32_t error_code); DN_API DN_WinError DN_Win_ErrorCodeToMsg (DN_Arena *arena, uint32_t error_code); DN_API DN_WinError DN_Win_ErrorCodeToMsgAlloc (uint32_t error_code); @@ -47,7 +30,7 @@ DN_API DN_Str16 DN_Win_Str8ToStr16 (DN_Arena *arena, DN_Str8 src); DN_API int DN_Win_Str8ToStr16Buffer (DN_Str16 src, char *dest, int dest_size); DN_API DN_Str8 DN_Win_Str16ToStr8 (DN_Arena *arena, DN_Str16 src); DN_API int DN_Win_Str16ToStr8Buffer (DN_Str16 src, char *dest, int dest_size); -DN_API DN_Str8 DN_Win_Str16ToStr8Alloc (DN_Str16 src); +DN_API DN_Str8 DN_Win_Str16ToStr8FromHeap(DN_Str16 src); // NOTE: Path navigation /////////////////////////////////////////////////////////////////////////// DN_API DN_Str16 DN_Win_EXEPathW (DN_Arena *arena); @@ -55,3 +38,4 @@ DN_API DN_Str16 DN_Win_EXEDirW (DN_Arena *arena); DN_API DN_Str8 DN_Win_WorkingDir (DN_Arena *arena, DN_Str8 suffix); DN_API DN_Str16 DN_Win_WorkingDirW (DN_Arena *arena, DN_Str16 suffix); DN_API bool DN_Win_DirWIterate (DN_Str16 path, DN_Win_FolderIteratorW *it); +#endif // !defined(DN_OS_WIN32) diff --git a/OS/dn_os_windows.h b/OS/dn_os_windows.h new file mode 100644 index 0000000..c73d7bc --- /dev/null +++ b/OS/dn_os_windows.h @@ -0,0 +1,1325 @@ +#if !defined(DN_OS_WINDOWS_H) +#define DN_OS_WINDOWS_H + +#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) + #pragma comment(lib, "bcrypt") + #pragma comment(lib, "winhttp") + #pragma comment(lib, "dbghelp") + #pragma comment(lib, "comdlg32") + #pragma comment(lib, "pathcch") + #pragma comment(lib, "Shell32") // ShellExecuteW + #pragma comment(lib, "shlwapi") +#endif + +#if defined(DN_NO_WINDOWS_H_REPLACEMENT_HEADER) || defined(_INC_WINDOWS) + #define WIN32_LEAN_AND_MEAN + #include // LONG + #include // DN_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc + #include // DN_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc + #include // PathRelativePathTO + #include // PathCchCanonicalizeEx + #include // WinHttp* + #include // PROCESS_MEMORY_COUNTERS_EX2 + #include // OPENFILENAMEW + #include +#else + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union + + // NOTE: basetsd.h ///////////////////////////////////////////////////////////////////////////// + typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; + typedef unsigned __int64 UINT_PTR, *PUINT_PTR; + typedef ULONG_PTR SIZE_T, *PSIZE_T; + typedef __int64 LONG_PTR, *PLONG_PTR; + typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; + typedef unsigned __int64 ULONG64, *PULONG64; + typedef unsigned __int64 DWORD64, *PDWORD64; + + // NOTE: shared/minwindef.h //////////////////////////////////////////////////////////////////// + struct HINSTANCE__ { + int unused; + }; + typedef struct HINSTANCE__ *HINSTANCE; + + typedef unsigned long DWORD; + typedef int BOOL; + typedef int INT; + typedef unsigned long ULONG; + typedef unsigned int UINT; + typedef unsigned short WORD; + typedef unsigned char BYTE; + typedef unsigned char UCHAR; + typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ + typedef void * HANDLE; + typedef HANDLE HLOCAL; + + typedef unsigned __int64 WPARAM; + typedef LONG_PTR LPARAM; + typedef LONG_PTR LRESULT; + + #define MAX_PATH 260 + + typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; + } FILETIME, *PFILETIME, *LPFILETIME; + + // NOTE: shared/winerror.h ///////////////////////////////////////////////////////////////////// + // NOTE: GetModuleFileNameW + #define ERROR_INSUFFICIENT_BUFFER 122L // dderror + + // NOTE: um/winnls.h /////////////////////////////////////////////////////////////////////////// + // NOTE: MultiByteToWideChar + #define CP_UTF8 65001 // UTF-8 translation + + // NOTE: um/winnt.h //////////////////////////////////////////////////////////////////////////// + typedef void VOID; + typedef __int64 LONGLONG; + typedef unsigned __int64 ULONGLONG; + typedef void * HANDLE; + typedef char CHAR; + typedef short SHORT; + typedef long LONG; + typedef wchar_t WCHAR; // wc, 16-bit UNICODE character + typedef CHAR * NPSTR, *LPSTR, *PSTR; + typedef WCHAR * NWPSTR, *LPWSTR, *PWSTR; + typedef long HRESULT; + + // NOTE: VirtualAlloc: Allocation Type + #define MEM_RESERVE 0x00002000 + #define MEM_COMMIT 0x00001000 + #define MEM_DECOMMIT 0x00004000 + #define MEM_RELEASE 0x00008000 + + // NOTE: VirtualAlloc: Page Permissions + #define PAGE_NOACCESS 0x01 + #define PAGE_READONLY 0x02 + #define PAGE_READWRITE 0x04 + #define PAGE_GUARD 0x100 + + // NOTE: HeapAlloc + #define HEAP_ZERO_MEMORY 0x00000008 + #define HEAP_NO_SERIALIZE 0x00000001 + #define HEAP_GROWABLE 0x00000002 + #define HEAP_GENERATE_EXCEPTIONS 0x00000004 + #define HEAP_ZERO_MEMORY 0x00000008 + #define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010 + #define HEAP_TAIL_CHECKING_ENABLED 0x00000020 + #define HEAP_FREE_CHECKING_ENABLED 0x00000040 + #define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080 + #define HEAP_CREATE_ALIGN_16 0x00010000 + #define HEAP_CREATE_ENABLE_TRACING 0x00020000 + #define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 + #define HEAP_MAXIMUM_TAG 0x0FFF + #define HEAP_PSEUDO_TAG_FLAG 0x8000 + #define HEAP_TAG_SHIFT 18 + #define HEAP_CREATE_SEGMENT_HEAP 0x00000100 + #define HEAP_CREATE_HARDENED 0x00000200 + + // NOTE: FormatMessageA + #define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) + #define LANG_NEUTRAL 0x00 + #define SUBLANG_DEFAULT 0x01 // user default + + // NOTE: CreateFile + #define GENERIC_READ (0x80000000L) + #define GENERIC_WRITE (0x40000000L) + #define GENERIC_EXECUTE (0x20000000L) + #define GENERIC_ALL (0x10000000L) + + #define FILE_APPEND_DATA (0x0004) // file + + // NOTE: CreateFile/FindFirstFile + #define FILE_SHARE_READ 0x00000001 + #define FILE_SHARE_WRITE 0x00000002 + #define FILE_SHARE_DELETE 0x00000004 + + #define FILE_ATTRIBUTE_READONLY 0x00000001 + #define FILE_ATTRIBUTE_HIDDEN 0x00000002 + #define FILE_ATTRIBUTE_SYSTEM 0x00000004 + #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 + #define FILE_ATTRIBUTE_NORMAL 0x00000080 + + // NOTE: STACKFRAME64 + #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) + + // NOTE: WaitForSingleObject + #define WAIT_TIMEOUT 258L // dderror + #define STATUS_WAIT_0 ((DWORD )0x00000000L) + #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) + + #define S_OK ((HRESULT)0L) + #define S_FALSE ((HRESULT)1L) + + typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + } DUMMYSTRUCTNAME; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; + } ULARGE_INTEGER; + + typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + } DUMMYSTRUCTNAME; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; + } LARGE_INTEGER; + + typedef struct __declspec(align(16)) _M128A { + ULONGLONG Low; + LONGLONG High; + } M128A, *PM128A; + + typedef struct __declspec(align(16)) _XSAVE_FORMAT { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + M128A FloatRegisters[8]; + #if defined(_WIN64) + M128A XmmRegisters[16]; + BYTE Reserved4[96]; + #else + M128A XmmRegisters[8]; + BYTE Reserved4[224]; + #endif + } XSAVE_FORMAT, *PXSAVE_FORMAT; + typedef XSAVE_FORMAT XMM_SAVE_AREA32, *PXMM_SAVE_AREA32; + + typedef struct __declspec(align(16)) _CONTEXT { + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + DWORD ContextFlags; + DWORD MxCsr; + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + DWORD64 Rip; + + union { + XMM_SAVE_AREA32 FltSave; + + struct { + M128A Header[2]; + M128A Legacy[8]; + M128A Xmm0; + M128A Xmm1; + M128A Xmm2; + M128A Xmm3; + M128A Xmm4; + M128A Xmm5; + M128A Xmm6; + M128A Xmm7; + M128A Xmm8; + M128A Xmm9; + M128A Xmm10; + M128A Xmm11; + M128A Xmm12; + M128A Xmm13; + M128A Xmm14; + M128A Xmm15; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + + M128A VectorRegister[26]; + DWORD64 VectorControl; + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; + } CONTEXT; + + typedef struct _LIST_ENTRY { + struct _LIST_ENTRY *Flink; + struct _LIST_ENTRY *Blink; + } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; + + typedef struct _RTL_CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + struct _RTL_CRITICAL_SECTION *CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD Flags; + WORD CreatorBackTraceIndexHigh; + WORD Identifier; + } RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG; + + #pragma pack(push, 8) + typedef struct _RTL_CRITICAL_SECTION { + PRTL_CRITICAL_SECTION_DEBUG DebugInfo; + + // + // The following three fields control entering and exiting the critical + // section for the resource + // + + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; // from the thread's ClientId->UniqueThread + HANDLE LockSemaphore; + ULONG_PTR SpinCount; // force size on 64-bit systems when packed + } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; + #pragma pack(pop) + + typedef struct _MODLOAD_DATA { + DWORD ssize; // size of this struct + DWORD ssig; // signature identifying the passed data + VOID *data; // pointer to passed data + DWORD size; // size of passed data + DWORD flags; // options + } MODLOAD_DATA, *PMODLOAD_DATA; + + #define SLMFLAG_VIRTUAL 0x1 + #define SLMFLAG_ALT_INDEX 0x2 + #define SLMFLAG_NO_SYMBOLS 0x4 + + extern "C" + { + __declspec(dllimport) VOID __stdcall RtlCaptureContext(CONTEXT *ContextRecord); + __declspec(dllimport) HANDLE __stdcall GetCurrentProcess(void); + __declspec(dllimport) HANDLE __stdcall GetCurrentThread(void); + __declspec(dllimport) DWORD __stdcall SymSetOptions(DWORD SymOptions); + __declspec(dllimport) BOOL __stdcall SymInitialize(HANDLE hProcess, const CHAR* UserSearchPath, BOOL fInvadeProcess); + __declspec(dllimport) DWORD64 __stdcall SymLoadModuleEx(HANDLE hProcess, HANDLE hFile, CHAR const *ImageName, CHAR const *ModuleName, DWORD64 BaseOfDll, DWORD DllSize, MODLOAD_DATA *Data, DWORD Flags); + __declspec(dllimport) BOOL __stdcall SymUnloadModule64(HANDLE hProcess, DWORD64 BaseOfDll); + } + + // NOTE: um/heapapi.h //////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HANDLE __stdcall HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); + __declspec(dllimport) BOOL __stdcall HeapDestroy(HANDLE hHeap); + __declspec(dllimport) VOID * __stdcall HeapAlloc(HANDLE hHeap, DWORD dwFlags,SIZE_T dwBytes); + __declspec(dllimport) VOID * __stdcall HeapReAlloc(HANDLE hHeap, DWORD dwFlags, VOID *lpMem, SIZE_T dwBytes); + __declspec(dllimport) BOOL __stdcall HeapFree(HANDLE hHeap, DWORD dwFlags, VOID *lpMem); + __declspec(dllimport) SIZE_T __stdcall HeapSize(HANDLE hHeap, DWORD dwFlags, VOID const *lpMem); + __declspec(dllimport) HANDLE __stdcall GetProcessHeap(VOID); + __declspec(dllimport) SIZE_T __stdcall HeapCompact(HANDLE hHeap, DWORD dwFlags); + } + + // NOTE: shared/windef.h //////////////////////////////////////////////////////////////////// + typedef struct tagPOINT + { + LONG x; + LONG y; + } POINT, *PPOINT, *NPPOINT, *LPPOINT; + + // NOTE: handleapi.h /////////////////////////////////////////////////////////////////////////// + #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) + + extern "C" + { + __declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE hObject); + } + + // NOTE: consoleapi.h /////////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall WriteConsoleA(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved); + __declspec(dllimport) BOOL __stdcall AllocConsole(VOID); + __declspec(dllimport) BOOL __stdcall FreeConsole(VOID); + __declspec(dllimport) BOOL __stdcall AttachConsole(DWORD dwProcessId); + __declspec(dllimport) BOOL __stdcall GetConsoleMode(HANDLE hConsoleHandle, DWORD *lpMode); + } + + // NOTE: um/minwinbase.h /////////////////////////////////////////////////////////////////////// + // NOTE: FindFirstFile + #define FIND_FIRST_EX_CASE_SENSITIVE 0x00000001 + #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 + + // NOTE: WaitFor.. + #define WAIT_FAILED ((DWORD)0xFFFFFFFF) + #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + #define WAIT_ABANDONED_0 ((STATUS_ABANDONED_WAIT_0 ) + 0 ) + + // NOTE: CreateProcessW + #define CREATE_UNICODE_ENVIRONMENT 0x00000400 + #define CREATE_NO_WINDOW 0x08000000 + + typedef enum _GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel + } GET_FILEEX_INFO_LEVELS; + + typedef struct _SECURITY_ATTRIBUTES { + DWORD nLength; + VOID *lpSecurityDescriptor; + BOOL bInheritHandle; + } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; + + typedef enum _FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel + } FINDEX_INFO_LEVELS; + + typedef enum _FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp + } FINDEX_SEARCH_OPS; + + typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + WCHAR cFileName[ MAX_PATH ]; + WCHAR cAlternateFileName[ 14 ]; + #ifdef _MAC + DWORD dwFileType; + DWORD dwCreatorType; + WORD wFinderFlags; + #endif + } WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW; + + typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; + } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; + + typedef struct _OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + } DUMMYSTRUCTNAME; + VOID *Pointer; + } DUMMYUNIONNAME; + + HANDLE hEvent; + } OVERLAPPED, *LPOVERLAPPED; + + typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; + + #define WAIT_FAILED ((DWORD)0xFFFFFFFF) + #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) + + #define INFINITE 0xFFFFFFFF // Wait/Synchronisation: Infinite timeout + + #define STD_INPUT_HANDLE ((DWORD)-10) + #define STD_OUTPUT_HANDLE ((DWORD)-11) + #define STD_ERROR_HANDLE ((DWORD)-12) + + #define HANDLE_FLAG_INHERIT 0x00000001 + #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 + + // NOTE: MoveFile + #define MOVEFILE_REPLACE_EXISTING 0x00000001 + #define MOVEFILE_COPY_ALLOWED 0x00000002 + + // NOTE: FormatMessageA + #define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 + #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 + #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 + #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 + + // NOTE: CreateProcessW + #define STARTF_USESTDHANDLES 0x00000100 + + extern "C" + { + __declspec(dllimport) BOOL __stdcall MoveFileExW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall CopyFileW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, BOOL bFailIfExists); + __declspec(dllimport) HANDLE __stdcall CreateSemaphoreA(SECURITY_ATTRIBUTES *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, const CHAR *lpName); + __declspec(dllimport) DWORD __stdcall FormatMessageW (DWORD dwFlags, VOID const *lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments); + __declspec(dllimport) HLOCAL __stdcall LocalFree (HLOCAL hMem); + } + + // NOTE: um/stringapiset.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, const CHAR *lpMultiByteStr, int cbMultiByte, WCHAR *lpWideCharStr, int cchWideChar); + __declspec(dllimport) int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, const WCHAR *lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, const CHAR *lpDefaultChar, BOOL *lpUsedDefaultChar); + } + + // NOTE: um/fileapi.h ////////////////////////////////////////////////////////////////////////// + #define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF) + #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) + + // NOTE: CreateFile + #define CREATE_NEW 1 + #define CREATE_ALWAYS 2 + #define OPEN_EXISTING 3 + #define OPEN_ALWAYS 4 + #define TRUNCATE_EXISTING 5 + + typedef struct _WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall FlushFileBuffers (HANDLE hFile); + __declspec(dllimport) BOOL __stdcall CreateDirectoryW (const WCHAR *lpPathName, SECURITY_ATTRIBUTES *lpSecurityAttributes); + __declspec(dllimport) BOOL __stdcall RemoveDirectoryW (const WCHAR *lpPathName); + __declspec(dllimport) BOOL __stdcall FindNextFileW (HANDLE hFindFile, WIN32_FIND_DATAW *lpFindFileData); + __declspec(dllimport) BOOL __stdcall FindClose (HANDLE hFindFile); + + __declspec(dllimport) HANDLE __stdcall FindFirstFileExW (const WCHAR *lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, VOID *lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, VOID *lpSearchFilter, DWORD dwAdditionalFlags); + __declspec(dllimport) BOOL __stdcall GetFileAttributesExW(const WCHAR *lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, VOID *lpFileInformation); + __declspec(dllimport) BOOL __stdcall GetFileSizeEx (HANDLE hFile, LARGE_INTEGER *lpFileSize); + __declspec(dllimport) BOOL __stdcall DeleteFileW (const WCHAR *lpFileName); + __declspec(dllimport) HANDLE __stdcall CreateFileW (const WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + __declspec(dllimport) BOOL __stdcall ReadFile (HANDLE hFile, VOID *lpBuffer, DWORD nNumberOfBytesToRead, DWORD *lpNumberOfBytesRead, OVERLAPPED *lpOverlapped); + __declspec(dllimport) BOOL __stdcall WriteFile (HANDLE hFile, const VOID *lpBuffer, DWORD nNumberOfBytesToWrite, DWORD *lpNumberOfBytesWritten, OVERLAPPED *lpOverlapped); + __declspec(dllimport) BOOL __stdcall GetDiskFreeSpaceExW (WCHAR const *lpDirectoryName, ULARGE_INTEGER *lpFreeBytesAvailableToCaller, ULARGE_INTEGER *lpTotalNumberOfBytes, ULARGE_INTEGER *lpTotalNumberOfFreeBytes); + } + + // NOTE: um/processenv.h /////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) DWORD __stdcall GetCurrentDirectoryW (DWORD nBufferLength, WCHAR *lpBuffer); + __declspec(dllimport) HANDLE __stdcall GetStdHandle (DWORD nStdHandle); + __declspec(dllimport) WCHAR* __stdcall GetEnvironmentStringsW (); + __declspec(dllimport) BOOL __stdcall FreeEnvironmentStringsW(WCHAR *penv); + __declspec(dllimport) DWORD __stdcall GetEnvironmentVariableW(WCHAR const *lpName, WCHAR *lpBuffer, DWORD nSize); + __declspec(dllimport) BOOL __stdcall SetEnvironmentVariableW(WCHAR const *lpName, WCHAR const *lpValue); + } + + // NOTE: um/psapi.h //////////////////////////////////////////////////////////////////////////// + typedef struct _PROCESS_MEMORY_COUNTERS { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + } PROCESS_MEMORY_COUNTERS; + typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetProcessMemoryInfo(HANDLE Process, PPROCESS_MEMORY_COUNTERS ppsmemCounters, DWORD cb); + } + + // NOTE: um/sysinfoapi.h /////////////////////////////////////////////////////////////////////// + typedef struct _SYSTEM_INFO { + union { + DWORD dwOemId; // Obsolete field...do not use + struct { + WORD wProcessorArchitecture; + WORD wReserved; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + DWORD dwPageSize; + VOID *lpMinimumApplicationAddress; + VOID *lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; + } SYSTEM_INFO, *LPSYSTEM_INFO; + + extern "C" + { + __declspec(dllimport) VOID __stdcall GetSystemInfo(SYSTEM_INFO *lpSystemInfo); + __declspec(dllimport) VOID __stdcall GetSystemTime(SYSTEMTIME *lpSystemTime); + __declspec(dllimport) VOID __stdcall GetSystemTimeAsFileTime(FILETIME *lpSystemTimeAsFileTime); + __declspec(dllimport) VOID __stdcall GetLocalTime(SYSTEMTIME *lpSystemTime); + } + + // NOTE: um/timezoneapi.h ////////////////////////////////////////////////////////////////////// + typedef struct _TIME_ZONE_INFORMATION { + LONG Bias; + WCHAR StandardName[32]; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR DaylightName[32]; + SYSTEMTIME DaylightDate; + LONG DaylightBias; + } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall FileTimeToSystemTime (const FILETIME* lpFileTime, SYSTEMTIME *lpSystemTime); + __declspec(dllimport) BOOL __stdcall SystemTimeToFileTime (const SYSTEMTIME* lpSystemTime, FILETIME *lpFileTime); + __declspec(dllimport) BOOL __stdcall TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION* lpTimeZoneInformation, const SYSTEMTIME* lpLocalTime, const LPSYSTEMTIME lpUniversalTime); + } + + // NOTE: shared/windef.h /////////////////////////////////////////////////////////////////////// + typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; + } RECT; + + struct HWND__ { + int unused; + }; + typedef struct HWND__ *HWND; + + struct DPI_AWARENESS_CONTEXT__ { + int unused; + }; + typedef struct DPI_AWARENESS_CONTEXT__ *DPI_AWARENESS_CONTEXT; + + typedef enum DPI_AWARENESS { + DPI_AWARENESS_INVALID = -1, + DPI_AWARENESS_UNAWARE = 0, + DPI_AWARENESS_SYSTEM_AWARE = 1, + DPI_AWARENESS_PER_MONITOR_AWARE = 2 + } DPI_AWARENESS; + + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) + + // NOTE: um/winuser.h ////////////////////////////////////////////////////////////////////////// + typedef struct tagWINDOWPLACEMENT { + UINT length; + UINT flags; + UINT showCmd; + POINT ptMinPosition; + POINT ptMaxPosition; + RECT rcNormalPosition; + #ifdef _MAC + RECT rcDevice; + #endif + } WINDOWPLACEMENT; + typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT; + + #define SW_HIDE 0 + #define SW_NORMAL 1 + #define SW_MAXIMIZE 3 + #define SW_SHOWNOACTIVATE 4 + #define SW_SHOW 5 + #define SW_FORCEMINIMIZE 11 + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetWindowRect (HWND hWnd, RECT *lpRect); + __declspec(dllimport) BOOL __stdcall SetWindowPos (HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); + __declspec(dllimport) UINT __stdcall GetWindowModuleFileNameA(HWND hwnd, LPSTR pszFileName, UINT cchFileNameMax); + __declspec(dllimport) BOOL __stdcall ShowWindow (HWND hWnd, int nCmdShow); + __declspec(dllimport) BOOL __stdcall GetWindowPlacement (HWND hWnd, WINDOWPLACEMENT *lpwndpl); + + } + + // NOTE: um/wininet.h ////////////////////////////////////////////////////////////////////////// + typedef WORD INTERNET_PORT; + typedef VOID *HINTERNET; + + // NOTE: um/winhttp.h ////////////////////////////////////////////////////////////////////////// + #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 + #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 + #define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 + #define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 + + #define INTERNET_DEFAULT_PORT 0 // use the protocol-specific default + #define INTERNET_DEFAULT_HTTP_PORT 80 // " " HTTP " + #define INTERNET_DEFAULT_HTTPS_PORT 443 // " " HTTPS " + + // NOTE: WinHttpOpen + #define WINHTTP_FLAG_ASYNC 0x10000000 // this session is asynchronous (where supported) + #define WINHTTP_FLAG_SECURE_DEFAULTS 0x30000000 // note that this flag also forces async + + // NOTE: WinHttpOpenRequest + #define WINHTTP_FLAG_SECURE 0x00800000 // use SSL if applicable (HTTPS) + #define WINHTTP_FLAG_ESCAPE_PERCENT 0x00000004 // if escaping enabled, escape percent as well + #define WINHTTP_FLAG_NULL_CODEPAGE 0x00000008 // assume all symbols are ASCII, use fast convertion + #define WINHTTP_FLAG_ESCAPE_DISABLE 0x00000040 // disable escaping + #define WINHTTP_FLAG_ESCAPE_DISABLE_QUERY 0x00000080 // if escaping enabled escape path part, but do not escape query + #define WINHTTP_FLAG_BYPASS_PROXY_CACHE 0x00000100 // add "pragma: no-cache" request header + #define WINHTTP_FLAG_REFRESH WINHTTP_FLAG_BYPASS_PROXY_CACHE + #define WINHTTP_FLAG_AUTOMATIC_CHUNKING 0x00000200 // Send request without content-length header or chunked TE + + #define WINHTTP_NO_PROXY_NAME NULL + #define WINHTTP_NO_PROXY_BYPASS NULL + + // + // WINHTTP_QUERY_FLAG_NUMBER - if this bit is set in the dwInfoLevel parameter of + // HttpQueryHeader(), then the value of the header will be converted to a number + // before being returned to the caller, if applicable + // + #define WINHTTP_QUERY_FLAG_NUMBER 0x20000000 + + #define WINHTTP_QUERY_MIME_VERSION 0 + #define WINHTTP_QUERY_CONTENT_TYPE 1 + #define WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING 2 + #define WINHTTP_QUERY_CONTENT_ID 3 + #define WINHTTP_QUERY_CONTENT_DESCRIPTION 4 + #define WINHTTP_QUERY_CONTENT_LENGTH 5 + #define WINHTTP_QUERY_CONTENT_LANGUAGE 6 + #define WINHTTP_QUERY_ALLOW 7 + #define WINHTTP_QUERY_PUBLIC 8 + #define WINHTTP_QUERY_DATE 9 + #define WINHTTP_QUERY_EXPIRES 10 + #define WINHTTP_QUERY_LAST_MODIFIED 11 + #define WINHTTP_QUERY_MESSAGE_ID 12 + #define WINHTTP_QUERY_URI 13 + #define WINHTTP_QUERY_DERIVED_FROM 14 + #define WINHTTP_QUERY_COST 15 + #define WINHTTP_QUERY_LINK 16 + #define WINHTTP_QUERY_PRAGMA 17 + #define WINHTTP_QUERY_VERSION 18 // special: part of status line + #define WINHTTP_QUERY_STATUS_CODE 19 // special: part of status line + #define WINHTTP_QUERY_STATUS_TEXT 20 // special: part of status line + #define WINHTTP_QUERY_RAW_HEADERS 21 // special: all headers as ASCIIZ + #define WINHTTP_QUERY_RAW_HEADERS_CRLF 22 // special: all headers + #define WINHTTP_QUERY_CONNECTION 23 + #define WINHTTP_QUERY_ACCEPT 24 + #define WINHTTP_QUERY_ACCEPT_CHARSET 25 + #define WINHTTP_QUERY_ACCEPT_ENCODING 26 + #define WINHTTP_QUERY_ACCEPT_LANGUAGE 27 + #define WINHTTP_QUERY_AUTHORIZATION 28 + #define WINHTTP_QUERY_CONTENT_ENCODING 29 + #define WINHTTP_QUERY_FORWARDED 30 + #define WINHTTP_QUERY_FROM 31 + #define WINHTTP_QUERY_IF_MODIFIED_SINCE 32 + #define WINHTTP_QUERY_LOCATION 33 + #define WINHTTP_QUERY_ORIG_URI 34 + #define WINHTTP_QUERY_REFERER 35 + #define WINHTTP_QUERY_RETRY_AFTER 36 + #define WINHTTP_QUERY_SERVER 37 + #define WINHTTP_QUERY_TITLE 38 + #define WINHTTP_QUERY_USER_AGENT 39 + #define WINHTTP_QUERY_WWW_AUTHENTICATE 40 + #define WINHTTP_QUERY_PROXY_AUTHENTICATE 41 + #define WINHTTP_QUERY_ACCEPT_RANGES 42 + #define WINHTTP_QUERY_SET_COOKIE 43 + #define WINHTTP_QUERY_COOKIE 44 + #define WINHTTP_QUERY_REQUEST_METHOD 45 // special: GET/POST etc. + #define WINHTTP_QUERY_REFRESH 46 + #define WINHTTP_QUERY_CONTENT_DISPOSITION 47 + + // NOTE: WinHttpQueryHeaders prettifiers for optional parameters. + #define WINHTTP_HEADER_NAME_BY_INDEX NULL + #define WINHTTP_NO_OUTPUT_BUFFER NULL + #define WINHTTP_NO_HEADER_INDEX NULL + + // NOTE: Http Response Status Codes + #define HTTP_STATUS_CONTINUE 100 // OK to continue with request + #define HTTP_STATUS_SWITCH_PROTOCOLS 101 // server has switched protocols in upgrade header + + #define HTTP_STATUS_OK 200 // request completed + #define HTTP_STATUS_CREATED 201 // object created, reason = new URI + #define HTTP_STATUS_ACCEPTED 202 // async completion (TBS) + #define HTTP_STATUS_PARTIAL 203 // partial completion + #define HTTP_STATUS_NO_CONTENT 204 // no info to return + #define HTTP_STATUS_RESET_CONTENT 205 // request completed, but clear form + #define HTTP_STATUS_PARTIAL_CONTENT 206 // partial GET fulfilled + #define HTTP_STATUS_WEBDAV_MULTI_STATUS 207 // WebDAV Multi-Status + + #define HTTP_STATUS_AMBIGUOUS 300 // server couldn't decide what to return + #define HTTP_STATUS_MOVED 301 // object permanently moved + #define HTTP_STATUS_REDIRECT 302 // object temporarily moved + #define HTTP_STATUS_REDIRECT_METHOD 303 // redirection w/ new access method + #define HTTP_STATUS_NOT_MODIFIED 304 // if-modified-since was not modified + #define HTTP_STATUS_USE_PROXY 305 // redirection to proxy, location header specifies proxy to use + #define HTTP_STATUS_REDIRECT_KEEP_VERB 307 // HTTP/1.1: keep same verb + #define HTTP_STATUS_PERMANENT_REDIRECT 308 // Object permanently moved keep verb + + #define HTTP_STATUS_BAD_REQUEST 400 // invalid syntax + #define HTTP_STATUS_DENIED 401 // access denied + #define HTTP_STATUS_PAYMENT_REQ 402 // payment required + #define HTTP_STATUS_FORBIDDEN 403 // request forbidden + #define HTTP_STATUS_NOT_FOUND 404 // object not found + #define HTTP_STATUS_BAD_METHOD 405 // method is not allowed + #define HTTP_STATUS_NONE_ACCEPTABLE 406 // no response acceptable to client found + #define HTTP_STATUS_PROXY_AUTH_REQ 407 // proxy authentication required + #define HTTP_STATUS_REQUEST_TIMEOUT 408 // server timed out waiting for request + #define HTTP_STATUS_CONFLICT 409 // user should resubmit with more info + #define HTTP_STATUS_GONE 410 // the resource is no longer available + #define HTTP_STATUS_LENGTH_REQUIRED 411 // the server refused to accept request w/o a length + #define HTTP_STATUS_PRECOND_FAILED 412 // precondition given in request failed + #define HTTP_STATUS_REQUEST_TOO_LARGE 413 // request entity was too large + #define HTTP_STATUS_URI_TOO_LONG 414 // request URI too long + #define HTTP_STATUS_UNSUPPORTED_MEDIA 415 // unsupported media type + #define HTTP_STATUS_RETRY_WITH 449 // retry after doing the appropriate action. + + #define HTTP_STATUS_SERVER_ERROR 500 // internal server error + #define HTTP_STATUS_NOT_SUPPORTED 501 // required not supported + #define HTTP_STATUS_BAD_GATEWAY 502 // error response received from gateway + #define HTTP_STATUS_SERVICE_UNAVAIL 503 // temporarily overloaded + #define HTTP_STATUS_GATEWAY_TIMEOUT 504 // timed out waiting for gateway + #define HTTP_STATUS_VERSION_NOT_SUP 505 // HTTP version not supported + + #define HTTP_STATUS_FIRST HTTP_STATUS_CONTINUE + #define HTTP_STATUS_LAST HTTP_STATUS_VERSION_NOT_SUP + + #define WINHTTP_CALLBACK_STATUS_RESOLVING_NAME 0x00000001 + #define WINHTTP_CALLBACK_STATUS_NAME_RESOLVED 0x00000002 + #define WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER 0x00000004 + #define WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER 0x00000008 + #define WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 0x00000010 + #define WINHTTP_CALLBACK_STATUS_REQUEST_SENT 0x00000020 + #define WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE 0x00000040 + #define WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED 0x00000080 + #define WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION 0x00000100 + #define WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED 0x00000200 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CREATED 0x00000400 + #define WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING 0x00000800 + #define WINHTTP_CALLBACK_STATUS_DETECTING_PROXY 0x00001000 + #define WINHTTP_CALLBACK_STATUS_REDIRECT 0x00004000 + #define WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE 0x00008000 + #define WINHTTP_CALLBACK_STATUS_SECURE_FAILURE 0x00010000 + #define WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE 0x00020000 + #define WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE 0x00040000 + #define WINHTTP_CALLBACK_STATUS_READ_COMPLETE 0x00080000 + #define WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE 0x00100000 + #define WINHTTP_CALLBACK_STATUS_REQUEST_ERROR 0x00200000 + #define WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 0x00400000 + + #define WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE 0x01000000 + #define WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE 0x02000000 + #define WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE 0x04000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE 0x10000000 + #define WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE 0x20000000 + + #define WINHTTP_CALLBACK_FLAG_RESOLVE_NAME (WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) + #define WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER (WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) + #define WINHTTP_CALLBACK_FLAG_SEND_REQUEST (WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | WINHTTP_CALLBACK_STATUS_REQUEST_SENT) + #define WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE (WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) + #define WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION (WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) + #define WINHTTP_CALLBACK_FLAG_HANDLES (WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) + #define WINHTTP_CALLBACK_FLAG_DETECTING_PROXY WINHTTP_CALLBACK_STATUS_DETECTING_PROXY + #define WINHTTP_CALLBACK_FLAG_REDIRECT WINHTTP_CALLBACK_STATUS_REDIRECT + #define WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE + #define WINHTTP_CALLBACK_FLAG_SECURE_FAILURE WINHTTP_CALLBACK_STATUS_SECURE_FAILURE + #define WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE + #define WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE + #define WINHTTP_CALLBACK_FLAG_READ_COMPLETE WINHTTP_CALLBACK_STATUS_READ_COMPLETE + #define WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE + #define WINHTTP_CALLBACK_FLAG_REQUEST_ERROR WINHTTP_CALLBACK_STATUS_REQUEST_ERROR + + #define WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE + + #define WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS (WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE \ + | WINHTTP_CALLBACK_STATUS_READ_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE \ + | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR \ + | WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE) + + #define WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 0xffffffff + #define WINHTTP_INVALID_STATUS_CALLBACK ((WINHTTP_STATUS_CALLBACK)(-1L)) + + typedef struct _WINHTTP_EXTENDED_HEADER + { + union + { + CHAR const *pwszName; + WCHAR const *pszName; + }; + union + { + WCHAR const *pwszValue; + CHAR const *pszValue; + }; + } WINHTTP_EXTENDED_HEADER, *PWINHTTP_EXTENDED_HEADER; + + typedef struct _WINHTTP_ASYNC_RESULT + { + DWORD *dwResult; // indicates which async API has encountered an error + DWORD dwError; // the error code if the API failed + } WINHTTP_ASYNC_RESULT, *LPWINHTTP_ASYNC_RESULT, *PWINHTTP_ASYNC_RESULT; + + typedef + VOID + (*WINHTTP_STATUS_CALLBACK)( + HINTERNET hInternet, + DWORD *dwContext, + DWORD dwInternetStatus, + VOID *lpvStatusInformation, + DWORD dwStatusInformationLength + ); + + extern "C" + { + __declspec(dllimport) HINTERNET __stdcall WinHttpOpen(WCHAR const *pszAgentW, DWORD dwAccessType, WCHAR const *pszProxyW, WCHAR const *pszProxyBypassW, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpCloseHandle(HINTERNET hInternet); + __declspec(dllimport) HINTERNET __stdcall WinHttpConnect(HINTERNET hSession, WCHAR const *pswzServerName, INTERNET_PORT nServerPort, DWORD dwReserved); + __declspec(dllimport) BOOL __stdcall WinHttpReadData(HINTERNET hRequest, VOID *lpBuffer, DWORD dwNumberOfBytesToRead, DWORD *lpdwNumberOfBytesRead); + __declspec(dllimport) HINTERNET __stdcall WinHttpOpenRequest(HINTERNET hConnect, WCHAR const *pwszVerb, WCHAR const *pwszObjectName, WCHAR const *pwszVersion, WCHAR const *pwszReferrer, WCHAR const *ppwszAcceptTypes, DWORD dwFlags); + __declspec(dllimport) BOOL __stdcall WinHttpSendRequest(HINTERNET hRequest, WCHAR const *lpszHeaders, DWORD dwHeadersLength, VOID *lpOptional, DWORD dwOptionalLength, DWORD dwTotalLength, DWORD_PTR dwContext); + __declspec(dllimport) DWORD __stdcall WinHttpAddRequestHeadersEx(HINTERNET hRequest, DWORD dwModifiers, ULONGLONG ullFlags, ULONGLONG ullExtra, DWORD cHeaders, WINHTTP_EXTENDED_HEADER *pHeaders); + __declspec(dllimport) BOOL __stdcall WinHttpSetCredentials(HINTERNET hRequest, // HINTERNET handle returned by WinHttpOpenRequest. + DWORD AuthTargets, // Only WINHTTP_AUTH_TARGET_SERVER and WINHTTP_AUTH_TARGET_PROXY are supported in this version and they are mutually exclusive + DWORD AuthScheme, // must be one of the supported Auth Schemes returned from WinHttpQueryAuthSchemes() + WCHAR * pwszUserName, // 1) NULL if default creds is to be used, in which case pszPassword will be ignored + WCHAR * pwszPassword, // 1) "" == Blank Password; 2)Parameter ignored if pszUserName is NULL; 3) Invalid to pass in NULL if pszUserName is not NULL + VOID * pAuthParams); + __declspec(dllimport) BOOL __stdcall WinHttpQueryHeaders(HINTERNET hRequest, DWORD dwInfoLevel, WCHAR const *pwszName, VOID *lpBuffer, DWORD *lpdwBufferLength, DWORD *lpdwIndex); + __declspec(dllimport) BOOL __stdcall WinHttpReceiveResponse(HINTERNET hRequest, VOID *lpReserved); + __declspec(dllimport) WINHTTP_STATUS_CALLBACK __stdcall WinHttpSetStatusCallback(HINTERNET hInternet, WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags, DWORD_PTR dwReserved); + } + + // NOTE: um/DbgHelp.h ////////////////////////////////////////////////////////////////////////// + #define SYMOPT_CASE_INSENSITIVE 0x00000001 + #define SYMOPT_UNDNAME 0x00000002 + #define SYMOPT_DEFERRED_LOADS 0x00000004 + #define SYMOPT_NO_CPP 0x00000008 + #define SYMOPT_LOAD_LINES 0x00000010 + #define SYMOPT_OMAP_FIND_NEAREST 0x00000020 + #define SYMOPT_LOAD_ANYTHING 0x00000040 + #define SYMOPT_IGNORE_CVREC 0x00000080 + #define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 + #define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 + #define SYMOPT_EXACT_SYMBOLS 0x00000400 + #define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 + #define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 + #define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 + #define SYMOPT_PUBLICS_ONLY 0x00004000 + #define SYMOPT_NO_PUBLICS 0x00008000 + #define SYMOPT_AUTO_PUBLICS 0x00010000 + #define SYMOPT_NO_IMAGE_SEARCH 0x00020000 + #define SYMOPT_SECURE 0x00040000 + #define SYMOPT_NO_PROMPTS 0x00080000 + #define SYMOPT_OVERWRITE 0x00100000 + #define SYMOPT_IGNORE_IMAGEDIR 0x00200000 + #define SYMOPT_FLAT_DIRECTORY 0x00400000 + #define SYMOPT_FAVOR_COMPRESSED 0x00800000 + #define SYMOPT_ALLOW_ZERO_ADDRESS 0x01000000 + #define SYMOPT_DISABLE_SYMSRV_AUTODETECT 0x02000000 + #define SYMOPT_READONLY_CACHE 0x04000000 + #define SYMOPT_SYMPATH_LAST 0x08000000 + #define SYMOPT_DISABLE_FAST_SYMBOLS 0x10000000 + #define SYMOPT_DISABLE_SYMSRV_TIMEOUT 0x20000000 + #define SYMOPT_DISABLE_SRVSTAR_ON_STARTUP 0x40000000 + #define SYMOPT_DEBUG 0x80000000 + + #define MAX_SYM_NAME 2000 + + typedef enum { + AddrMode1616, + AddrMode1632, + AddrModeReal, + AddrModeFlat + } ADDRESS_MODE; + + typedef struct _tagADDRESS64 { + DWORD64 Offset; + WORD Segment; + ADDRESS_MODE Mode; + } ADDRESS64, *LPADDRESS64; + + + typedef struct _KDHELP64 { + DWORD64 Thread; + DWORD ThCallbackStack; + DWORD ThCallbackBStore; + DWORD NextCallback; + DWORD FramePointer; + DWORD64 KiCallUserMode; + DWORD64 KeUserCallbackDispatcher; + DWORD64 SystemRangeStart; + DWORD64 KiUserExceptionDispatcher; + DWORD64 StackBase; + DWORD64 StackLimit; + DWORD BuildVersion; + DWORD RetpolineStubFunctionTableSize; + DWORD64 RetpolineStubFunctionTable; + DWORD RetpolineStubOffset; + DWORD RetpolineStubSize; + DWORD64 Reserved0[2]; + } KDHELP64, *PKDHELP64; + + typedef struct _tagSTACKFRAME64 { + ADDRESS64 AddrPC; // program counter + ADDRESS64 AddrReturn; // return address + ADDRESS64 AddrFrame; // frame pointer + ADDRESS64 AddrStack; // stack pointer + ADDRESS64 AddrBStore; // backing store pointer + VOID *FuncTableEntry; // pointer to pdata/fpo or NULL + DWORD64 Params[4]; // possible arguments to the function + BOOL Far; // WOW far call + BOOL Virtual; // is this a virtual frame? + DWORD64 Reserved[3]; + KDHELP64 KdHelp; + } STACKFRAME64; + + typedef struct _IMAGEHLP_LINEW64 { + DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) + VOID *Key; // internal + DWORD LineNumber; // line number in file + WCHAR *FileName; // full filename + DWORD64 Address; // first instruction of line + } IMAGEHLP_LINEW64; + + typedef struct _SYMBOL_INFOW { + ULONG SizeOfStruct; + ULONG TypeIndex; // Type Index of symbol + ULONG64 Reserved[2]; + ULONG Index; + ULONG Size; + ULONG64 ModBase; // Base Address of module comtaining this symbol + ULONG Flags; + ULONG64 Value; // Value of symbol, ValuePresent should be 1 + ULONG64 Address; // Address of symbol including base address of module + ULONG Register; // register holding value or pointer to value + ULONG Scope; // scope of the symbol + ULONG Tag; // pdb classification + ULONG NameLen; // Actual length of name + ULONG MaxNameLen; + WCHAR Name[1]; // Name of symbol + } SYMBOL_INFOW; + + typedef BOOL (__stdcall READ_PROCESS_MEMORY_ROUTINE64)(HANDLE hProcess, DWORD64 qwBaseAddress, VOID *lpBuffer, DWORD nSize, DWORD *lpNumberOfBytesRead); + typedef VOID * (__stdcall FUNCTION_TABLE_ACCESS_ROUTINE64)(HANDLE ahProcess, DWORD64 AddrBase); + typedef DWORD64(__stdcall GET_MODULE_BASE_ROUTINE64)(HANDLE hProcess, DWORD64 Address); + typedef DWORD64(__stdcall TRANSLATE_ADDRESS_ROUTINE64)(HANDLE hProcess, HANDLE hThread, ADDRESS64 *lpaddr); + + extern "C" + { + __declspec(dllimport) BOOL __stdcall StackWalk64 (DWORD MachineType, HANDLE hProcess, HANDLE hThread, STACKFRAME64 *StackFrame, VOID *ContextRecord, READ_PROCESS_MEMORY_ROUTINE64 *ReadMemoryRoutine, FUNCTION_TABLE_ACCESS_ROUTINE64 *FunctionTableAccessRoutine, GET_MODULE_BASE_ROUTINE64 *GetModuleBaseRoutine, TRANSLATE_ADDRESS_ROUTINE64 *TranslateAddress); + __declspec(dllimport) BOOL __stdcall SymFromAddrW (HANDLE hProcess, DWORD64 Address, DWORD64 *Displacement, SYMBOL_INFOW *Symbol); + __declspec(dllimport) VOID * __stdcall SymFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase); + __declspec(dllimport) BOOL __stdcall SymGetLineFromAddrW64 (HANDLE hProcess, DWORD64 dwAddr, DWORD *pdwDisplacement, IMAGEHLP_LINEW64 *Line); + __declspec(dllimport) DWORD64 __stdcall SymGetModuleBase64 (HANDLE hProcess, DWORD64 qwAddr); + __declspec(dllimport) BOOL __stdcall SymRefreshModuleList (HANDLE hProcess); + }; + + // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) DWORD __stdcall GetLastError(VOID); + } + + // NOTE: um/libloaderapi.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HMODULE __stdcall LoadLibraryA (const CHAR *lpLibFileName); + __declspec(dllimport) BOOL __stdcall FreeLibrary (HMODULE hLibModule); + __declspec(dllimport) void * __stdcall GetProcAddress (HMODULE hModule, const CHAR *lpProcName); + __declspec(dllimport) HMODULE __stdcall GetModuleHandleA (const CHAR *lpModuleName); + __declspec(dllimport) DWORD __stdcall GetModuleFileNameW(HMODULE hModule, WCHAR *lpFilename, DWORD nSize); + } + + // NOTE: um/synchapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) VOID __stdcall InitializeCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall EnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall LeaveCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionEx (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount, DWORD Flags); + __declspec(dllimport) DWORD __stdcall SetCriticalSectionSpinCount (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); + __declspec(dllimport) BOOL __stdcall TryEnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) VOID __stdcall DeleteCriticalSection (CRITICAL_SECTION *lpCriticalSection); + __declspec(dllimport) DWORD __stdcall WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds); + __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); + __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); + } + + // NOTE: um/profileapi.h /////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall QueryPerformanceCounter (LARGE_INTEGER* lpPerformanceCount); + __declspec(dllimport) BOOL __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); + } + + // NOTE: um/processthreadsapi.h //////////////////////////////////////////////////////////////// + typedef struct _PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; + } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; + + typedef struct _STARTUPINFOW { + DWORD cb; + WCHAR *lpReserved; + WCHAR *lpDesktop; + WCHAR *lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + BYTE *lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; + } STARTUPINFOW, *LPSTARTUPINFOW; + + typedef DWORD (__stdcall *PTHREAD_START_ROUTINE)( + VOID *lpThreadParameter + ); + typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE; + + extern "C" + { + __declspec(dllimport) BOOL __stdcall CreateProcessW (WCHAR const *lpApplicationName, WCHAR *lpCommandLine, SECURITY_ATTRIBUTES *lpProcessAttributes, SECURITY_ATTRIBUTES *lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, VOID *lpEnvironment, WCHAR const *lpCurrentDirectory, STARTUPINFOW *lpStartupInfo, PROCESS_INFORMATION *lpProcessInformation); + __declspec(dllimport) HANDLE __stdcall CreateThread (SECURITY_ATTRIBUTES *lpThreadAttributes, SIZE_T dwStackSize, PTHREAD_START_ROUTINE lpStartAddress, VOID *lpParameter, DWORD dwCreationFlags, DWORD *lpThreadId); + __declspec(dllimport) DWORD __stdcall GetCurrentThreadId(VOID); + __declspec(dllimport) BOOL __stdcall GetExitCodeProcess(HANDLE hProcess, DWORD *lpExitCode); + __declspec(dllimport) void __stdcall ExitProcess (UINT uExitCode); + } + + // NOTE: um/memoryapi.h //////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) VOID * __stdcall VirtualAlloc (VOID *lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); + __declspec(dllimport) BOOL __stdcall VirtualProtect(VOID *lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD *lpflOldProtect); + __declspec(dllimport) BOOL __stdcall VirtualFree (VOID *lpAddress, SIZE_T dwSize, DWORD dwFreeType); + } + + // NOTE: shared/bcrypt.h /////////////////////////////////////////////////////////////////////// + typedef VOID *BCRYPT_ALG_HANDLE; + typedef LONG NTSTATUS; + + extern "C" + { + __declspec(dllimport) NTSTATUS __stdcall BCryptOpenAlgorithmProvider(BCRYPT_ALG_HANDLE *phAlgorithm, const WCHAR *pszAlgId, const WCHAR *pszImplementation, ULONG dwFlags); + __declspec(dllimport) NTSTATUS __stdcall BCryptGenRandom (BCRYPT_ALG_HANDLE hAlgorithm, UCHAR *pbBuffer, ULONG cbBuffer, ULONG dwFlags); + } + + // NOTE: um/shellapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) HINSTANCE __stdcall ShellExecuteA(HWND hwnd, CHAR const *lpOperation, CHAR const *lpFile, CHAR const *lpParameters, CHAR const *lpDirectory, INT nShowCmd); + __declspec(dllimport) HINSTANCE __stdcall ShellExecuteW(HWND hwnd, WCHAR const *lpOperation, WCHAR const *lpFile, WCHAR const *lpParameters, WCHAR const *lpDirectory, INT nShowCmd); + } + + // NOTE: um/debugapi.h ///////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall IsDebuggerPresent(); + } + + // NOTE: um/namedpipeapi.h ///////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall CreatePipe (HANDLE *hReadPipe, HANDLE *hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); + __declspec(dllimport) BOOL __stdcall PeekNamedPipe(HANDLE hNamedPipe, VOID *lpBuffer, DWORD nBufferSize, DWORD *lpBytesRead, DWORD *lpTotalBytesAvail, DWORD *lpBytesLeftThisMessage); + } + + // NOTE: um/handleapi.h //////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); + } + + // NOTE: um/commdlg.h ////////////////////////////////////////////////////////////////////////// + typedef UINT_PTR (__stdcall *LPOFNHOOKPROC)(HWND, UINT, WPARAM, LPARAM); + typedef struct tagOFNW { + DWORD lStructSize; + HWND hwndOwner; + HINSTANCE hInstance; + WCHAR const * lpstrFilter; + LPWSTR lpstrCustomFilter; + DWORD nMaxCustFilter; + DWORD nFilterIndex; + LPWSTR lpstrFile; + DWORD nMaxFile; + LPWSTR lpstrFileTitle; + DWORD nMaxFileTitle; + WCHAR const * lpstrInitialDir; + WCHAR const * lpstrTitle; + DWORD Flags; + WORD nFileOffset; + WORD nFileExtension; + WCHAR const * lpstrDefExt; + LPARAM lCustData; + LPOFNHOOKPROC lpfnHook; + WCHAR const * lpTemplateName; + #ifdef _MAC + LPEDITMENU lpEditInfo; + LPCSTR lpstrPrompt; + #endif + #if (_WIN32_WINNT >= 0x0500) + void * pvReserved; + DWORD dwReserved; + DWORD FlagsEx; + #endif // (_WIN32_WINNT >= 0x0500) + } OPENFILENAMEW, *LPOPENFILENAMEW; + + + #define OFN_READONLY 0x00000001 + #define OFN_OVERWRITEPROMPT 0x00000002 + #define OFN_HIDEREADONLY 0x00000004 + #define OFN_NOCHANGEDIR 0x00000008 + #define OFN_SHOWHELP 0x00000010 + #define OFN_ENABLEHOOK 0x00000020 + #define OFN_ENABLETEMPLATE 0x00000040 + #define OFN_ENABLETEMPLATEHANDLE 0x00000080 + #define OFN_NOVALIDATE 0x00000100 + #define OFN_ALLOWMULTISELECT 0x00000200 + #define OFN_EXTENSIONDIFFERENT 0x00000400 + #define OFN_PATHMUSTEXIST 0x00000800 + #define OFN_FILEMUSTEXIST 0x00001000 + #define OFN_CREATEPROMPT 0x00002000 + #define OFN_SHAREAWARE 0x00004000 + #define OFN_NOREADONLYRETURN 0x00008000 + #define OFN_NOTESTFILECREATE 0x00010000 + #define OFN_NONETWORKBUTTON 0x00020000 + #define OFN_NOLONGNAMES 0x00040000 // force no long names for 4.x modules + #if(WINVER >= 0x0400) + #define OFN_EXPLORER 0x00080000 // new look commdlg + #define OFN_NODEREFERENCELINKS 0x00100000 + #define OFN_LONGNAMES 0x00200000 // force long names for 3.x modules + // OFN_ENABLEINCLUDENOTIFY and OFN_ENABLESIZING require + // Windows 2000 or higher to have any effect. + #define OFN_ENABLEINCLUDENOTIFY 0x00400000 // send include message to callback + #define OFN_ENABLESIZING 0x00800000 + #endif /* WINVER >= 0x0400 */ + #if (_WIN32_WINNT >= 0x0500) + #define OFN_DONTADDTORECENT 0x02000000 + #define OFN_FORCESHOWHIDDEN 0x10000000 // Show All files including System and hidden files + #endif // (_WIN32_WINNT >= 0x0500) + + //FlagsEx Values + #if (_WIN32_WINNT >= 0x0500) + #define OFN_EX_NOPLACESBAR 0x00000001 + #endif // (_WIN32_WINNT >= 0x0500) + + extern "C" + { + __declspec(dllimport) BOOL __stdcall GetSaveFileNameW(LPOPENFILENAMEW); + __declspec(dllimport) BOOL __stdcall GetOpenFileNameW(LPOPENFILENAMEW); + } + + // NOTE: um/shlwapi.h ////////////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) BOOL __stdcall PathRelativePathToW(WCHAR *pszPath, WCHAR const *pszFrom, DWORD dwAttrFrom, WCHAR const *pszTo, DWORD dwAttrTo); + __declspec(dllimport) BOOL __stdcall PathIsRelativeW(WCHAR *pszPath); + } + + // NOTE: um/pathcch.h ////////////////////////////////////////////////////////////////////////// + typedef enum PATHCCH_OPTIONS + { + PATHCCH_NONE = 0x0, + + // This option allows applications to gain access to long paths. It has two + // different behaviors. For process configured to enable long paths it will allow + // the returned path to be longer than the max path limit that is normally imposed. + // For process that are not this option will convert long paths into the extended + // length DOS device form (with \\?\ prefix) when the path is longer than the limit. + // This form is not length limited by the Win32 file system API on all versions of Windows. + // This second behavior is the same behavior for OSes that don't have the long path feature. + // This can not be specified with PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH. + PATHCCH_ALLOW_LONG_PATHS = 0x01, + + // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This + // Forces the API to treat the caller as long path enabled, independent of the + // process's long name enabled state. Cannot be used with PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS. + PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS = 0x02, + + // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This + // Forces the API to treat the caller as long path disabled, independent of the + // process's long name enabled state. Cannot be used with PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS. + PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS = 0x04, + + // Disable the normalization of path segments that includes removing trailing dots and spaces. + // This enables access to paths that win32 path normalization will block. + PATHCCH_DO_NOT_NORMALIZE_SEGMENTS = 0x08, + + // Convert the input path into the extended length DOS device path form (with the \\?\ prefix) + // if not already in that form. This enables access to paths that are otherwise not addressable + // due to Win32 normalization rules (that can strip trailing dots and spaces) and path + // length limitations. This option implies the same behavior of PATHCCH_DO_NOT_NORMALIZE_SEGMENTS. + // This can not be specified with PATHCCH_ALLOW_LONG_PATHS. + PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH = 0x10, + + // When combining or normalizing a path ensure there is a trailing backslash. + PATHCCH_ENSURE_TRAILING_SLASH = 0x020, + + // Convert forward slashes to back slashes and collapse multiple slashes. + // This is needed to to support sub-path or identity comparisons. + PATHCCH_CANONICALIZE_SLASHES = 0x040, + } PATHCCH_OPTIONS; + + extern "C" + { + __declspec(dllimport) HRESULT __stdcall PathCchCanonicalizeEx(PWSTR pszPathOut, size_t cchPathOut, WCHAR const *pszPathIn, ULONG dwFlags); + }; + + // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// + extern "C" + { + __declspec(dllimport) VOID __stdcall RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR* lpArguments); + }; + + // NOTE: include/excpt.h /////////////////////////////////////////////////////////////////// + #define EXCEPTION_EXECUTE_HANDLER 1 + #define EXCEPTION_CONTINUE_SEARCH 0 + #define EXCEPTION_CONTINUE_EXECUTION (-1) + + DN_MSVC_WARNING_POP +#endif // !defined(_INC_WINDOWS) +#endif // !defined(DN_OS_WINDOWS_H) diff --git a/SIMD/dn_simd_avx512f.cpp b/SIMD/dn_simd_avx512f.cpp new file mode 100644 index 0000000..24373cc --- /dev/null +++ b/SIMD/dn_simd_avx512f.cpp @@ -0,0 +1,271 @@ +#define DN_SIMD_AVX512F_CPP + +#include + +DN_API DN_Str8FindResult DN_SIMD_Str8FindAVX512F(DN_Str8 string, DN_Str8 find) +{ + // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html + DN_Str8FindResult result = {}; + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size) + return result; + + __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); + __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); + + DN_USize const search_size = string.size - find.size; + DN_USize simd_iterations = search_size / sizeof(__m512i); + char const *ptr = string.data; + + while (simd_iterations--) { + __m512i find_first_ch_block = _mm512_loadu_si512(ptr); + __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); + + // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit + // where matches are found. + __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); + + // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one + // operation using the ternarylogic intrinsic. + // + // A = first_ch_matches (find_first_ch_block ^ find_first_ch) + // B = find_last_ch_block + // C = find_last_ch + // + // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 + // + // / A / B / C / B ^ C / A | (B ^ C) / + // | 0 | 0 | 0 | 0 | 0 | + // | 0 | 0 | 1 | 1 | 1 | + // | 0 | 1 | 0 | 1 | 1 | + // | 0 | 1 | 1 | 0 | 0 | + // | 1 | 0 | 0 | 0 | 1 | + // | 1 | 0 | 1 | 1 | 1 | + // | 1 | 1 | 0 | 1 | 1 | + // | 1 | 1 | 1 | 0 | 1 | + + __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); + + // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask + // out which 32 bit elements in the vector had zero bytes. This uses a + // bit twiddling trick + // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord + __mmask16 zero_byte_mask = {}; + { + const __m512i v01 = _mm512_set1_epi32(0x01010101u); + const __m512i v80 = _mm512_set1_epi32(0x80808080u); + const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); + const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); + zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); + } + + while (zero_byte_mask) { + uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); + char const *base_ptr = ptr + (4 * lsb_zero_pos); + + if (DN_MEMCMP(base_ptr + 0, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data; + } else if (DN_MEMCMP(base_ptr + 1, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 1; + } else if (DN_MEMCMP(base_ptr + 2, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 2; + } else if (DN_MEMCMP(base_ptr + 3, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 3; + } + + if (result.found) { + result.start_to_before_match = DN_Str8_Init(string.data, result.index); + result.match = DN_Str8_Init(string.data + result.index, find.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - result.index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find.size); + return result; + } + + zero_byte_mask = DN_Bit_ClearNextLSB(zero_byte_mask); + } + + ptr += sizeof(__m512i); + } + + for (DN_USize index = ptr - string.data; index < string.size; index++) { + DN_Str8 string_slice = DN_Str8_Slice(string, index, find.size); + if (DN_Str8_Eq(string_slice, find)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8_Init(string.data, index); + result.match = DN_Str8_Init(string.data + index, find.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find.size); + return result; + } + } + + return result; +} + +DN_API DN_Str8FindResult DN_SIMD_Str8FindLastAVX512F(DN_Str8 string, DN_Str8 find) +{ + // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html + DN_Str8FindResult result = {}; + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size) + return result; + + __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); + __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); + + DN_USize const search_size = string.size - find.size; + DN_USize simd_iterations = search_size / sizeof(__m512i); + char const *ptr = string.data + search_size + 1; + + while (simd_iterations--) { + ptr -= sizeof(__m512i); + __m512i find_first_ch_block = _mm512_loadu_si512(ptr); + __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); + + // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit + // where matches are found. + __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); + + // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one + // operation using the ternarylogic intrinsic. + // + // A = first_ch_matches (find_first_ch_block ^ find_first_ch) + // B = find_last_ch_block + // C = find_last_ch + // + // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 + // + // / A / B / C / B ^ C / A | (B ^ C) / + // | 0 | 0 | 0 | 0 | 0 | + // | 0 | 0 | 1 | 1 | 1 | + // | 0 | 1 | 0 | 1 | 1 | + // | 0 | 1 | 1 | 0 | 0 | + // | 1 | 0 | 0 | 0 | 1 | + // | 1 | 0 | 1 | 1 | 1 | + // | 1 | 1 | 0 | 1 | 1 | + // | 1 | 1 | 1 | 0 | 1 | + + __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); + + // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask + // out which 32 bit elements in the vector had zero bytes. This uses a + // bit twiddling trick + // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord + __mmask16 zero_byte_mask = {}; + { + const __m512i v01 = _mm512_set1_epi32(0x01010101u); + const __m512i v80 = _mm512_set1_epi32(0x80808080u); + const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); + const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); + zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); + } + + while (zero_byte_mask) { + uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); + char const *base_ptr = ptr + (4 * lsb_zero_pos); + + if (DN_MEMCMP(base_ptr + 0, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data; + } else if (DN_MEMCMP(base_ptr + 1, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 1; + } else if (DN_MEMCMP(base_ptr + 2, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 2; + } else if (DN_MEMCMP(base_ptr + 3, find.data, find.size) == 0) { + result.found = true; + result.index = base_ptr - string.data + 3; + } + + if (result.found) { + result.start_to_before_match = DN_Str8_Init(string.data, result.index); + result.match = DN_Str8_Init(string.data + result.index, find.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - result.index); + return result; + } + + zero_byte_mask = DN_Bit_ClearNextLSB(zero_byte_mask); + } + } + + for (DN_USize index = ptr - string.data - 1; index < string.size; index--) { + DN_Str8 string_slice = DN_Str8_Slice(string, index, find.size); + if (DN_Str8_Eq(string_slice, find)) { + result.found = true; + result.index = index; + result.start_to_before_match = DN_Str8_Init(string.data, index); + result.match = DN_Str8_Init(string.data + index, find.size); + result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); + return result; + } + } + + return result; +} + +DN_API DN_Str8BinarySplitResult DN_SIMD_Str8BinarySplitAVX512F(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = {}; + DN_Str8FindResult find_result = DN_SIMD_Str8FindAVX512F(string, find); + if (find_result.found) { + result.lhs.data = string.data; + result.lhs.size = find_result.index; + result.rhs = DN_Str8_Advance(find_result.match_to_end_of_buffer, find.size); + } else { + result.lhs = string; + } + + return result; +} + +DN_API DN_Str8BinarySplitResult DN_SIMD_Str8BinarySplitLastAVX512F(DN_Str8 string, DN_Str8 find) +{ + DN_Str8BinarySplitResult result = {}; + DN_Str8FindResult find_result = DN_SIMD_Str8FindLastAVX512F(string, find); + if (find_result.found) { + result.lhs.data = string.data; + result.lhs.size = find_result.index; + result.rhs = DN_Str8_Advance(find_result.match_to_end_of_buffer, find.size); + } else { + result.lhs = string; + } + + return result; +} + +DN_API DN_USize DN_SIMD_Str8SplitAVX512F(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_USize result = 0; // The number of splits in the actual string. + if (!DN_Str8_HasData(string) || !DN_Str8_HasData(delimiter) || delimiter.size <= 0) + return result; + + DN_Str8BinarySplitResult split = {}; + DN_Str8 first = string; + do { + split = DN_SIMD_Str8BinarySplitAVX512F(first, delimiter); + if (split.lhs.size || mode == DN_Str8SplitIncludeEmptyStrings_Yes) { + if (splits && result < splits_count) + splits[result] = split.lhs; + result++; + } + first = split.rhs; + } while (first.size); + + return result; +} + +DN_API DN_Slice DN_SIMD_Str8SplitAllocAVX512F(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) +{ + DN_Slice result = {}; + DN_USize splits_required = DN_SIMD_Str8SplitAVX512F(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); + result.data = DN_Arena_NewArray(arena, DN_Str8, splits_required, DN_ZeroMem_No); + if (result.data) { + result.size = DN_SIMD_Str8SplitAVX512F(string, delimiter, result.data, splits_required, mode); + DN_Assert(splits_required == result.size); + } + return result; +} diff --git a/dqn_avx512f.h b/SIMD/dn_simd_avx512f.h similarity index 91% rename from dqn_avx512f.h rename to SIMD/dn_simd_avx512f.h index b286e0c..4c1a434 100644 --- a/dqn_avx512f.h +++ b/SIMD/dn_simd_avx512f.h @@ -1,5 +1,5 @@ -#if !defined(DN_AVX512F_H) -#define DN_AVX512F_H +#if !defined(DN_SIMD_AVX512F_H) +#define DN_SIMD_AVX512F_H /* //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -13,13 +13,11 @@ // $$ | $$ | \$ / $$ / $$ | \$$$$$$ |$$$$$$\ $$$$$$$$\ $$ | // \__| \__| \_/ \__| \__| \______/ \______|\________|\__| // -// dqn_avx512f.h -- Functions implemented w/ AVX512 +// dn_avx512f.h -- Functions implemented w/ AVX512 // //////////////////////////////////////////////////////////////////////////////////////////////////// */ -#include "dqn.h" - DN_API DN_Str8FindResult DN_Str8_FindStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8FindResult DN_Str8_FindLastStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitAVX512F (DN_Str8 string, DN_Str8 find); @@ -27,4 +25,4 @@ DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastAVX512F(DN_Str8 string, D DN_API DN_USize DN_Str8_SplitAVX512F (DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode); DN_API DN_Slice DN_Str8_SplitAllocAVX512F (DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode); -#endif // DN_AVX512F_H +#endif // DN_SIMD_AVX512F_H diff --git a/Standalone/dqn_cpp_file.h b/Standalone/dn_cpp_file.h similarity index 92% rename from Standalone/dqn_cpp_file.h rename to Standalone/dn_cpp_file.h index c794ce7..1944969 100644 --- a/Standalone/dqn_cpp_file.h +++ b/Standalone/dn_cpp_file.h @@ -1,19 +1,3 @@ -/* -//////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$\ $$$$$$$$\ -// $$ __$$\ $$ __$$\ $$ __$$\ $$ _____|\_$$ _|$$ | $$ _____| -// $$ / \__|$$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ | -// $$ | $$$$$$$ |$$$$$$$ | $$$$$\ $$ | $$ | $$$$$\ -// $$ | $$ ____/ $$ ____/ $$ __| $$ | $$ | $$ __| -// $$ | $$\ $$ | $$ | $$ | $$ | $$ | $$ | -// \$$$$$$ |$$ | $$ | $$ | $$$$$$\ $$$$$$$$\ $$$$$$$$\ -// \______/ \__| \__| \__| \______|\________|\________| -// -// dn_cpp_file.h -- Functions to emit C++ formatted code -// -//////////////////////////////////////////////////////////////////////////////// -*/ #if !defined(DN_CPP_FILE_H) #define DN_CPP_FILE_H diff --git a/Standalone/dn_keccak.h b/Standalone/dn_keccak.h new file mode 100644 index 0000000..fd44e4a --- /dev/null +++ b/Standalone/dn_keccak.h @@ -0,0 +1,666 @@ +#if !defined(DN_KC_H) +#define DN_KC_H + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ +// $$ | $$ |$$ _____|$$ __$$\ $$ __$$\ $$ __$$\ $$ | $$ | +// $$ |$$ / $$ | $$ / \__|$$ / \__|$$ / $$ |$$ |$$ / +// $$$$$ / $$$$$\ $$ | $$ | $$$$$$$$ |$$$$$ / +// $$ $$< $$ __| $$ | $$ | $$ __$$ |$$ $$< +// $$ |\$$\ $$ | $$ | $$\ $$ | $$\ $$ | $$ |$$ |\$$\ +// $$ | \$$\ $$$$$$$$\ \$$$$$$ |\$$$$$$ |$$ | $$ |$$ | \$$\ +// \__| \__|\________| \______/ \______/ \__| \__|\__| \__| +// +// dn_keccak.h -- FIPS202 SHA3 + non-finalized SHA3 (aka. Keccak) hashing algorithms +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Implementation of the Keccak hashing algorithms from the Keccak and SHA3 +// families (including the FIPS202 published algorithms and the non-finalized +// ones, i.e. the ones used in Ethereum and Monero which adopted SHA3 before it +// was finalized. The only difference between the 2 is a different delimited +// suffix). +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// MIT License +// +// Copyright (c) 2021 github.com/doy-lee +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$$$$$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ +// $$ __$$\ $$ __$$\\__$$ __|\_$$ _|$$ __$$\ $$$\ $$ |$$ __$$\ +// $$ / $$ |$$ | $$ | $$ | $$ | $$ / $$ |$$$$\ $$ |$$ / \__| +// $$ | $$ |$$$$$$$ | $$ | $$ | $$ | $$ |$$ $$\$$ |\$$$$$$\ +// $$ | $$ |$$ ____/ $$ | $$ | $$ | $$ |$$ \$$$$ | \____$$\ +// $$ | $$ |$$ | $$ | $$ | $$ | $$ |$$ |\$$$ |$$\ $$ | +// $$$$$$ |$$ | $$ | $$$$$$\ $$$$$$ |$$ | \$$ |\$$$$$$ | +// \______/ \__| \__| \______| \______/ \__| \__| \______/ +// +// Options -- Compile time build customisation +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// - Define this in one and only one C++ file to enable the implementation +// code of the header file. +// +// #define DN_KC_IMPLEMENTATION +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +#include + +#if !defined(DN_KC_MEMCPY) + #include + #define DN_KC_MEMCPY(dest, src, count) memcpy(dest, src, count) +#endif + +#if !defined(DN_KC_MEMCMP) + #include + #define DN_KC_MEMCMP(dest, src, count) memcmp(dest, src, count) +#endif + +#if !defined(DN_KC_MEMSET) + #include + #define DN_KC_MEMSET(dest, byte, count) memset(dest, byte, count) +#endif + +#if !defined(DN_KC_ASSERT) + #if defined(NDEBUG) + #define DN_KC_ASSERT(expr) + #else + #define DN_KC_ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + (*(volatile int *)0) = 0; \ + } \ + } while (0) + #endif +#endif + + +// Use this macro in a printf-like function, +/* + DN_KCString64 string = {}; + printf("%.*s\n", DN_KC_STRING64_FMT(string)); +*/ +#define DN_KC_STRING56_FMT(string) 56, string +#define DN_KC_STRING64_FMT(string) 64, string +#define DN_KC_STRING96_FMT(string) 96, string +#define DN_KC_STRING128_FMT(string) 128, string + +typedef struct DN_KCBytes28 { char data[28]; } DN_KCBytes28; // 224 bit +typedef struct DN_KCBytes32 { char data[32]; } DN_KCBytes32; // 256 bit +typedef struct DN_KCBytes48 { char data[48]; } DN_KCBytes48; // 384 bit +typedef struct DN_KCBytes64 { char data[64]; } DN_KCBytes64; // 512 bit + +typedef struct DN_KCString56 { char data[(sizeof(DN_KCBytes28) * 2) + 1]; } DN_KCString56; +typedef struct DN_KCString64 { char data[(sizeof(DN_KCBytes32) * 2) + 1]; } DN_KCString64; +typedef struct DN_KCString96 { char data[(sizeof(DN_KCBytes48) * 2) + 1]; } DN_KCString96; +typedef struct DN_KCString128 { char data[(sizeof(DN_KCBytes64) * 2) + 1]; } DN_KCString128; + +#define DN_KC_LANE_SIZE_U64 5 +typedef struct DN_KCState { + uint8_t state[DN_KC_LANE_SIZE_U64 * DN_KC_LANE_SIZE_U64 * sizeof(uint64_t)]; + int state_size; // The number of bytes written to the state + int absorb_size; // The amount of bytes to absorb/sponge in/from the state + int hash_size_bits; // The size of the hash the context was initialised for in bits + char delimited_suffix; // The delimited suffix of the current hash +} DN_KCState; + +// NOTE: SHA3/Keccak Streaming API ///////////////////////////////////////////////////////////////// +// Setup a hashing state for either +// - FIPS 202 SHA3 +// - Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6 in SHA3) +// The non-finalized SHA3 version is the one adopted by many cryptocurrencies +// such as Ethereum and Monero as they adopted SHA3 before it was finalized. +// +// The state produced from this function is to be used alongside the +// 'KeccakUpdate' and 'KC_HashFinish' functions. +// +// sha3: If true, setup the state for FIPS 202 SHA3, otherwise the non-finalized version +// hash_size_bits: The number of bits to setup the context for, available sizes are 224, 256, 384 and 512. +DN_KCState DN_KC_HashInit(bool sha3, uint16_t hash_size_bits); + +// After initialising a 'DN_KCState' via 'DN_KeccakSHA3Init', iteratively +// update the hash with new data by calling 'DN_KC_HashUpdate'. On completion, +// call 'DN_KC_HashFinish' to generate the hash from the state. The 'dest_size' +// must be at-least the (bit-size / 8), i.e. for 'DN_Keccak512Init' it must be +// atleast (512 / 8) bytes, 64 bytes. +void DN_KC_HashUpdate(DN_KCState *keccak, void const *data, size_t data_size); +DN_KCBytes32 DN_KC_HashFinish(DN_KCState const *keccak); +void DN_KC_HashFinishPtr(DN_KCState *keccak, void *dest, size_t dest_size); + +// NOTE: Simple API //////////////////////////////////////////////////////////////////////////////// +// Helper function that combines the Init, Update and Finish step in one shot, +// i.e. hashing a singlular contiguous buffer. Use the streaming API if data +// is split across different buffers. +void DN_KC_Hash(bool sha3, uint16_t hash_size_bits, void const *src, uint64_t src_size, void *dest, int dest_size); + +// NOTE: SHA3 Helpers ////////////////////////////////////////////////////////////////////////////// +// NOTE: SHA3 Streaming API +#define DN_KC_SHA3_224Init(src, size, dest, dest_size) DN_KC_HashInit(true /*sha3*/, 224) +#define DN_KC_SHA3_224Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_SHA3_224Finish(state) DN_KC_HashFinish(state) +#define DN_KC_SHA3_224FinishPtr(state, dest, size) DN_KC_HashFinish(state, dest, size) + +#define DN_KC_SHA3_256Init(src, size, dest, dest_size) DN_KC_HashInit(true /*sha3*/, 256) +#define DN_KC_SHA3_256Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_SHA3_256Finish(state) DN_KC_HashFinish(state) +#define DN_KC_SHA3_256FinishPtr(state, dest, size) DN_KC_HashFinish(state, dest, size) + +#define DN_KC_SHA3_384Init(src, size, dest, dest_size) DN_KC_HashInit(true /*sha3*/, 384) +#define DN_KC_SHA3_384Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_SHA3_384Finish(state) DN_KC_HashFinish(state) +#define DN_KC_SHA3_384FinishPtr(state, dest, size) DN_KC_HashFinish(state, dest, size) + +#define DN_KC_SHA3_512Init(src, size, dest, dest_size) DN_KC_HashInit(true /*sha3*/, 512) +#define DN_KC_SHA3_512Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_SHA3_512Finish(state) DN_KC_HashFinish(state) +#define DN_KC_SHA3_512FinishPtr(state, dest, size) DN_KC_HashFinishPtr(state, dest, size) + +// NOTE: SHA3 one-shot API +#define DN_KC_SHA3_224Ptr(src, size, dest, dest_size) DN_KC_Hash(true /*sha3*/, 224, src, size, dest, dest_size) +#define DN_KC_SHA3_256Ptr(src, size, dest, dest_size) DN_KC_Hash(true /*sha3*/, 256, src, size, dest, dest_size) +#define DN_KC_SHA3_384Ptr(src, size, dest, dest_size) DN_KC_Hash(true /*sha3*/, 384, src, size, dest, dest_size) +#define DN_KC_SHA3_512Ptr(src, size, dest, dest_size) DN_KC_Hash(true /*sha3*/, 512, src, size, dest, dest_size) + +DN_KCBytes28 DN_KC_SHA3_224(void const *src, uint64_t size); +DN_KCBytes32 DN_KC_SHA3_256(void const *src, uint64_t size); +DN_KCBytes48 DN_KC_SHA3_384(void const *src, uint64_t size); +DN_KCBytes64 DN_KC_SHA3_512(void const *src, uint64_t size); + +// NOTE: Keccak Helpers //////////////////////////////////////////////////////////////////////////// +// NOTE: SHA3 Streaming API +#define DN_KC_Keccak224Init() DN_KC_HashInit(false /*sha3*/, 224) +#define DN_KC_Keccak224Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_Keccak224Finish(state) DN_KC_HashFinish(state) +#define DN_KC_Keccak224FinishPtr(state, dest, size) DN_KC_HashFinishPtr(state, dest, size) + +#define DN_KC_Keccak256Init() DN_KC_HashInit(false /*sha3*/, 256) +#define DN_KC_Keccak256Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_Keccak256Finish(state) DN_KC_HashFinish(state) +#define DN_KC_Keccak256FinishPtr(state, dest, size) DN_KC_HashFinishPtr(state, dest, size) + +#define DN_KC_Keccak384Init() DN_KC_HashInit(false /*sha3*/, 384) +#define DN_KC_Keccak384Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_Keccak384Finish(state) DN_KC_HashFinish(state) +#define DN_KC_Keccak384FinishPtr(state, dest, size) DN_KC_HashFinishPtr(state, dest, size) + +#define DN_KC_Keccak512Init() DN_KC_HashInit(false /*sha3*/, 512) +#define DN_KC_Keccak512Update(state, src, size) DN_KC_HashUpdate(state, src, size) +#define DN_KC_Keccak512Finish(state) DN_KC_HashFinish(state) +#define DN_KC_Keccak512FinishPtr(state, dest, size) DN_KC_HashFinishPtr(state, dest, size) + +// NOTE: SHA3 one-shot API +#define DN_KC_Keccak224Ptr(src, size, dest, dest_size) DN_KC_Hash(false /*sha3*/, 224, src, size, dest, dest_size) +#define DN_KC_Keccak256Ptr(src, size, dest, dest_size) DN_KC_Hash(false /*sha3*/, 256, src, size, dest, dest_size) +#define DN_KC_Keccak384Ptr(src, size, dest, dest_size) DN_KC_Hash(false /*sha3*/, 384, src, size, dest, dest_size) +#define DN_KC_Keccak512Ptr(src, size, dest, dest_size) DN_KC_Hash(false /*sha3*/, 512, src, size, dest, dest_size) + +DN_KCBytes28 DN_KC_Keccak224(void const *src, uint64_t size); +DN_KCBytes32 DN_KC_Keccak256(void const *src, uint64_t size); +DN_KCBytes48 DN_KC_Keccak384(void const *src, uint64_t size); +DN_KCBytes64 DN_KC_Keccak512(void const *src, uint64_t size); + +#if defined(DN_BASE_STRING_H) +// NOTE: SHA3 - Helpers for DN data structures //////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_SHA3_224Str8(DN_Str8 string); +DN_KCBytes32 DN_KC_SHA3_256Str8(DN_Str8 string); +DN_KCBytes48 DN_KC_SHA3_384Str8(DN_Str8 string); +DN_KCBytes64 DN_KC_SHA3_512Str8(DN_Str8 string); + +// NOTE: Keccak - Helpers for DN data structures ////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_Keccak224Str8(DN_Str8 string); +DN_KCBytes32 DN_KC_Keccak256Str8(DN_Str8 string); +DN_KCBytes48 DN_KC_Keccak384Str8(DN_Str8 string); +DN_KCBytes64 DN_KC_Keccak512Str8(DN_Str8 string); +#endif // DN_BASE_STRING_H + +// NOTE: Helper functions ////////////////////////////////////////////////////////////////////////// +// Convert a binary buffer into its hex representation into dest. The dest +// buffer must be large enough to contain the hex representation, i.e. +// atleast src_size * 2). This function does *not* null-terminate the buffer. +void DN_KC_BytesToHex(void const *src, uint64_t src_size, char *dest, uint64_t dest_size); + +// Converts a fixed amount of bytes into a hexadecimal string. Helper functions +// that call into DN_KCBytesToHex. +// return: The hexadecimal string of the bytes, null-terminated. +DN_KCString56 DN_KC_Bytes28ToHex(DN_KCBytes28 const *bytes); +DN_KCString64 DN_KC_Bytes32ToHex(DN_KCBytes32 const *bytes); +DN_KCString96 DN_KC_Bytes48ToHex(DN_KCBytes48 const *bytes); +DN_KCString128 DN_KC_Bytes64ToHex(DN_KCBytes64 const *bytes); + +// Compares byte data structures for byte equality (via memcmp). +// return: 1 if the contents are equal otherwise 0. +int DN_KC_Bytes28Equals(DN_KCBytes28 const *a, DN_KCBytes28 const *b); +int DN_KC_Bytes32Equals(DN_KCBytes32 const *a, DN_KCBytes32 const *b); +int DN_KC_Bytes48Equals(DN_KCBytes48 const *a, DN_KCBytes48 const *b); +int DN_KC_Bytes64Equals(DN_KCBytes64 const *a, DN_KCBytes64 const *b); + +#if defined(DN_BASE_STRING_H) +// NOTE: Other helper functions for DN data structures //////////////////////////////////////////// +// Converts a 64 character hex string into the 32 byte binary representation. +// Invalid hex characters in the string will be represented as 0. +// hex: Must be exactly a 64 character hex string. +DN_KCBytes32 DN_KC_Hex64ToBytes(DN_Str8 hex); +#endif // DN_BASE_STRING_H +#endif // DN_KC_H + +#if defined(DN_KC_IMPLEMENTATION) +uint64_t const DN_KC_ROUNDS[] = { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B, + 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088, + 0x0000000080008009, 0x000000008000000A, 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, +}; + +uint64_t const DN_KC_ROTATIONS[][5] = +{ + {0, 36, 3, 41, 18}, + {1, 44, 10, 45, 2}, + {62, 6, 43, 15, 61}, + {28, 55, 25, 21, 56}, + {27, 20, 39, 8, 14}, +}; + +#define DN_KC_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate))))) + +static void DN_KC_Permute_(void *state) +{ + // TODO(dn): Do some profiling on unrolling and can we SIMD some part of + // this? Unroll loop, look at data dependencies and investigate. + + uint64_t *lanes_u64 = (uint64_t *)state; + for (int round_index = 0; round_index < 24; round_index++) + { + #define LANE_INDEX(x, y) ((x) + ((y) * DN_KC_LANE_SIZE_U64)) + // ?? step ////////////////////////////////////////////////////////////////////////////////// +#if 1 + uint64_t c[DN_KC_LANE_SIZE_U64]; + for (int x = 0; x < DN_KC_LANE_SIZE_U64; x++) { + c[x] = lanes_u64[LANE_INDEX(x, 0)] ^ + lanes_u64[LANE_INDEX(x, 1)] ^ + lanes_u64[LANE_INDEX(x, 2)] ^ + lanes_u64[LANE_INDEX(x, 3)] ^ + lanes_u64[LANE_INDEX(x, 4)]; + } + + uint64_t d[DN_KC_LANE_SIZE_U64]; + for (int x = 0; x < DN_KC_LANE_SIZE_U64; x++) + d[x] = c[(x + 4) % DN_KC_LANE_SIZE_U64] ^ DN_KC_ROL64(c[(x + 1) % DN_KC_LANE_SIZE_U64], 1); + + for (int y = 0; y < DN_KC_LANE_SIZE_U64; y++) + for (int x = 0; x < DN_KC_LANE_SIZE_U64; x++) + lanes_u64[LANE_INDEX(x, y)] ^= d[x]; +#else + uint64_t c[5], d[5]; + c[0] = lanes_u64[0 * 5 + 0] ^ lanes_u64[1 * 5 + 0] ^ lanes_u64[2 * 5 + 0] ^ lanes_u64[3 * 5 + 0] ^ lanes_u64[4 * 5 + 0]; + c[1] = lanes_u64[0 * 5 + 1] ^ lanes_u64[1 * 5 + 1] ^ lanes_u64[2 * 5 + 1] ^ lanes_u64[3 * 5 + 1] ^ lanes_u64[4 * 5 + 1]; + c[2] = lanes_u64[0 * 5 + 2] ^ lanes_u64[1 * 5 + 2] ^ lanes_u64[2 * 5 + 2] ^ lanes_u64[3 * 5 + 2] ^ lanes_u64[4 * 5 + 2]; + c[3] = lanes_u64[0 * 5 + 3] ^ lanes_u64[1 * 5 + 3] ^ lanes_u64[2 * 5 + 3] ^ lanes_u64[3 * 5 + 3] ^ lanes_u64[4 * 5 + 3]; + c[4] = lanes_u64[0 * 5 + 4] ^ lanes_u64[1 * 5 + 4] ^ lanes_u64[2 * 5 + 4] ^ lanes_u64[3 * 5 + 4] ^ lanes_u64[4 * 5 + 4]; + + d[0] = c[4] ^ DN_KC_ROL64(c[1], 1); + d[1] = c[0] ^ DN_KC_ROL64(c[2], 1); + d[2] = c[1] ^ DN_KC_ROL64(c[3], 1); + d[3] = c[2] ^ DN_KC_ROL64(c[4], 1); + d[4] = c[3] ^ DN_KC_ROL64(c[0], 1); +#endif + + // ?? and ?? steps /////////////////////////////////////////////////////////////////////////// + uint64_t b[DN_KC_LANE_SIZE_U64 * DN_KC_LANE_SIZE_U64]; + for (int y = 0; y < DN_KC_LANE_SIZE_U64; y++) { + for (int x = 0; x < DN_KC_LANE_SIZE_U64; x++) { + uint64_t lane = lanes_u64[LANE_INDEX(x, y)]; + uint64_t rotate_count = DN_KC_ROTATIONS[x][y]; + b[LANE_INDEX(y, (2 * x + 3 * y) % 5)] = DN_KC_ROL64(lane, rotate_count); + } + } + + // ?? step ////////////////////////////////////////////////////////////////////////////////// + for (int y = 0; y < DN_KC_LANE_SIZE_U64; y++) { + for (int x = 0; x < DN_KC_LANE_SIZE_U64; x++) { + uint64_t rhs = ~b[LANE_INDEX((x + 1) % 5, y)] & b[LANE_INDEX((x + 2) % 5, y)]; + + lanes_u64[LANE_INDEX(x, y)] = b[LANE_INDEX(x, y)] ^ rhs; + } + } + + // ?? step ////////////////////////////////////////////////////////////////////////////////// + lanes_u64[LANE_INDEX(0, 0)] ^= DN_KC_ROUNDS[round_index]; + #undef LANE_INDEX + #undef DN_KC_ROL64 + } +} + +// NOTE: Streaming API ///////////////////////////////////////////////////////////////////////////// +DN_KCState DN_KC_HashInit(bool sha3, uint16_t hash_size_bits) +{ + char const SHA3_DELIMITED_SUFFIX = 0x06; + char const KECCAK_DELIMITED_SUFFIX = 0x01; + int const bitrate = 1600 - (hash_size_bits * 2); + +#if defined(__cplusplus) + DN_KCState result = {}; +#else + DN_KCState result = {0}; +#endif + result.hash_size_bits = hash_size_bits; + result.absorb_size = bitrate / 8; + result.delimited_suffix = sha3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX; + DN_KC_ASSERT(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600); + return result; +} + +void DN_KC_HashUpdate(DN_KCState *keccak, void const *data, size_t data_size) +{ + uint8_t *state = keccak->state; + uint8_t const *ptr = (uint8_t *)data; + size_t ptr_size = data_size; + while (ptr_size > 0) { + size_t space = keccak->absorb_size - keccak->state_size; + int bytes_to_absorb = (int)(space < ptr_size ? space : ptr_size); + + for (int index = 0; index < bytes_to_absorb; index++) + state[keccak->state_size + index] ^= ptr[index]; + + ptr += bytes_to_absorb; + keccak->state_size += bytes_to_absorb; + ptr_size -= bytes_to_absorb; + + if (keccak->state_size >= keccak->absorb_size) { + DN_KC_ASSERT(keccak->state_size == keccak->absorb_size); + DN_KC_Permute_(state); + keccak->state_size = 0; + } + } +} + +DN_KCBytes32 DN_KC_HashFinish(DN_KCState *keccak) +{ + DN_KCBytes32 result = {}; + DN_KC_HashFinishPtr(keccak, result.data, sizeof(result.data)); + return result; +} + +void DN_KC_HashFinishPtr(DN_KCState *keccak, void *dest, size_t dest_size) +{ + DN_KC_ASSERT(dest_size >= (size_t)(keccak->hash_size_bits / 8)); + + // Sponge Finalization Step: Final padding bit ///////////////////////////////////////////////// + int const INDEX_OF_0X80_BYTE = keccak->absorb_size - 1; + int const delimited_suffix_index = keccak->state_size; + DN_KC_ASSERT(delimited_suffix_index < keccak->absorb_size); + + uint8_t *state = keccak->state; + state[delimited_suffix_index] ^= keccak->delimited_suffix; + + // NOTE: In the reference implementation, it checks that if the + // delimited suffix is set to the padding bit (0x80), then we need to + // permute twice. Once for the delimited suffix, and a second time for + // the "padding" permute. + // + // However all standard algorithms either specify a 0x01, or 0x06, 0x04 + // delimited suffix and so forth- so this case is never hit. We can omit + // this from the implementation here. + + state[INDEX_OF_0X80_BYTE] ^= 0x80; + DN_KC_Permute_(state); + + // Squeeze Step: Squeeze bytes from the state into our hash //////////////////////////////////// + uint8_t *dest_u8 = (uint8_t *)dest; + size_t const squeeze_count = dest_size / keccak->absorb_size; + size_t squeeze_index = 0; + for (; squeeze_index < squeeze_count; squeeze_index++) { + if (squeeze_index) + DN_KC_Permute_(state); + DN_KC_MEMCPY(dest_u8, state, keccak->absorb_size); + dest_u8 += keccak->absorb_size; + } + + // Squeeze Finalisation Step: Remainder bytes in hash ////////////////////////////////////////// + int const remainder = dest_size % keccak->absorb_size; + if (remainder) { + if (squeeze_index) + DN_KC_Permute_(state); + DN_KC_MEMCPY(dest_u8, state, remainder); + } +} + +void DN_KC_Hash(bool sha3, uint16_t hash_size_bits, void const *src, size_t src_size, void *dest, size_t dest_size) +{ + DN_KCState state = DN_KC_HashInit(sha3, hash_size_bits); + DN_KC_HashUpdate(&state, src, src_size); + DN_KC_HashFinishPtr(&state, dest, dest_size); +} + +// NOTE: SHA3 Helpers ////////////////////////////////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_SHA3_224(void const *src, size_t size) +{ + DN_KCBytes28 result; + DN_KC_SHA3_224Ptr(src, size, result.data, sizeof(result.data)); + return result; +} + +DN_KCBytes32 DN_KC_SHA3_256(void const *src, size_t size) +{ + DN_KCBytes32 result; + DN_KC_SHA3_256Ptr(src, size, result.data, sizeof(result.data)); + return result; +} + +DN_KCBytes48 DN_KC_SHA3_384(void const *src, size_t size) +{ + DN_KCBytes48 result; + DN_KC_SHA3_384Ptr(src, size, result.data, sizeof(result.data)); + return result; +} + +DN_KCBytes64 DN_KC_SHA3_512(void const *src, size_t size) +{ + DN_KCBytes64 result; + DN_KC_SHA3_512Ptr(src, size, result.data, sizeof(result.data)); + return result; +} + +// NOTE: Keccak Helpers //////////////////////////////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_Keccak224(void const *src, size_t size) +{ + DN_KCBytes28 result; + DN_KC_Keccak224Ptr(src, size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes32 DN_KC_Keccak256(void const *src, size_t size) +{ + DN_KCBytes32 result; + DN_KC_Keccak256Ptr(src, size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes48 DN_KC_Keccak384(void const *src, size_t size) +{ + DN_KCBytes48 result; + DN_KC_Keccak384Ptr(src, size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes64 DN_KC_Keccak512(void const *src, size_t size) +{ + DN_KCBytes64 result; + DN_KC_Keccak512Ptr(src, size, result.data, sizeof(result)); + return result; +} + +#if defined(DN_BASE_STRING_H) +// NOTE: SHA3 - Helpers for DN data structures //////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_SHA3_224Str8(DN_Str8 string) +{ + DN_KCBytes28 result; + DN_KC_SHA3_224Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes32 DN_KC_SHA3_256Str8(DN_Str8 string) +{ + DN_KCBytes32 result; + DN_KC_SHA3_256Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes48 DN_KC_SHA3_384Str8(DN_Str8 string) +{ + DN_KCBytes48 result; + DN_KC_SHA3_384Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes64 DN_KC_SHA3_512Str8(DN_Str8 string) +{ + DN_KCBytes64 result; + DN_KC_SHA3_512Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} +#endif // DN_BASE_STRING_H + +#if defined(DN_BASE_STRING_H) +// NOTE: Keccak - Helpers for DN data structures ////////////////////////////////////////////////// +DN_KCBytes28 DN_KC_Keccak224Str8(DN_Str8 string) +{ + DN_KCBytes28 result; + DN_KC_Keccak224Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes32 DN_KC_Keccak256Str8(DN_Str8 string) +{ + DN_KCBytes32 result; + DN_KC_Keccak256Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes48 DN_KC_Keccak384Str8(DN_Str8 string) +{ + DN_KCBytes48 result; + DN_KC_Keccak384Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} + +DN_KCBytes64 DN_KC_Keccak512Str8(DN_Str8 string) +{ + DN_KCBytes64 result; + DN_KC_Keccak512Ptr(string.data, string.size, result.data, sizeof(result)); + return result; +} +#endif // DN_BASE_STRING_H + +// NOTE: Helper functions ////////////////////////////////////////////////////////////////////////// +void DN_KC_BytesToHex(void const *src, size_t src_size, char *dest, size_t dest_size) +{ + (void)src_size; (void)dest_size; + DN_KC_ASSERT(dest_size >= src_size * 2); + + unsigned char *src_u8 = (unsigned char *)src; + for (size_t src_index = 0, dest_index = 0; src_index < src_size; + src_index += 1, dest_index += 2) { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index + 0] = hex01 < 10 ? (hex01 + '0') : (hex01 - 10) + 'a'; + dest[dest_index + 1] = hex02 < 10 ? (hex02 + '0') : (hex02 - 10) + 'a'; + } +} + +DN_KCString56 DN_KC_Bytes28ToHex(DN_KCBytes28 const *bytes) +{ + DN_KCString56 result; + DN_KC_BytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + result.data[sizeof(result.data) - 1] = 0; + return result; +} + +DN_KCString64 DN_KC_Bytes32ToHex(DN_KCBytes32 const *bytes) +{ + DN_KCString64 result; + DN_KC_BytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + result.data[sizeof(result.data) - 1] = 0; + return result; +} + +DN_KCString96 DN_KC_Bytes48ToHex(DN_KCBytes48 const *bytes) +{ + DN_KCString96 result; + DN_KC_BytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + result.data[sizeof(result.data) - 1] = 0; + return result; +} + +DN_KCString128 DN_KC_Bytes64ToHex(DN_KCBytes64 const *bytes) +{ + DN_KCString128 result; + DN_KC_BytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + result.data[sizeof(result.data) - 1] = 0; + return result; +} + +int DN_KC_Bytes28Equals(DN_KCBytes28 const *a, DN_KCBytes28 const *b) +{ + int result = DN_KC_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int DN_KC_Bytes32Equals(DN_KCBytes32 const *a, DN_KCBytes32 const *b) +{ + int result = DN_KC_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int DN_KC_Bytes48Equals(DN_KCBytes48 const *a, DN_KCBytes48 const *b) +{ + int result = DN_KC_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int DN_KC_Bytes64Equals(DN_KCBytes64 const *a, DN_KCBytes64 const *b) +{ + int result = DN_KC_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +#if defined(DN_BASE_STRING_H) +// NOTE: Other helper functions for DN data structures //////////////////////////////////////////// +DN_KCBytes32 DN_KC_Hex64ToBytes(DN_Str8 hex) +{ + DN_KC_ASSERT(hex.size == 64); + DN_KCBytes32 result; + DN_CVT_HexToBytesPtr(hex, result.data, sizeof(result)); + return result; +} +#endif // DN_BASE_STRING_H +#endif // DN_KC_IMPLEMENTATION diff --git a/Standalone/dn_utest.h b/Standalone/dn_utest.h new file mode 100644 index 0000000..599517b --- /dev/null +++ b/Standalone/dn_utest.h @@ -0,0 +1,330 @@ +#if !defined(DN_UT_H) +#define DN_UT_H + +/* +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$$\ +// $$ | $$ |\__$$ __|$$ _____|$$ __$$\\__$$ __| +// $$ | $$ | $$ | $$ | $$ / \__| $$ | +// $$ | $$ | $$ | $$$$$\ \$$$$$$\ $$ | +// $$ | $$ | $$ | $$ __| \____$$\ $$ | +// $$ | $$ | $$ | $$ | $$\ $$ | $$ | +// \$$$$$$ | $$ | $$$$$$$$\ \$$$$$$ | $$ | +// \______/ \__| \________| \______/ \__| +// +// dn_utest.h -- Extremely minimal unit testing framework +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// A super minimal testing framework, most of the logic here is the pretty +// printing of test results. +// +// NOTE: Configuration ///////////////////////////////////////////////////////////////////////////// +// +// #define DN_UT_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. This will also automatically enable the JSMN +// implementation. +// +// #define DN_UT_RESULT_LPAD +// Define this to a number to specify how much to pad the output of the test +// result line before the test result is printed. +// +// #define DN_UT_RESULT_PAD_CHAR +// Define this to a character to specify the default character to use for +// padding. By default this is '.' +// +// #define DN_UT_SPACING +// Define this to a number to specify the number of spaces between the group +// declaration and the test output in the group. +// +// #define DN_UT_BAD_COLOR +// Define this to a terminal color code to specify what color errors will be +// presented as. +// +// #define DN_UT_GOOD_COLOR +// Define this to a terminal color code to specify what color sucess will be +// presented as. +// +//////////////////////////////////////////////////////////////////////////////////////////////////// +*/ + +// NOTE: Macros //////////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include + +#if !defined(DN_UT_RESULT_LPAD) + #define DN_UT_RESULT_LPAD 90 +#endif + +#if !defined(DN_UT_RESULT_PAD_CHAR) + #define DN_UT_RESULT_PAD_CHAR '.' +#endif + +#if !defined(DN_UT_SPACING) + #define DN_UT_SPACING 2 +#endif + +#if !defined(DN_UT_BAD_COLOR) + #define DN_UT_BAD_COLOR "\x1b[31m" +#endif + +#if !defined(DN_UT_GOOD_COLOR) + #define DN_UT_GOOD_COLOR "\x1b[32m" +#endif + +#define DN_UT_COLOR_RESET "\x1b[0m" + +#define DN_UT_Test(test, fmt, ...) \ + for (int dummy_ = (DN_UT_BeginF((test), fmt, ##__VA_ARGS__), 0); \ + (void)dummy_, (test)->state == DN_UTState_TestBegun; \ + DN_UT_End(test)) + +#define DN_UT_AssertF(test, expr, fmt, ...) \ + DN_UT_AssertAtF((test), __FILE__, __LINE__, (expr), fmt, ##__VA_ARGS__) + +#define DN_UT_Assert(test, expr) \ + DN_UT_AssertAt((test), __FILE__, __LINE__, (expr)) + +// TODO: Fix the logs. They print before the tests, we should accumulate logs +// per test, then, dump them on test on. But to do this nicely without crappy +// mem management we need to implement an arena. +#define DN_UT_Log(test, fmt, ...) \ + DN_UT_LogF(test, "%*s" fmt "\n", DN_UT_SPACING * 2, "", ##__VA_ARGS__) + +#define DN_UT_AssertAtF(test, file, line, expr, fmt, ...) \ + do { \ + if (!(expr)) { \ + (test)->state = DN_UTState_TestFailed; \ + DN_UT_LogInsideTestF(test, \ + "%*sAssertion File: %s:%d\n" \ + "%*sExpression: [" #expr \ + "]\n" \ + "%*sReason: " fmt "\n", \ + DN_UT_SPACING * 2, \ + "", \ + file, \ + line, \ + DN_UT_SPACING * 2, \ + "", \ + DN_UT_SPACING * 2, \ + "", \ + ##__VA_ARGS__); \ + } \ + } while (0) + +#define DN_UT_AssertAt(test, file, line, expr) \ + do { \ + if (!(expr)) { \ + (test)->state = DN_UTState_TestFailed; \ + DN_UT_LogInsideTestF(test, \ + "%*sAssertion File: %s:%d\n" \ + "%*sExpression: [" #expr "]\n", \ + DN_UT_SPACING * 2, \ + "", \ + file, \ + line, \ + DN_UT_SPACING * 2, \ + ""); \ + } \ + } while (0) + +// NOTE: Header //////////////////////////////////////////////////////////////////////////////////// +typedef enum DN_UTState +{ + DN_UTState_Nil, + DN_UTState_TestBegun, + DN_UTState_TestFailed, +} DN_UTState; + +typedef struct DN_UTStr8Link +{ + char *data; + size_t size; + DN_UTStr8Link *next; + DN_UTStr8Link *prev; +} DN_UTStr8Link; + +typedef struct DN_UTCore +{ + int num_tests_in_group; + int num_tests_ok_in_group; + DN_UTState state; + char name[256]; + size_t name_size; + DN_UTStr8Link *curr_test_messages; + DN_UTStr8Link *output; +} DN_UTCore; + +void DN_UT_BeginFV(DN_UTCore *test, char const *fmt, va_list args); +void DN_UT_BeginF(DN_UTCore *test, char const *fmt, ...); +void DN_UT_End(DN_UTCore *test); + +void DN_UT_LogF(DN_UTCore *test, char const *fmt, ...); +void DN_UT_LogInsideTestF(DN_UTCore *test, char const *fmt, ...); + +bool DN_UT_AllTestsPassed(DN_UTCore const *test); +void DN_UT_PrintTests(DN_UTCore const *test); +#endif // DN_UT_H + +// NOTE: Implementation //////////////////////////////////////////////////////////////////////////// +#if defined(DN_UT_IMPLEMENTATION) +DN_UTCore DN_UT_Init() +{ + DN_UTCore result = {}; + result.output = (DN_UTStr8Link *)calloc(1, sizeof(*result.output)); + result.curr_test_messages = (DN_UTStr8Link *)calloc(1, sizeof(*result.curr_test_messages)); + assert(result.output); + assert(result.curr_test_messages); + result.output->next = result.output->prev = result.output; + result.curr_test_messages->next = result.curr_test_messages->prev = result.curr_test_messages; + return result; +} + +void DN_UT_Deinit(DN_UTCore *ut) +{ + for (DN_UTStr8Link *it = ut->output->next; it != ut->output; it = ut->output->next) { + it->next->prev = it->prev; + it->prev->next = it->next; + free(it); + } + free(ut->output); + + for (DN_UTStr8Link *it = ut->curr_test_messages->next; it != ut->curr_test_messages; it = ut->curr_test_messages->next) { + it->next->prev = it->prev; + it->prev->next = it->next; + free(it); + } + free(ut->curr_test_messages); +} + +void DN_UT_BeginFV(DN_UTCore *ut, char const *fmt, va_list args) +{ + assert(ut->output && ut->curr_test_messages && "Test must be initialised by calling DN_UT_Init()"); + assert(ut->state == DN_UTState_Nil && + "Nesting a unit ut within another unit test is not allowed, ensure" + "the first test has finished by calling DN_UT_End"); + + ut->num_tests_in_group++; + ut->state = DN_UTState_TestBegun; + ut->name_size = 0; + { + va_list args_copy; + va_copy(args_copy, args); + ut->name_size = vsnprintf(NULL, 0, fmt, args_copy); + va_end(args_copy); + } + + assert(ut->name_size < sizeof(ut->name)); + vsnprintf(ut->name, sizeof(ut->name), fmt, args); +} + +void DN_UT_BeginF(DN_UTCore *ut, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_UT_BeginFV(ut, fmt, args); + va_end(args); +} + +static DN_UTStr8Link *DN_UT_AllocStr8LinkFV(char const *fmt, va_list args) +{ + va_list args_copy; + va_copy(args_copy, args); + size_t size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; + va_end(args_copy); + + DN_UTStr8Link *result = (DN_UTStr8Link *)malloc(sizeof(*result) + size); + if (result) { + result->data = (char *)result + sizeof(*result); + result->size = vsnprintf(result->data, size, fmt, args); + } + return result; +} + +void DN_UT_End(DN_UTCore *ut) +{ + assert(ut->state != DN_UTState_Nil && "Test was marked as ended but a ut was never commenced using DN_UT_Begin"); + size_t pad_size = DN_UT_RESULT_LPAD - (DN_UT_SPACING + ut->name_size); + if (pad_size < 0) + pad_size = 0; + + char pad_buffer[DN_UT_RESULT_LPAD] = {}; + memset(pad_buffer, DN_UT_RESULT_PAD_CHAR, pad_size); + + DN_UT_LogF(ut, "%*s%.*s%.*s", DN_UT_SPACING, "", (int)ut->name_size, ut->name, (int)pad_size, pad_buffer); + if (ut->state == DN_UTState_TestFailed) { + DN_UT_LogF(ut, DN_UT_BAD_COLOR " FAILED"); + } else { + DN_UT_LogF(ut, DN_UT_GOOD_COLOR " OK"); + ut->num_tests_ok_in_group++; + } + DN_UT_LogF(ut, DN_UT_COLOR_RESET "\n"); + ut->state = DN_UTState_Nil; + + // NOTE: Append any test messages (like assertions) into the main output buffer + for (DN_UTStr8Link *it = ut->curr_test_messages->next; it != ut->curr_test_messages; it = ut->curr_test_messages->next) { + // NOTE: Detach + it->next->prev = it->prev; + it->prev->next = it->next; + + // NOTE: Attach + it->next = ut->output; + it->prev = ut->output->prev; + it->next->prev = it; + it->prev->next = it; + } +} + +void DN_UT_LogF(DN_UTCore *ut, char const *fmt, ...) +{ + assert(ut->output && ut->curr_test_messages && "UT was not initialised by calling UT_Init yet"); + va_list args; + va_start(args, fmt); + DN_UTStr8Link *result = DN_UT_AllocStr8LinkFV(fmt, args); + va_end(args); + + result->next = ut->output; + result->prev = ut->output->prev; + result->next->prev = result; + result->prev->next = result; +} + +void DN_UT_LogInsideTestF(DN_UTCore *ut, char const *fmt, ...) +{ + assert(ut->state >= DN_UTState_TestBegun && ""); + va_list args; + va_start(args, fmt); + DN_UTStr8Link *result = DN_UT_AllocStr8LinkFV(fmt, args); + va_end(args); + + result->next = ut->curr_test_messages; + result->prev = ut->curr_test_messages->prev; + result->next->prev = result; + result->prev->next = result; +} + +bool DN_UT_AllTestsPassed(DN_UTCore const *ut) +{ + bool result = ut->num_tests_ok_in_group == ut->num_tests_in_group; + return result; +} + +void DN_UT_PrintTests(DN_UTCore const *ut) +{ + for (DN_UTStr8Link *it = ut->output->next; it != ut->output; it = it->next) + fprintf(stdout, "%.*s", (int)it->size, it->data); + + bool all_clear = DN_UT_AllTestsPassed(ut); + fprintf(stdout, + "%s\n %02d/%02d tests passed -- %s\n\n" DN_UT_COLOR_RESET, + all_clear ? DN_UT_GOOD_COLOR : DN_UT_BAD_COLOR, + ut->num_tests_ok_in_group, + ut->num_tests_in_group, + all_clear ? "OK" : "FAILED"); +} +#endif // DN_UT_IMPLEMENTATION diff --git a/Standalone/dqn_keccak.h b/Standalone/dqn_keccak.h deleted file mode 100644 index b18d62f..0000000 --- a/Standalone/dqn_keccak.h +++ /dev/null @@ -1,636 +0,0 @@ -#if !defined(DN_KECCAK_H) -#define DN_KECCAK_H - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ -// $$ | $$ |$$ _____|$$ __$$\ $$ __$$\ $$ __$$\ $$ | $$ | -// $$ |$$ / $$ | $$ / \__|$$ / \__|$$ / $$ |$$ |$$ / -// $$$$$ / $$$$$\ $$ | $$ | $$$$$$$$ |$$$$$ / -// $$ $$< $$ __| $$ | $$ | $$ __$$ |$$ $$< -// $$ |\$$\ $$ | $$ | $$\ $$ | $$\ $$ | $$ |$$ |\$$\ -// $$ | \$$\ $$$$$$$$\ \$$$$$$ |\$$$$$$ |$$ | $$ |$$ | \$$\ -// \__| \__|\________| \______/ \______/ \__| \__|\__| \__| -// -// dn_keccak.h -- FIPS202 SHA3 + non-finalized SHA3 (aka. Keccak) hashing algorithms -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Implementation of the Keccak hashing algorithms from the Keccak and SHA3 -// families (including the FIPS202 published algorithms and the non-finalized -// ones, i.e. the ones used in Ethereum and Monero which adopted SHA3 before it -// was finalized. The only difference between the 2 is a different delimited -// suffix). -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// MIT License -// -// Copyright (c) 2021 github.com/doy-lee -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\ $$ __$$\\__$$ __|\_$$ _|$$ __$$\ $$$\ $$ |$$ __$$\ -// $$ / $$ |$$ | $$ | $$ | $$ | $$ / $$ |$$$$\ $$ |$$ / \__| -// $$ | $$ |$$$$$$$ | $$ | $$ | $$ | $$ |$$ $$\$$ |\$$$$$$\ -// $$ | $$ |$$ ____/ $$ | $$ | $$ | $$ |$$ \$$$$ | \____$$\ -// $$ | $$ |$$ | $$ | $$ | $$ | $$ |$$ |\$$$ |$$\ $$ | -// $$$$$$ |$$ | $$ | $$$$$$\ $$$$$$ |$$ | \$$ |\$$$$$$ | -// \______/ \__| \__| \______| \______/ \__| \__| \______/ -// -// Options -- Compile time build customisation -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// - Define this in one and only one C++ file to enable the implementation -// code of the header file. -// -// #define DN_KECCAK_IMPLEMENTATION -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -#if !defined(DN_KECCAK_MEMCPY) - #include - #define DN_KECCAK_MEMCPY(dest, src, count) memcpy(dest, src, count) -#endif - -#if !defined(DN_KECCAK_MEMCMP) - #include - #define DN_KECCAK_MEMCMP(dest, src, count) memcmp(dest, src, count) -#endif - -#if !defined(DN_KECCAK_MEMSET) - #include - #define DN_KECCAK_MEMSET(dest, byte, count) memset(dest, byte, count) -#endif - -#if !defined(DN_KECCAK_ASSERT) - #if defined(NDEBUG) - #define DN_KECCAK_ASSERT(expr) - #else - #define DN_KECCAK_ASSERT(expr) \ - do \ - { \ - if (!(expr)) \ - { \ - (*(volatile int *)0) = 0; \ - } \ - } while (0) - #endif -#endif - -// Use this macro in a printf-like function, -/* - DN_KeccakString64 string = {}; - printf("%.*s\n", DN_KECCAK_STRING64_FMT(string)); -*/ -#define DN_KECCAK_STRING56_FMT(string) 56, string -#define DN_KECCAK_STRING64_FMT(string) 64, string -#define DN_KECCAK_STRING96_FMT(string) 96, string -#define DN_KECCAK_STRING128_FMT(string) 128, string - -typedef unsigned char DN_KeccakU8; -typedef unsigned short DN_KeccakU16; -typedef unsigned int DN_KeccakU32; -typedef unsigned int DN_KeccakUint; -#ifdef _MSC_VER - typedef unsigned __int64 DN_KeccakU64; -#else - typedef unsigned long long DN_KeccakU64; -#endif - -typedef struct DN_KeccakBytes28 { char data[28]; } DN_KeccakBytes28; // 224 bit -typedef struct DN_KeccakBytes32 { char data[32]; } DN_KeccakBytes32; // 256 bit -typedef struct DN_KeccakBytes48 { char data[48]; } DN_KeccakBytes48; // 384 bit -typedef struct DN_KeccakBytes64 { char data[64]; } DN_KeccakBytes64; // 512 bit - -typedef struct DN_KeccakString56 { char data[(sizeof(DN_KeccakBytes28) * 2) + 1]; } DN_KeccakString56; -typedef struct DN_KeccakString64 { char data[(sizeof(DN_KeccakBytes32) * 2) + 1]; } DN_KeccakString64; -typedef struct DN_KeccakString96 { char data[(sizeof(DN_KeccakBytes48) * 2) + 1]; } DN_KeccakString96; -typedef struct DN_KeccakString128 { char data[(sizeof(DN_KeccakBytes64) * 2) + 1]; } DN_KeccakString128; - -#define DN_KECCAK_LANE_SIZE_U64 5 -typedef struct DN_KeccakState -{ - DN_KeccakU8 state[DN_KECCAK_LANE_SIZE_U64 * DN_KECCAK_LANE_SIZE_U64 * sizeof(DN_KeccakU64)]; - int state_size; // The number of bytes written to the state - int absorb_size; // The amount of bytes to absorb/sponge in/from the state - int hash_size_bits; // The size of the hash the context was initialised for in bits - char delimited_suffix; // The delimited suffix of the current hash -} DN_KeccakState; - -// NOTE: SHA3/Keccak Streaming API ///////////////////////////////////////////////////////////////// -// Setup a hashing state for either -// - FIPS 202 SHA3 -// - Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6 in SHA3) -// The non-finalized SHA3 version is the one adopted by many cryptocurrencies -// such as Ethereum and Monero as they adopted SHA3 before it was finalized. -// -// The state produced from this function is to be used alongside the -// 'KeccakUpdate' and 'KeccakFinish' functions. -// -// sha3: If true, setup the state for FIPS 202 SHA3, otherwise the non-finalized version -// hash_size_bits: The number of bits to setup the context for, available sizes are 224, 256, 384 and 512. -DN_KeccakState DN_KeccakSHA3Init(bool sha3, int hash_size_bits); - -// After initialising a 'DN_KeccakState' via 'DN_KeccakSHA3Init', iteratively -// update the hash with new data by calling 'DN_KeccakUpdate'. On completion, -// call 'DN_KeccakFinish' to generate the hash from the state. The 'dest_size' -// must be at-least the (bit-size / 8), i.e. for 'DN_Keccak512Init' it must be -// atleast (512 / 8) bytes, 64 bytes. -void DN_KeccakUpdate(DN_KeccakState *keccak, void const *data, DN_KeccakU64 data_size); -void DN_KeccakFinish(DN_KeccakState *keccak, void *dest, int dest_size); - -// NOTE: Simple API //////////////////////////////////////////////////////////////////////////////// -// Helper function that combines the Init, Update and Finish step in one shot, -// i.e. hashing a singlular contiguous buffer. Use the streaming API if data -// is split across different buffers. -void DN_KeccakSHA3Hash(bool sha3, int hash_size_bits, void const *src, DN_KeccakU64 src_size, void *dest, int dest_size); - -#define DN_SHA3Hash(hash_size_bits, src, src_size, dest, dest_size) DN_KeccakSHA3Hash(true /*sha3*/, hash_size_bits, src, src_size, dest, dest_size) -#define DN_SHA3_224(src, src_size, dest, dest_size) DN_SHA3Hash(224, src, src_size, dest, dest_size) -#define DN_SHA3_256(src, src_size, dest, dest_size) DN_SHA3Hash(256, src, src_size, dest, dest_size) -#define DN_SHA3_384(src, src_size, dest, dest_size) DN_SHA3Hash(384, src, src_size, dest, dest_size) -#define DN_SHA3_512(src, src_size, dest, dest_size) DN_SHA3Hash(512, src, src_size, dest, dest_size) - -#define DN_KeccakHash(hash_size_bits, src, src_size, dest, dest_size) DN_KeccakSHA3Hash(false /*sha3*/, hash_size_bits, src, src_size, dest, dest_size) -#define DN_Keccak224(src, src_size, dest, dest_size) DN_KeccakHash(224, src, src_size, dest, dest_size) -#define DN_Keccak256(src, src_size, dest, dest_size) DN_KeccakHash(256, src, src_size, dest, dest_size) -#define DN_Keccak384(src, src_size, dest, dest_size) DN_KeccakHash(384, src, src_size, dest, dest_size) -#define DN_Keccak512(src, src_size, dest, dest_size) DN_KeccakHash(512, src, src_size, dest, dest_size) - -// NOTE: SHA3 Helpers ////////////////////////////////////////////////////////////////////////////// -DN_KeccakBytes28 DN_SHA3_224ToBytes28(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes32 DN_SHA3_256ToBytes32(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes48 DN_SHA3_384ToBytes48(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes64 DN_SHA3_512ToBytes64(void *bytes, DN_KeccakU64 bytes_size); - -// NOTE: Keccak Helpers //////////////////////////////////////////////////////////////////////////// -DN_KeccakBytes28 DN_Keccak224ToBytes28(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes32 DN_Keccak256ToBytes32(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes48 DN_Keccak384ToBytes48(void *bytes, DN_KeccakU64 bytes_size); -DN_KeccakBytes64 DN_Keccak512ToBytes64(void *bytes, DN_KeccakU64 bytes_size); - -#if defined(DN_H) -// NOTE: SHA3 - Helpers for DN data structures //////////////////////////////////////////////////// -DN_KeccakBytes28 DN_SHA3_224StringToBytes28(DN_Str8 string); -DN_KeccakBytes32 DN_SHA3_256StringToBytes32(DN_Str8 string); -DN_KeccakBytes48 DN_SHA3_384StringToBytes48(DN_Str8 string); -DN_KeccakBytes64 DN_SHA3_512StringToBytes64(DN_Str8 string); - -// NOTE: Keccak - Helpers for DN data structures ////////////////////////////////////////////////// -DN_KeccakBytes28 DN_Keccak224StringToBytes28(DN_Str8 string); -DN_KeccakBytes32 DN_Keccak256StringToBytes32(DN_Str8 string); -DN_KeccakBytes48 DN_Keccak384StringToBytes48(DN_Str8 string); -DN_KeccakBytes64 DN_Keccak512StringToBytes64(DN_Str8 string); -#endif // DN_H - -// NOTE: Helper functions ////////////////////////////////////////////////////////////////////////// -// Convert a binary buffer into its hex representation into dest. The dest -// buffer must be large enough to contain the hex representation, i.e. -// atleast src_size * 2). This function does *not* null-terminate the buffer. -void DN_KeccakBytesToHex(void const *src, DN_KeccakU64 src_size, char *dest, DN_KeccakU64 dest_size); - -// Converts a fixed amount of bytes into a hexadecimal string. Helper functions -// that call into DN_KeccakBytesToHex. -// return: The hexadecimal string of the bytes, null-terminated. -DN_KeccakString56 DN_KeccakBytes28ToHex(DN_KeccakBytes28 const *bytes); -DN_KeccakString64 DN_KeccakBytes32ToHex(DN_KeccakBytes32 const *bytes); -DN_KeccakString96 DN_KeccakBytes48ToHex(DN_KeccakBytes48 const *bytes); -DN_KeccakString128 DN_KeccakBytes64ToHex(DN_KeccakBytes64 const *bytes); - -// Compares byte data structures for byte equality (via memcmp). -// return: 1 if the contents are equal otherwise 0. -int DN_KeccakBytes28Equals(DN_KeccakBytes28 const *a, DN_KeccakBytes28 const *b); -int DN_KeccakBytes32Equals(DN_KeccakBytes32 const *a, DN_KeccakBytes32 const *b); -int DN_KeccakBytes48Equals(DN_KeccakBytes48 const *a, DN_KeccakBytes48 const *b); -int DN_KeccakBytes64Equals(DN_KeccakBytes64 const *a, DN_KeccakBytes64 const *b); - -#if defined(DN_H) && defined(DN_WITH_HEX) -// NOTE: Other helper functions for DN data structures //////////////////////////////////////////// -// Converts a 64 character hex string into the 32 byte binary representation. -// Invalid hex characters in the string will be represented as 0. -// hex: Must be exactly a 64 character hex string. -DN_KeccakBytes32 DN_KeccakHex64StringToBytes(DN_Str8 hex); -#endif // DN_H && DN_WITH_HEX -#endif // DN_KECCAK_H - -#if defined(DN_KECCAK_IMPLEMENTATION) -DN_KeccakU64 const DN_KECCAK_ROUNDS[] = { - 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B, - 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088, - 0x0000000080008009, 0x000000008000000A, 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A, - 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, -}; - -DN_KeccakU64 const DN_KECCAK_ROTATIONS[][5] = -{ - {0, 36, 3, 41, 18}, - {1, 44, 10, 45, 2}, - {62, 6, 43, 15, 61}, - {28, 55, 25, 21, 56}, - {27, 20, 39, 8, 14}, -}; - -#define DN_KECCAK_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate))))) - -static void DN_Keccak__Permute(void *state) -{ - // TODO(dn): Do some profiling on unrolling and can we SIMD some part of - // this? Unroll loop, look at data dependencies and investigate. - - DN_KeccakU64 *lanes_u64 = (DN_KeccakU64 *)state; - for (int round_index = 0; round_index < 24; round_index++) - { - #define LANE_INDEX(x, y) ((x) + ((y) * DN_KECCAK_LANE_SIZE_U64)) - // ?? step ////////////////////////////////////////////////////////////////////////////////// -#if 1 - DN_KeccakU64 c[DN_KECCAK_LANE_SIZE_U64]; - for (int x = 0; x < DN_KECCAK_LANE_SIZE_U64; x++) - { - c[x] = lanes_u64[LANE_INDEX(x, 0)] ^ - lanes_u64[LANE_INDEX(x, 1)] ^ - lanes_u64[LANE_INDEX(x, 2)] ^ - lanes_u64[LANE_INDEX(x, 3)] ^ - lanes_u64[LANE_INDEX(x, 4)]; - } - - DN_KeccakU64 d[DN_KECCAK_LANE_SIZE_U64]; - for (int x = 0; x < DN_KECCAK_LANE_SIZE_U64; x++) - d[x] = c[(x + 4) % DN_KECCAK_LANE_SIZE_U64] ^ DN_KECCAK_ROL64(c[(x + 1) % DN_KECCAK_LANE_SIZE_U64], 1); - - for (int y = 0; y < DN_KECCAK_LANE_SIZE_U64; y++) - for (int x = 0; x < DN_KECCAK_LANE_SIZE_U64; x++) - lanes_u64[LANE_INDEX(x, y)] ^= d[x]; -#else - DN_KeccakU64 c[5], d[5]; - c[0] = lanes_u64[0 * 5 + 0] ^ lanes_u64[1 * 5 + 0] ^ lanes_u64[2 * 5 + 0] ^ lanes_u64[3 * 5 + 0] ^ lanes_u64[4 * 5 + 0]; - c[1] = lanes_u64[0 * 5 + 1] ^ lanes_u64[1 * 5 + 1] ^ lanes_u64[2 * 5 + 1] ^ lanes_u64[3 * 5 + 1] ^ lanes_u64[4 * 5 + 1]; - c[2] = lanes_u64[0 * 5 + 2] ^ lanes_u64[1 * 5 + 2] ^ lanes_u64[2 * 5 + 2] ^ lanes_u64[3 * 5 + 2] ^ lanes_u64[4 * 5 + 2]; - c[3] = lanes_u64[0 * 5 + 3] ^ lanes_u64[1 * 5 + 3] ^ lanes_u64[2 * 5 + 3] ^ lanes_u64[3 * 5 + 3] ^ lanes_u64[4 * 5 + 3]; - c[4] = lanes_u64[0 * 5 + 4] ^ lanes_u64[1 * 5 + 4] ^ lanes_u64[2 * 5 + 4] ^ lanes_u64[3 * 5 + 4] ^ lanes_u64[4 * 5 + 4]; - - d[0] = c[4] ^ DN_KECCAK_ROL64(c[1], 1); - d[1] = c[0] ^ DN_KECCAK_ROL64(c[2], 1); - d[2] = c[1] ^ DN_KECCAK_ROL64(c[3], 1); - d[3] = c[2] ^ DN_KECCAK_ROL64(c[4], 1); - d[4] = c[3] ^ DN_KECCAK_ROL64(c[0], 1); -#endif - - // ?? and ?? steps /////////////////////////////////////////////////////////////////////////// - DN_KeccakU64 b[DN_KECCAK_LANE_SIZE_U64 * DN_KECCAK_LANE_SIZE_U64]; - for (int y = 0; y < DN_KECCAK_LANE_SIZE_U64; y++) - { - for (int x = 0; x < DN_KECCAK_LANE_SIZE_U64; x++) - { - DN_KeccakU64 lane = lanes_u64[LANE_INDEX(x, y)]; - DN_KeccakU64 rotate_count = DN_KECCAK_ROTATIONS[x][y]; - b[LANE_INDEX(y, (2 * x + 3 * y) % 5)] = DN_KECCAK_ROL64(lane, rotate_count); - - } - } - - // ?? step ////////////////////////////////////////////////////////////////////////////////// - for (int y = 0; y < DN_KECCAK_LANE_SIZE_U64; y++) - { - for (int x = 0; x < DN_KECCAK_LANE_SIZE_U64; x++) - { - DN_KeccakU64 rhs = ~b[LANE_INDEX((x + 1) % 5, y)] & - b[LANE_INDEX((x + 2) % 5, y)]; - - lanes_u64[LANE_INDEX(x, y)] = b[LANE_INDEX(x, y)] ^ rhs; - } - } - - // ?? step ////////////////////////////////////////////////////////////////////////////////// - lanes_u64[LANE_INDEX(0, 0)] ^= DN_KECCAK_ROUNDS[round_index]; - #undef LANE_INDEX - #undef DN_KECCAK_ROL64 - } -} - -// NOTE: Streaming API ///////////////////////////////////////////////////////////////////////////// -DN_KeccakState DN_KeccakSHA3Init(bool sha3, int hash_size_bits) -{ - char const SHA3_DELIMITED_SUFFIX = 0x06; - char const KECCAK_DELIMITED_SUFFIX = 0x01; - int const bitrate = 1600 - (hash_size_bits * 2); - -#if defined(__cplusplus) - DN_KeccakState result = {}; -#else - DN_KeccakState result = {0}; -#endif - result.hash_size_bits = hash_size_bits; - result.absorb_size = bitrate / 8; - result.delimited_suffix = sha3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX; - DN_KECCAK_ASSERT(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600); - return result; -} - -void DN_KeccakUpdate(DN_KeccakState *keccak, void const *data, DN_KeccakU64 data_size) -{ - DN_KeccakU8 *state = keccak->state; - DN_KeccakU8 const *ptr = (DN_KeccakU8 *)data; - DN_KeccakU64 ptr_size = data_size; - while (ptr_size > 0) - { - DN_KeccakU64 space = keccak->absorb_size - keccak->state_size; - int bytes_to_absorb = (int)(space < ptr_size ? space : ptr_size); - - for (int index = 0; index < bytes_to_absorb; index++) - state[keccak->state_size + index] ^= ptr[index]; - - ptr += bytes_to_absorb; - keccak->state_size += bytes_to_absorb; - ptr_size -= bytes_to_absorb; - - if (keccak->state_size >= keccak->absorb_size) - { - DN_KECCAK_ASSERT(keccak->state_size == keccak->absorb_size); - DN_Keccak__Permute(state); - keccak->state_size = 0; - } - } -} - -void DN_KeccakFinish(DN_KeccakState *keccak, void *dest, int dest_size) -{ - DN_KECCAK_ASSERT(dest_size >= keccak->hash_size_bits / 8); - - // Sponge Finalization Step: Final padding bit ///////////////////////////////////////////////// - int const INDEX_OF_0X80_BYTE = keccak->absorb_size - 1; - int const delimited_suffix_index = keccak->state_size; - DN_KECCAK_ASSERT(delimited_suffix_index < keccak->absorb_size); - - DN_KeccakU8 *state = keccak->state; - state[delimited_suffix_index] ^= keccak->delimited_suffix; - - // NOTE: In the reference implementation, it checks that if the - // delimited suffix is set to the padding bit (0x80), then we need to - // permute twice. Once for the delimited suffix, and a second time for - // the "padding" permute. - // - // However all standard algorithms either specify a 0x01, or 0x06, 0x04 - // delimited suffix and so forth- so this case is never hit. We can omit - // this from the implementation here. - - state[INDEX_OF_0X80_BYTE] ^= 0x80; - DN_Keccak__Permute(state); - - // Squeeze Step: Squeeze bytes from the state into our hash //////////////////////////////////// - DN_KeccakU8 *dest_u8 = (DN_KeccakU8 *)dest; - int const squeeze_count = dest_size / keccak->absorb_size; - int squeeze_index = 0; - for (; squeeze_index < squeeze_count; squeeze_index++) - { - if (squeeze_index) DN_Keccak__Permute(state); - DN_KECCAK_MEMCPY(dest_u8, state, keccak->absorb_size); - dest_u8 += keccak->absorb_size; - } - - // Squeeze Finalisation Step: Remainder bytes in hash ////////////////////////////////////////// - int const remainder = dest_size % keccak->absorb_size; - if (remainder) - { - if (squeeze_index) DN_Keccak__Permute(state); - DN_KECCAK_MEMCPY(dest_u8, state, remainder); - } -} - -void DN_KeccakSHA3Hash(bool sha3, int hash_size_bits, void const *src, DN_KeccakU64 src_size, void *dest, int dest_size) -{ - DN_KeccakState keccak = DN_KeccakSHA3Init(sha3, hash_size_bits); - DN_KeccakUpdate(&keccak, src, src_size); - DN_KeccakFinish(&keccak, dest, dest_size); -} - -// NOTE: SHA3 Helpers ////////////////////////////////////////////////////////////////////////////// -DN_KeccakBytes28 DN_SHA3_224ToBytes28(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes28 result; - DN_SHA3_224(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes32 DN_SHA3_256ToBytes32(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes32 result; - DN_SHA3_256(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes48 DN_SHA3_384ToBytes48(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes48 result; - DN_SHA3_384(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes64 DN_SHA3_512ToBytes64(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes64 result; - DN_SHA3_512(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -// NOTE: Keccak Helpers //////////////////////////////////////////////////////////////////////////// -DN_KeccakBytes28 DN_Keccak224ToBytes28(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes28 result; - DN_Keccak224(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes32 DN_Keccak256ToBytes32(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes32 result; - DN_Keccak256(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes48 DN_Keccak384ToBytes48(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes48 result; - DN_Keccak384(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes64 DN_Keccak512ToBytes64(void *bytes, DN_KeccakU64 bytes_size) -{ - DN_KeccakBytes64 result; - DN_Keccak512(bytes, bytes_size, result.data, sizeof(result)); - return result; -} - - -#if defined(DN_H) -// NOTE: SHA3 - Helpers for DN data structures //////////////////////////////////////////////////// -DN_KeccakBytes28 DN_SHA3_224StringToBytes28(DN_Str8 string) -{ - DN_KeccakBytes28 result; - DN_SHA3_224(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes32 DN_SHA3_256StringToBytes32(DN_Str8 string) -{ - DN_KeccakBytes32 result; - DN_SHA3_256(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes48 DN_SHA3_384StringToBytes48(DN_Str8 string) -{ - DN_KeccakBytes48 result; - DN_SHA3_384(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes64 DN_SHA3_512StringToBytes64(DN_Str8 string) -{ - DN_KeccakBytes64 result; - DN_SHA3_512(string.data, string.size, result.data, sizeof(result)); - return result; -} -#endif // DN_H - -#if defined(DN_H) -// NOTE: Keccak - Helpers for DN data structures ////////////////////////////////////////////////// -DN_KeccakBytes28 DN_Keccak224StringToBytes28(DN_Str8 string) -{ - DN_KeccakBytes28 result; - DN_Keccak224(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes32 DN_Keccak256StringToBytes32(DN_Str8 string) -{ - DN_KeccakBytes32 result; - DN_Keccak256(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes48 DN_Keccak384StringToBytes48(DN_Str8 string) -{ - DN_KeccakBytes48 result; - DN_Keccak384(string.data, string.size, result.data, sizeof(result)); - return result; -} - -DN_KeccakBytes64 DN_Keccak512StringToBytes64(DN_Str8 string) -{ - DN_KeccakBytes64 result; - DN_Keccak512(string.data, string.size, result.data, sizeof(result)); - return result; -} -#endif // DN_H - -// NOTE: Helper functions ////////////////////////////////////////////////////////////////////////// -void DN_KeccakBytesToHex(void const *src, DN_KeccakU64 src_size, char *dest, DN_KeccakU64 dest_size) -{ - (void)src_size; (void)dest_size; - DN_KECCAK_ASSERT(dest_size >= src_size * 2); - - unsigned char *src_u8 = (unsigned char *)src; - for (DN_KeccakU64 src_index = 0, dest_index = 0; - src_index < src_size; - src_index += 1, dest_index += 2) - { - char byte = src_u8[src_index]; - char hex01 = (byte >> 4) & 0b1111; - char hex02 = (byte >> 0) & 0b1111; - dest[dest_index + 0] = hex01 < 10 ? (hex01 + '0') : (hex01 - 10) + 'a'; - dest[dest_index + 1] = hex02 < 10 ? (hex02 + '0') : (hex02 - 10) + 'a'; - } -} - -DN_KeccakString56 DN_KeccakBytes28ToHex(DN_KeccakBytes28 const *bytes) -{ - DN_KeccakString56 result; - DN_KeccakBytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_KeccakString64 DN_KeccakBytes32ToHex(DN_KeccakBytes32 const *bytes) -{ - DN_KeccakString64 result; - DN_KeccakBytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_KeccakString96 DN_KeccakBytes48ToHex(DN_KeccakBytes48 const *bytes) -{ - DN_KeccakString96 result; - DN_KeccakBytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_KeccakString128 DN_KeccakBytes64ToHex(DN_KeccakBytes64 const *bytes) -{ - DN_KeccakString128 result; - DN_KeccakBytesToHex(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -int DN_KeccakBytes28Equals(DN_KeccakBytes28 const *a, DN_KeccakBytes28 const *b) -{ - int result = DN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -int DN_KeccakBytes32Equals(DN_KeccakBytes32 const *a, DN_KeccakBytes32 const *b) -{ - int result = DN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -int DN_KeccakBytes48Equals(DN_KeccakBytes48 const *a, DN_KeccakBytes48 const *b) -{ - int result = DN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -int DN_KeccakBytes64Equals(DN_KeccakBytes64 const *a, DN_KeccakBytes64 const *b) -{ - int result = DN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -#if defined(DN_H) && defined(DN_WITH_HEX) -// NOTE: Other helper functions for DN data structures //////////////////////////////////////////// -DN_KeccakBytes32 DN_KeccakHex64StringToBytes(DN_Str8 hex) -{ - DN_KECCAK_ASSERT(hex.size == 64); - DN_KeccakBytes32 result; - DN_Hex_CString8ToByteBuffer(hex.data, hex.size, result.data, sizeof(result)); - return result; -} -#endif // DN_H && DN_WITH_HEX -#endif // DN_KECCAK_IMPLEMENTATION diff --git a/Standalone/dqn_utest.h b/Standalone/dqn_utest.h deleted file mode 100644 index 0057294..0000000 --- a/Standalone/dqn_utest.h +++ /dev/null @@ -1,230 +0,0 @@ -#if !defined(DN_UTEST_H) -#define DN_UTEST_H - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$$\ -// $$ | $$ |\__$$ __|$$ _____|$$ __$$\\__$$ __| -// $$ | $$ | $$ | $$ | $$ / \__| $$ | -// $$ | $$ | $$ | $$$$$\ \$$$$$$\ $$ | -// $$ | $$ | $$ | $$ __| \____$$\ $$ | -// $$ | $$ | $$ | $$ | $$\ $$ | $$ | -// \$$$$$$ | $$ | $$$$$$$$\ \$$$$$$ | $$ | -// \______/ \__| \________| \______/ \__| -// -// dn_utest.h -- Extremely minimal unit testing framework -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// A super minimal testing framework, most of the logic here is the pretty -// printing of test results. -// -// NOTE: Configuration ///////////////////////////////////////////////////////////////////////////// -// -// #define DN_UTEST_IMPLEMENTATION -// Define this in one and only one C++ file to enable the implementation -// code of the header file. This will also automatically enable the JSMN -// implementation. -// -// #define DN_UTEST_RESULT_LPAD -// Define this to a number to specify how much to pad the output of the test -// result line before the test result is printed. -// -// #define DN_UTEST_RESULT_PAD_CHAR -// Define this to a character to specify the default character to use for -// padding. By default this is '.' -// -// #define DN_UTEST_SPACING -// Define this to a number to specify the number of spaces between the group -// declaration and the test output in the group. -// -// #define DN_UTEST_BAD_COLOR -// Define this to a terminal color code to specify what color errors will be -// presented as. -// -// #define DN_UTEST_GOOD_COLOR -// Define this to a terminal color code to specify what color sucess will be -// presented as. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: Macros //////////////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include - -#if !defined(DN_UTEST_RESULT_LPAD) - #define DN_UTEST_RESULT_LPAD 90 -#endif - -#if !defined(DN_UTEST_RESULT_PAD_CHAR) - #define DN_UTEST_RESULT_PAD_CHAR '.' -#endif - -#if !defined(DN_UTEST_SPACING) - #define DN_UTEST_SPACING 2 -#endif - -#if !defined(DN_UTEST_BAD_COLOR) - #define DN_UTEST_BAD_COLOR "\x1b[31m" -#endif - -#if !defined(DN_UTEST_GOOD_COLOR) - #define DN_UTEST_GOOD_COLOR "\x1b[32m" -#endif - -#define DN_UTEST_COLOR_RESET "\x1b[0m" - -#define DN_UTEST_GROUP(test, fmt, ...) \ - for (DN_UTest *test_var_ = (printf(fmt "\n", ## __VA_ARGS__), &test); \ - test_var_ != nullptr; \ - DN_UTest_PrintStats(&test), test_var_ = nullptr) - -#define DN_UTEST_TEST(fmt, ...) \ - for (int dummy_ = (DN_UTest_Begin(test_var_, fmt, ## __VA_ARGS__), 0); \ - (void)dummy_, test_var_->state == DN_UTestState_TestBegun; \ - DN_UTest_End(test_var_)) - -#define DN_UTEST_ASSERTF(test, expr, fmt, ...) \ - DN_UTEST_ASSERTF_AT((test), __FILE__, __LINE__, (expr), fmt, ##__VA_ARGS__) - -#define DN_UTEST_ASSERT(test, expr) \ - DN_UTEST_ASSERT_AT((test), __FILE__, __LINE__, (expr)) - -// TODO: Fix the logs. They print before the tests, we should accumulate logs -// per test, then, dump them on test on. But to do this nicely without crappy -// mem management we need to implement an arena. -#define DN_UTEST_LOG(fmt, ...) \ - fprintf(stdout, "%*s" fmt "\n", DN_UTEST_SPACING * 2, "", ##__VA_ARGS__) - -#define DN_UTEST_ASSERTF_AT(test, file, line, expr, fmt, ...) \ - do { \ - if (!(expr)) { \ - (test)->state = DN_UTestState_TestFailed; \ - fprintf(stderr, \ - "%*sAssertion Triggered\n" \ - "%*sFile: %s:%d\n" \ - "%*sExpression: [" #expr "]\n" \ - "%*sReason: " fmt "\n\n", \ - DN_UTEST_SPACING * 2, \ - "", \ - DN_UTEST_SPACING * 3, \ - "", \ - file, \ - line, \ - DN_UTEST_SPACING * 3, \ - "", \ - DN_UTEST_SPACING * 3, \ - "", \ - ##__VA_ARGS__); \ - } \ - } while (0) - -#define DN_UTEST_ASSERT_AT(test, file, line, expr) \ - do { \ - if (!(expr)) { \ - (test)->state = DN_UTestState_TestFailed; \ - fprintf(stderr, \ - "%*sFile: %s:%d\n" \ - "%*sExpression: [" #expr "]\n\n", \ - DN_UTEST_SPACING * 2, \ - "", \ - file, \ - line, \ - DN_UTEST_SPACING * 2, \ - ""); \ - } \ - } while (0) - -// NOTE: Header //////////////////////////////////////////////////////////////////////////////////// -typedef enum DN_UTestState { - DN_UTestState_Nil, - DN_UTestState_TestBegun, - DN_UTestState_TestFailed, -} DN_UTestState; - -typedef struct DN_UTest { - int num_tests_in_group; - int num_tests_ok_in_group; - DN_UTestState state; - bool finished; - char name[256]; - size_t name_size; -} DN_UTest; - -void DN_UTest_PrintStats(DN_UTest *test); -void DN_UTest_BeginV(DN_UTest *test, char const *fmt, va_list args); -void DN_UTest_Begin(DN_UTest *test, char const *fmt, ...); -void DN_UTest_End(DN_UTest *test); -#endif // DN_UTEST_H - -// NOTE: Implementation //////////////////////////////////////////////////////////////////////////// -#if defined(DN_UTEST_IMPLEMENTATION) -void DN_UTest_PrintStats(DN_UTest *test) -{ - if (test->finished) - return; - - test->finished = true; - bool all_clear = test->num_tests_ok_in_group == test->num_tests_in_group; - fprintf(stdout, - "%s\n %02d/%02d tests passed -- %s\n\n" DN_UTEST_COLOR_RESET, - all_clear ? DN_UTEST_GOOD_COLOR : DN_UTEST_BAD_COLOR, - test->num_tests_ok_in_group, - test->num_tests_in_group, - all_clear ? "OK" : "FAILED"); -} - -void DN_UTest_BeginV(DN_UTest *test, char const *fmt, va_list args) -{ - assert(test->state == DN_UTestState_Nil && - "Nesting a unit test within another unit test is not allowed, ensure" - "the first test has finished by calling DN_UTest_End"); - - test->num_tests_in_group++; - test->state = DN_UTestState_TestBegun; - - test->name_size = 0; - { - va_list args_copy; - va_copy(args_copy, args); - test->name_size = vsnprintf(NULL, 0, fmt, args_copy); - va_end(args_copy); - } - - assert(test->name_size < sizeof(test->name)); - vsnprintf(test->name, sizeof(test->name), fmt, args); -} - -void DN_UTest_Begin(DN_UTest *test, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_UTest_BeginV(test, fmt, args); - va_end(args); -} - -void DN_UTest_End(DN_UTest *test) -{ - assert(test->state != DN_UTestState_Nil && "Test was marked as ended but a test was never commenced using DN_UTest_Begin"); - size_t pad_size = DN_UTEST_RESULT_LPAD - (DN_UTEST_SPACING + test->name_size); - if (pad_size < 0) - pad_size = 0; - - char pad_buffer[DN_UTEST_RESULT_LPAD] = {}; - memset(pad_buffer, DN_UTEST_RESULT_PAD_CHAR, pad_size); - - printf("%*s%.*s%.*s", DN_UTEST_SPACING, "", (int)test->name_size, test->name, (int)pad_size, pad_buffer); - if (test->state == DN_UTestState_TestFailed) { - printf(DN_UTEST_BAD_COLOR " FAILED"); - } else { - printf(DN_UTEST_GOOD_COLOR " OK"); - test->num_tests_ok_in_group++; - } - printf(DN_UTEST_COLOR_RESET "\n"); - test->state = DN_UTestState_Nil; -} -#endif // DN_UTEST_IMPLEMENTATION diff --git a/_clang-format b/_clang-format index 4e5604a..ea0f0f3 100644 --- a/_clang-format +++ b/_clang-format @@ -1,9 +1,8 @@ ---- -IndentWidth: 4 -TabWidth: 4 ---- Language: Cpp +IndentWidth: 2 +TabWidth: 2 + # Align parameters on the open bracket, e.g.: # someLongFunction(argument1, # argument2); @@ -27,10 +26,10 @@ AlignArrayOfStructures: Left # int d = 3; # /* A comment. */ # double e = 4; -AlignConsecutiveAssignments: Consecutive -AlignConsecutiveBitFields: Consecutive +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveBitFields: Consecutive AlignConsecutiveDeclarations: Consecutive -AlignConsecutiveMacros: Consecutive +AlignConsecutiveMacros: Consecutive # Align escaped newlines as far left as possible. # #define A \ @@ -135,7 +134,7 @@ AlwaysBreakTemplateDeclarations: MultiLine # aaaaaaaaaaaaaaaaaaaa, # aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa); # } -BinPackArguments: false +BinPackArguments: false BinPackParameters: false # As BinPackArguments but for function definition parameters # Add space after the : only (space may be added before if needed for @@ -154,9 +153,9 @@ BitFieldColonSpacing: After # ccccccccccccccccccccccccccccccccccccccccc; BreakBeforeBinaryOperators: None -# Always attach braces to surrounding context, but break before braces on -# function, namespace and class definitions. -BreakBeforeBraces: Linux +# BS_Attach (in configuration: Attach) Always attach braces to surrounding context. +# BS_Mozilla (in configuration: Mozilla) Like Attach, but break before braces on enum, function, and record definitions. +BreakBeforeBraces: Mozilla # true: # template @@ -197,7 +196,7 @@ BreakInheritanceList: AfterComma # "ryVeryVeryVeryVeryVery" # "VeryLongString"; BreakStringLiterals: true -ColumnLimit: 100 +ColumnLimit: 0 # false: # namespace Foo { diff --git a/build.bat b/build.bat index e9ce6f1..f83c426 100644 --- a/build.bat +++ b/build.bat @@ -13,15 +13,15 @@ pushd Build REM O2 Optimisation Level 2 REM Oi Use CPU Intrinsics REM Z7 Combine multi-debug files to one debug file - set common_flags=-D DN_UNIT_TESTS_WITH_KECCAK -D DN_USE_STD_PRINTF %script_dir%\dqn_unit_tests_main.cpp + set common_flags=-D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Extra\dn_tests_main.cpp set msvc_driver_flags=%common_flags% -MT -EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo REM Optionally pass `-analyze` to `msvc_compile_flags` for more checks, but, REM it slows down compilation by around 5s on my old laptop. - set msvc_compile_flags=%msvc_driver_flags% -analyze -fsanitize=address /Fe:dqn_unit_tests_msvc - set clang_compile_flags=%msvc_driver_flags% -fsanitize=address -fsanitize=undefined /Fe:dqn_unit_tests_clang - set zig_compile_flags=%common_flags% -fsanitize=address -fsanitize=undefined -o dqn_unit_tests_zig + set msvc_compile_flags=%msvc_driver_flags% -analyze -fsanitize=address /Fe:dn_unit_tests_msvc + set clang_compile_flags=%msvc_driver_flags% -fsanitize=address -fsanitize=undefined /Fe:dn_unit_tests_clang + set zig_compile_flags=%common_flags% -fsanitize=address -fsanitize=undefined -o dn_unit_tests_zig set msvc_link_flags=-link set clang_link_flags=%msvc_link_flags% @@ -45,7 +45,7 @@ pushd Build ) REM zig ======================================================================================== - REM TODO(doyle):Can't build "Misc\dqn_unit_tests.cpp|1 col 1| error: unable to build C object: FileNotFound" + REM TODO(doyle):Can't build "Misc\dn_unit_tests.cpp|1 col 1| error: unable to build C object: FileNotFound" REM set has_zig=1 REM where /q zig || set has_zig=0 REM if %has_zig% == 1 ( diff --git a/build.sh b/build.sh index a4505c7..b9fffb4 100755 --- a/build.sh +++ b/build.sh @@ -11,9 +11,9 @@ pushd Build -Werror \ -fsanitize=address \ -std=c++17 \ - -D DQN_UNIT_TESTS_WITH_MAIN \ - -D DQN_UNIT_TESTS_WITH_KECCAK \ - -x ${code_dir}/dqn.cpp \ + -D DN_UNIT_TESTS_WITH_MAIN \ + -D DN_UNIT_TESTS_WITH_KECCAK \ + -x ${code_dir}/dn.cpp \ -g \ - -o dqn_unit_tests + -o dn_unit_tests popd diff --git a/dn_base_inc.cpp b/dn_base_inc.cpp new file mode 100644 index 0000000..158e4aa --- /dev/null +++ b/dn_base_inc.cpp @@ -0,0 +1,6 @@ +#include "Base/dn_base.cpp" +#include "Base/dn_base_containers.cpp" +#include "Base/dn_base_convert.cpp" +#include "Base/dn_base_mem.cpp" +#include "Base/dn_base_string.cpp" +#include "Base/dn_base_log.cpp" diff --git a/dn_base_inc.h b/dn_base_inc.h new file mode 100644 index 0000000..3d8f9c6 --- /dev/null +++ b/dn_base_inc.h @@ -0,0 +1,14 @@ +#if !defined(DN_BASE_INC_H) +#define DN_BASE_INC_H + +#include "Base/dn_base_compiler.h" +#include "Base/dn_base.h" +#include "Base/dn_base_os.h" +#include "Base/dn_base_assert.h" +#include "Base/dn_base_mem.h" +#include "Base/dn_base_log.h" +#include "Base/dn_base_string.h" +#include "Base/dn_base_containers.h" +#include "Base/dn_base_convert.h" + +#endif // !defined(DN_BASE_INC_H) diff --git a/dn_clangd.h b/dn_clangd.h new file mode 100644 index 0000000..fe81844 --- /dev/null +++ b/dn_clangd.h @@ -0,0 +1,31 @@ +#if defined(_CLANDG) +#include "dn_base_inc.h" +#include "dn_core_inc.h" +#include "dn_os_inc.h" + +#include "Standalone/dn_cpp_file.h" +#include "Standalone/dn_keccak.h" +#include "Standalone/dn_utest.h" + +#include "Extra/dn_cgen.h" +#include "Extra/dn_csv.h" +#include "Extra/dn_hash.h" +#include "Extra/dn_helpers.h" +#include "Extra/dn_json.h" +#include "Extra/dn_math.h" +#include "Extra/dn_type_info.h" + +#include "Extra/dn_cgen.cpp" +#include "Extra/dn_csv.cpp" +#include "Extra/dn_hash.cpp" +#include "Extra/dn_helpers.cpp" +#include "Extra/dn_json.cpp" +#include "Extra/dn_math.cpp" +#include "Extra/dn_tests.cpp" +#include "Extra/dn_type_info.cpp" + +#include "dn_base_inc.cpp" +#include "dn_core_inc.cpp" +#include "dn_os_inc.cpp" +#endif + diff --git a/dn_core_inc.cpp b/dn_core_inc.cpp new file mode 100644 index 0000000..c0944f2 --- /dev/null +++ b/dn_core_inc.cpp @@ -0,0 +1,3 @@ +#include "Core/dn_core.cpp" +#include "Core/dn_core_debug.cpp" +#include "Core/dn_core_demo.cpp" diff --git a/dn_core_inc.h b/dn_core_inc.h new file mode 100644 index 0000000..f5aeaef --- /dev/null +++ b/dn_core_inc.h @@ -0,0 +1,7 @@ +#if !defined(DN_CORE_INC_H) +#define DN_CORE_INC_H + +#include "Core/dn_core_debug.h" +#include "Core/dn_core.h" + +#endif // !defined(DN_CORE_INC_H) diff --git a/dn_os_inc.cpp b/dn_os_inc.cpp new file mode 100644 index 0000000..0e4b269 --- /dev/null +++ b/dn_os_inc.cpp @@ -0,0 +1,14 @@ +#include "OS/dn_os_tls.cpp" +#include "OS/dn_os.cpp" +#include "OS/dn_os_allocator.cpp" +#include "OS/dn_os_containers.cpp" +#include "OS/dn_os_print.cpp" +#include "OS/dn_os_string.cpp" + +#if defined(DN_PLATFORM_POSIX) + #include "OS/dn_os_posix.cpp" +#elif defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_win32.cpp" +#else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs +#endif diff --git a/dn_os_inc.h b/dn_os_inc.h new file mode 100644 index 0000000..3df6507 --- /dev/null +++ b/dn_os_inc.h @@ -0,0 +1,13 @@ +#include "OS/dn_os_tls.h" +#include "OS/dn_os.h" +#include "OS/dn_os_allocator.h" +#include "OS/dn_os_containers.h" +#include "OS/dn_os_print.h" +#include "OS/dn_os_string.h" + +#if defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_windows.h" + #include "OS/dn_os_win32.h" +#elif defined(DN_PLATFORM_POSIX) + #include "OS/dn_os_posix.h" +#endif diff --git a/dqn.cpp b/dqn.cpp deleted file mode 100644 index e803c97..0000000 --- a/dqn.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "dqn.h" - -#define DN_CPP - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// /$$$$$$\ $$\ $$\ $$$$$$$\ $$\ -// \_$$ _|$$$\ $$$ |$$ __$$\ $$ | -// $$ | $$$$\ $$$$ |$$ | $$ |$$ | -// $$ | $$\$$\$$ $$ |$$$$$$$ |$$ | -// $$ | $$ \$$$ $$ |$$ ____/ $$ | -// $$ | $$ |\$ /$$ |$$ | $$ | -// $$$$$$\ $$ | \_/ $$ |$$ | $$$$$$$$\ -// \______|\__| \__|\__| \________| -// -// Implementation -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -#if defined(DN_WITH_CGEN) - #if !defined(DN_NO_METADESK) - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4505) // warning C4505: '': unreferenced function with internal linkage has been removed - - DN_GCC_WARNING_PUSH - DN_GCC_WARNING_DISABLE(-Wwrite-strings) - DN_GCC_WARNING_DISABLE(-Wunused-but-set-variable) - DN_GCC_WARNING_DISABLE(-Wsign-compare) - DN_GCC_WARNING_DISABLE(-Wunused-function) - DN_GCC_WARNING_DISABLE(-Wunused-result) - - #include "External/metadesk/md.c" - - DN_GCC_WARNING_POP - DN_MSVC_WARNING_POP - #endif - #define DN_CPP_FILE_IMPLEMENTATION - #include "Standalone/dqn_cpp_file.h" - #include "dqn_cgen.cpp" -#endif - -#if defined(DN_WITH_JSON) - #include "dqn_json.cpp" -#endif - -#include "dqn_base.cpp" -#include "dqn_external.cpp" -#include "dqn_allocator.cpp" -#include "dqn_debug.cpp" -#include "dqn_string.cpp" -#include "dqn_containers.cpp" -#include "dqn_type_info.cpp" -#include "dqn_os.cpp" - -#if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_ARM64) - #include "dqn_os_posix.cpp" -#elif defined(DN_PLATFORM_WIN32) - #include "dqn_os_win32.cpp" -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif - -#include "dqn_tls.cpp" -#include "dqn_math.cpp" -#include "dqn_hash.cpp" -#include "dqn_helpers.cpp" - -#if defined(DN_WITH_UNIT_TESTS) - #include "dqn_unit_tests.cpp" -#endif - -#include "dqn_docs.cpp" diff --git a/dqn.h b/dqn.h deleted file mode 100644 index 8b97631..0000000 --- a/dqn.h +++ /dev/null @@ -1,354 +0,0 @@ -#pragma once -#define DN_H - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$\ $$\ $$\ -// $$ __$$\ $$ __$$\ $$$\ $$ | -// $$ | $$ |$$ / $$ |$$$$\ $$ | -// $$ | $$ |$$ | $$ |$$ $$\$$ | -// $$ | $$ |$$ | $$ |$$ \$$$$ | -// $$ | $$ |$$ $$\$$ |$$ |\$$$ | -// $$$$$$$ |\$$$$$$ / $$ | \$$ | -// \_______/ \___$$$\ \__| \__| -// \___| -// -// dqn.h -- Personal standard library -- MIT License -- git.doylet.dev/dn -// ASCII -- BigMoney-NW by Nathan Bloomfild -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// This library is a single-header file-esque library with inspiration taken -// from STB libraries for ease of integration and use. It defines a bunch of -// primitives and standard library functions that are missing and or more -// appropriate for development in modern day computing (e.g. allocator -// first-class APIs, a 64bit MMU and in general non-pessimized APIs that aren't -// constrained by the language specification and operate closer to the OS). -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\ $$ _____|\__$$ __|\__$$ __|\_$$ _|$$$\ $$ |$$ __$$\ -// $$ / \__|$$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__| -// $$ |$$$$\ $$$$$\ $$ | $$ | $$ | $$ $$\$$ |$$ |$$$$\ -// $$ |\_$$ |$$ __| $$ | $$ | $$ | $$ \$$$$ |$$ |\_$$ | -// $$ | $$ |$$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | -// \$$$$$$ |$$$$$$$$\ $$ | $$ | $$$$$$\ $$ | \$$ |\$$$$$$ | -// \______/ \________| \__| \__| \______|\__| \__| \______/ -// -// Getting started -- Compiling with the library and library documentation -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// -- Compiling -- -// -// Compile dn.cpp or include it into one of your translation units. -// -// Additionally, this library supports including/excluding specific sections -// of the library by using #define on the name of the section. These names are -// documented in the section table of contents at the #define column, for -// example: -// -// #define DN_ONLY_VARRAY -// #define DN_ONLY_WIN -// -// Compiles the library with all optional APIs turned off except virtual arrays -// and the Win32 helpers. Alternatively: -// -// #define DN_NO_VARRAY -// #define DN_NO_WIN -// -// Compiles the library with all optional APIs turned on except the previously -// mentioned APIs. -// -// -- Library documentation -- -// -// The header files in this library are intentionally extremely minimal and -// concise as to provide a dense reference of the APIs available without -// drowning out the library interface with code comments like many other -// documentation systems. -// -// Instead, documentation is laid out in dn_docs.cpp in alphabetical order -// which provides self-contained examples in one contiguous top-down block of -// source code with comments. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\ $$ __$$\\__$$ __|\_$$ _|$$ __$$\ $$$\ $$ |$$ __$$\ -// $$ / $$ |$$ | $$ | $$ | $$ | $$ / $$ |$$$$\ $$ |$$ / \__| -// $$ | $$ |$$$$$$$ | $$ | $$ | $$ | $$ |$$ $$\$$ |\$$$$$$\ -// $$ | $$ |$$ ____/ $$ | $$ | $$ | $$ |$$ \$$$$ | \____$$\ -// $$ | $$ |$$ | $$ | $$ | $$ | $$ |$$ |\$$$ |$$\ $$ | -// $$$$$$ |$$ | $$ | $$$$$$\ $$$$$$ |$$ | \$$ |\$$$$$$ | -// \______/ \__| \__| \______| \______/ \__| \__| \______/ -// -// Options -- Compile time build customisation -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// - Override these routines from the CRT by redefining them. By default we wrap -// the CRT functions from and , e.g: -// -// #define DN_MEMCPY(dest, src, count) memcpy(dest, src, value) -// #define DN_MEMSET(dest, value, count) memset(dest, value, count) -// #define DN_MEMCMP(lhs, rhs, count) memcpy(lhs, rhs, count) -// #define DN_MEMMOVE(dest, src, count) memmove(dest, src, count) -// #define DN_SQRTF(val) sqrtf(val) -// #define DN_SINF(val) sinf(val) -// #define DN_COSF(val) cosf(val) -// #define DN_TANF(val) tanf(val) -// -// - Redefine 'DN_API' to change the prefix of all declared functions in the library -// -// #define DN_API -// -// - Define 'DN_STATIC_API' to apply 'static' to all function definitions and -// disable external linkage to other translation units by redefining 'DN_API' to -// 'static'. -// -// #define DN_STATIC_API -// -// - Turn all assertion macros to no-ops except for hard asserts (which are -// always enabled and represent unrecoverable errors in the library). -// -// #define DN_NO_ASSERT -// -// - Augment DN_CHECK(expr) macro's behaviour. By default when the check fails a -// debug break is emitted. If this macro is defined, the check will not trigger -// a debug break. -// -// #define DN_NO_CHECK_BREAK -// -// - Define this macro to enable memory leak tracking on arena's that are -// configured to track allocations. -// -// Allocations are stored in a global hash-table and their respective stack -// traces for the allocation location. Memory leaks can be dumped at the end -// of the program or some epoch by calling DN_Debug_DumpLeaks() -// -// #define DN_LEAK_TRACKING -// -// - Define this to revert to the family of printf functions from -// instead of using stb_sprintf in this library. stb_sprintf is 5-6x faster -// than printf with a smaller binary footprint and has deterministic behaviour -// across all supported platforms. -// -// #define DN_USE_STD_PRINTF -// -// However, if you are compiling with ASAN on MSVC, MSVC's implementation of -// __declspec(no_sanitize_address) is unable to suppress warnings in some -// individual functions in stb's implementation causing ASAN to trigger. This -// library will error on compilation if it detects this is the case and is -// being compiled with STB sprintf. -// -// - Define this to stop this library from defining a minimal subset of Win32 -// prototypes and definitions in this file. You should use this macro if you -// intend to #include yourself to avoid symbol conflicts with -// the redefined declarations in this library. -// -// #define DN_NO_WIN32_MIN_HEADER -// -// - Define this to stop this library from defining STB_SPRINTF_IMPLEMENTATION. -// Useful if another library uses and includes "stb_sprintf.h" -// -// #define DN_STB_SPRINTF_HEADER_ONLY -// -// - Override the default break into the active debugger function. By default -// we use __debugbreak() on Windows and raise(SIGTRAP) on other platforms. -// -// #define DN_DEBUG_BREAK -// -// - Define this macro to 1 to enable poisoning of memory from arenas when ASAN -// `-fsanitize=address` is enabled. Enabling this will detect memory overwrite -// by padding allocated before and after with poisoned memory which will raise -// a use-after-poison in ASAN on read/write. This is a no-op if the library is -// not compiled with ASAN. -// -// #define DN_ASAN_POISON 1 -// -// - Define this macro 1 to enable sanity checks for manually poisoned memory in -// this library when ASAN `-fsanitize=address` is enabled. These sanity checks -// ensure that memory from arenas are correctly un/poisoned when pointers are -// allocated and returned to the memory arena's. This is a no-op if we are not -// compiled with ASAN or `DN_ASAN_POISON` is not set to `1`. -// -// #define DN_ASAN_VET_POISON 1 -// -// - Define this macro to the size of the guard memory reserved before and after -// allocations made that are poisoned to protect against out-of-bounds memory -// accesses. By default the library sets the guard to 128 bytes. -// -// #define DN_ASAN_POISON_GUARD_SIZE 128 -// -// - Enable 'DN_CGen' a parser that can emit run-time type information and -// allow arbitrary querying of data definitions expressed in Excel-like tables -// using text files encoded in Dion-System's Metadesk grammar. -// -// This option automatically includes 'dn_cpp_file.h' to assist with code -// generation and Metadesk's 'md.h' and its implementation library. -// -// #define DN_WITH_CGEN -// -// Optionally define 'DN_NO_METADESK' to disable the inclusion of Metadesk -// in the library. This might be useful if you are including the librarin in -// your project yourself. This library must still be defined and visible -// before this header. -// -// - Enable 'DN_JSON' a json parser. This option requires Sheredom's 'json.h' -// to be included prior to this file. -// -// #define DN_WITH_JSON -// -// Optionally define 'DN_NO_SHEREDOM_JSON' to prevent Sheredom's 'json.h' -// library from being included. This might be useful if you are including the -// library in your project yourself. The library must still be defined and -// visible before this header. -// -// - Enable compilation of unit tests with the library. -// -// #define DN_WITH_UNIT_TESTS -// -// - Increase the capacity of the job queue, default is 128. -// -// #define DN_JOB_QUEUE_SPMC_SIZE 128 -*/ - -#if defined(DN_ONLY_VARRAY) || \ - defined(DN_ONLY_SARRAY) || \ - defined(DN_ONLY_FARRAY) || \ - defined(DN_ONLY_DSMAP) || \ - defined(DN_ONLY_LIST) || \ - defined(DN_ONLY_FSTR8) || \ - defined(DN_ONLY_FS) || \ - defined(DN_ONLY_WINNET) || \ - defined(DN_ONLY_WIN) || \ - defined(DN_ONLY_SEMAPHORE) || \ - defined(DN_ONLY_THREAD) || \ - defined(DN_ONLY_V2) || \ - defined(DN_ONLY_V3) || \ - defined(DN_ONLY_V4) || \ - defined(DN_ONLY_M4) || \ - defined(DN_ONLY_RECT) || \ - defined(DN_ONLY_JSON_BUILDER) || \ - defined(DN_ONLY_BIN) || \ - defined(DN_ONLY_PROFILER) - - #if !defined(DN_ONLY_VARRAY) - #define DN_NO_VARRAY - #endif - #if !defined(DN_ONLY_FARRAY) - #define DN_NO_FARRAY - #endif - #if !defined(DN_ONLY_SARRAY) - #define DN_NO_SARRAY - #endif - #if !defined(DN_ONLY_DSMAP) - #define DN_NO_DSMAP - #endif - #if !defined(DN_ONLY_LIST) - #define DN_NO_LIST - #endif - #if !defined(DN_ONLY_FSTR8) - #define DN_NO_FSTR8 - #endif - #if !defined(DN_ONLY_FS) - #define DN_NO_FS - #endif - #if !defined(DN_ONLY_WINNET) - #define DN_NO_WINNET - #endif - #if !defined(DN_ONLY_WIN) - #define DN_NO_WIN - #endif - #if !defined(DN_ONLY_SEMAPHORE) - #define DN_NO_SEMAPHORE - #endif - #if !defined(DN_ONLY_THREAD) - #define DN_NO_THREAD - #endif - #if !defined(DN_ONLY_V2) - #define DN_NO_V2 - #endif - #if !defined(DN_ONLY_V3) - #define DN_NO_V3 - #endif - #if !defined(DN_ONLY_V4) - #define DN_NO_V4 - #endif - #if !defined(DN_ONLY_M4) - #define DN_NO_M4 - #endif - #if !defined(DN_ONLY_RECT) - #define DN_NO_RECT - #endif - #if !defined(DN_ONLY_JSON_BUILDER) - #define DN_NO_JSON_BUILDER - #endif - #if !defined(DN_ONLY_BIN) - #define DN_NO_BIN - #endif - #if !defined(DN_ONLY_PROFILER) - #define DN_NO_PROFILER - #endif -#endif - -#include "dqn_base.h" -#if defined(DN_WITH_CGEN) - #if !defined(DN_NO_METADESK) - #if !defined(_CRT_SECURE_NO_WARNINGS) - #define _CRT_SECURE_NO_WARNINGS - #define DN_UNDO_CRT_SECURE_NO_WARNINGS - #endif - - // NOTE: Metadesk does not have the header for 'size_t' - #if defined(DN_COMPILER_GCC) - #include - #endif - - #define MD_DEFAULT_SPRINTF 0 - #define MD_IMPL_Vsnprintf DN_VSNPRINTF - #include "External/metadesk/md.h" - #if defined(DN_UNDO_CRT_SECURE_NO_WARNINGS) - #undef _CRT_SECURE_NO_WARNINGS - #endif - #endif - - // Metadesk includes Windows.h - #define DN_NO_WIN32_MIN_HEADER -#endif - -#include "dqn_external.h" -#if defined(DN_PLATFORM_WIN32) -#include "dqn_win32.h" -#endif -#include "dqn_allocator.h" -#include "dqn_tls.h" -#include "dqn_debug.h" -#include "dqn_string.h" -#if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_ARM64) - #include "dqn_os_posix.h" -#elif defined(DN_PLATFORM_WIN32) - #include "dqn_os_win32.h" -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif -#include "dqn_os.h" -#include "dqn_containers.h" -#include "dqn_math.h" -#include "dqn_hash.h" -#include "dqn_helpers.h" -#include "dqn_type_info.h" - -#if defined(DN_WITH_CGEN) - #include "Standalone/dqn_cpp_file.h" - #include "dqn_cgen.h" -#endif - -#if defined(DN_WITH_JSON) - #if !defined(DN_NO_SHEREDOM_JSON) - #include "External/json.h" - #endif - #include "dqn_json.h" -#endif diff --git a/dqn_allocator.cpp b/dqn_allocator.cpp deleted file mode 100644 index 37c9bab..0000000 --- a/dqn_allocator.cpp +++ /dev/null @@ -1,653 +0,0 @@ -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ -// $$ __$$\ $$ | $$ | $$ __$$\ $$ __$$\ $$ __$$\\__$$ __|$$ __$$\ $$ __$$\ -// $$ / $$ |$$ | $$ | $$ / $$ |$$ / \__|$$ / $$ | $$ | $$ / $$ |$$ | $$ | -// $$$$$$$$ |$$ | $$ | $$ | $$ |$$ | $$$$$$$$ | $$ | $$ | $$ |$$$$$$$ | -// $$ __$$ |$$ | $$ | $$ | $$ |$$ | $$ __$$ | $$ | $$ | $$ |$$ __$$< -// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$\ $$ | $$ | $$ | $$ | $$ |$$ | $$ | -// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$$$$$ |\$$$$$$ |$$ | $$ | $$ | $$$$$$ |$$ | $$ | -// \__| \__|\________|\________|\______/ \______/ \__| \__| \__| \______/ \__| \__| -// -// dqn_allocator.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$AREN] DN_Arena ///////////////////////////////////////////////////////////////////////// -DN_API DN_ArenaBlock *DN_Arena_BlockInit(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak) -{ - DN_USize const page_size = g_dn_core->os_page_size; - DN_U64 real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE; - DN_U64 real_commit = commit ? commit : DN_ARENA_COMMIT_SIZE; - real_reserve = DN_AlignUpPowerOfTwo(real_reserve, page_size); - real_commit = DN_MIN(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve); - - DN_ASSERTF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve); - DN_ASSERTF(page_size, "Call DN_Library_Init() to initialise the known page size"); - - DN_OSMemCommit mem_commit = real_reserve == real_commit ? DN_OSMemCommit_Yes : DN_OSMemCommit_No; - DN_ArenaBlock *result = DN_CAST(DN_ArenaBlock *)DN_OS_MemReserve(real_reserve, mem_commit, DN_OSMemPage_ReadWrite); - if (!result) - return result; - - if (mem_commit == DN_OSMemCommit_No && !DN_OS_MemCommit(result, real_commit, DN_OSMemPage_ReadWrite)) { - DN_OS_MemRelease(result, real_reserve); - return result; - } - - result->used = DN_ARENA_HEADER_SIZE; - result->commit = real_commit; - result->reserve = real_reserve; - - if (track_alloc) - DN_Debug_TrackAlloc(result, result->reserve, alloc_can_leak); - return result; -} - -DN_API DN_ArenaBlock *DN_Arena_BlockInitFlags(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags) -{ - bool track_alloc = (flags & DN_ArenaFlags_NoAllocTrack) == 0; - bool alloc_can_leak = flags & DN_ArenaFlags_AllocCanLeak; - DN_ArenaBlock *result = DN_Arena_BlockInit(reserve, commit, track_alloc, alloc_can_leak); - if (result && ((flags & DN_ArenaFlags_NoPoison) == 0)) - DN_ASAN_PoisonMemoryRegion(DN_CAST(char *)result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); - return result; -} - -static void DN_Arena_UpdateStatsOnNewBlock_(DN_Arena *arena, DN_ArenaBlock const *block) -{ - DN_ASSERT(arena); - if (block) { - arena->stats.info.used += block->used; - arena->stats.info.commit += block->commit; - arena->stats.info.reserve += block->reserve; - arena->stats.info.blocks += 1; - - arena->stats.hwm.used = DN_MAX(arena->stats.hwm.used, arena->stats.info.used); - arena->stats.hwm.commit = DN_MAX(arena->stats.hwm.commit, arena->stats.info.commit); - arena->stats.hwm.reserve = DN_MAX(arena->stats.hwm.reserve, arena->stats.info.reserve); - arena->stats.hwm.blocks = DN_MAX(arena->stats.hwm.blocks, arena->stats.info.blocks); - } -} - -DN_API DN_Arena DN_Arena_InitBuffer(void *buffer, DN_USize size, DN_ArenaFlags flags) -{ - DN_ASSERT(buffer); - DN_ASSERTF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE); - DN_ASSERTF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size); - - // NOTE: Init block - DN_ArenaBlock *block = DN_CAST(DN_ArenaBlock *) buffer; - block->commit = size; - block->reserve = size; - block->used = DN_ARENA_HEADER_SIZE; - if (block && ((flags & DN_ArenaFlags_NoPoison) == 0)) { - DN_ASAN_PoisonMemoryRegion(DN_CAST(char *)block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE); - } - - DN_Arena result = {}; - result.flags = flags | DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_UserBuffer; - result.curr = block; - DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); - return result; -} - -DN_API DN_Arena DN_Arena_InitSize(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags) -{ - DN_Arena result = {}; - result.flags = flags; - result.curr = DN_Arena_BlockInitFlags(reserve, commit, flags); - DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr); - return result; -} - -static void DN_Arena_BlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block) -{ - DN_USize release_size = block->reserve; - if (DN_Bit_IsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack)) - DN_Debug_TrackDealloc(block); - DN_ASAN_UnpoisonMemoryRegion(block, block->commit); - DN_OS_MemRelease(block, release_size); -} - -DN_API void DN_Arena_Deinit(DN_Arena *arena) -{ - for (DN_ArenaBlock *block = arena ? arena->curr : nullptr; block; ) { - DN_ArenaBlock *block_to_free = block; - block = block->prev; - DN_Arena_BlockDeinit_(arena, block_to_free); - } - if (arena) - *arena = {}; -} - -DN_API bool DN_Arena_CommitTo(DN_Arena *arena, DN_U64 pos) -{ - if (!arena || !arena->curr) - return false; - - DN_ArenaBlock *curr = arena->curr; - if (pos <= curr->commit) - return true; - - DN_U64 real_pos = pos; - if (!DN_CHECK(pos <= curr->reserve)) - real_pos = curr->reserve; - - DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, g_dn_core->os_page_size); - DN_USize commit_size = end_commit - curr->commit; - char *commit_ptr = DN_CAST(char *) curr + curr->commit; - if (!DN_OS_MemCommit(commit_ptr, commit_size, DN_OSMemPage_ReadWrite)) - return false; - - bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); - if (poison) - DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); - - curr->commit = end_commit; - return true; -} - -DN_API bool DN_Arena_Commit(DN_Arena *arena, DN_U64 size) -{ - if (!arena || !arena->curr) - return false; - DN_U64 pos = arena->curr->commit + size; - bool result = DN_Arena_CommitTo(arena, pos); - return result; -} - -DN_API bool DN_Arena_Grow(DN_Arena *arena, DN_U64 reserve, DN_U64 commit) -{ - if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer)) - return false; - - DN_ArenaBlock *new_block = DN_Arena_BlockInitFlags(reserve, commit, arena->flags); - if (new_block) { - new_block->prev = arena->curr; - arena->curr = new_block; - new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve; - DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); - } - bool result = new_block; - return result; -} - -DN_API void *DN_Arena_Alloc(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) -{ - if (!arena) - return nullptr; - - if (!arena->curr) { - arena->curr = DN_Arena_BlockInitFlags(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, arena->flags); - DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr); - } - - if (!arena->curr) - return nullptr; - - try_alloc_again: - DN_ArenaBlock *curr = arena->curr; - bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0); - uint8_t real_align = poison ? DN_MAX(align, DN_ASAN_POISON_ALIGNMENT) : align; - DN_U64 offset_pos = DN_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DN_ASAN_POISON_GUARD_SIZE : 0); - DN_U64 end_pos = offset_pos + size; - DN_U64 alloc_size = end_pos - curr->used; - - if (end_pos > curr->reserve) { - if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer)) - return nullptr; - DN_USize new_reserve = DN_MAX(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE); - DN_USize new_commit = DN_MAX(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE); - if (!DN_Arena_Grow(arena, new_reserve, new_commit)) - return nullptr; - goto try_alloc_again; - } - - DN_USize prev_arena_commit = curr->commit; - if (end_pos > curr->commit) { - DN_ASSERT((arena->flags & DN_ArenaFlags_UserBuffer) == 0); - DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, g_dn_core->os_page_size); - DN_USize commit_size = end_commit - curr->commit; - char *commit_ptr = DN_CAST(char *) curr + curr->commit; - if (!DN_OS_MemCommit(commit_ptr, commit_size, DN_OSMemPage_ReadWrite)) - return nullptr; - if (poison) - DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size); - curr->commit = end_commit; - arena->stats.info.commit += commit_size; - arena->stats.hwm.commit = DN_MAX(arena->stats.hwm.commit, arena->stats.info.commit); - } - - void *result = DN_CAST(char *) curr + offset_pos; - curr->used += alloc_size; - arena->stats.info.used += alloc_size; - arena->stats.hwm.used = DN_MAX(arena->stats.hwm.used, arena->stats.info.used); - DN_ASAN_UnpoisonMemoryRegion(result, size); - - if (zero_mem == DN_ZeroMem_Yes) { - DN_USize reused_bytes = DN_MIN(prev_arena_commit - offset_pos, size); - DN_MEMSET(result, 0, reused_bytes); - } - - DN_ASSERT(arena->stats.hwm.used >= arena->stats.info.used); - DN_ASSERT(arena->stats.hwm.commit >= arena->stats.info.commit); - DN_ASSERT(arena->stats.hwm.reserve >= arena->stats.info.reserve); - DN_ASSERT(arena->stats.hwm.blocks >= arena->stats.info.blocks); - return result; -} - -DN_API void *DN_Arena_AllocContiguous(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem) -{ - DN_ArenaFlags prev_flags = arena->flags; - arena->flags |= (DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoPoison); - void *memory = DN_Arena_Alloc(arena, size, align, zero_mem); - arena->flags = prev_flags; - return memory; -} - -DN_API void *DN_Arena_Copy(DN_Arena *arena, void const *data, DN_U64 size, uint8_t align) -{ - if (!arena || !data || size == 0) - return nullptr; - void *result = DN_Arena_Alloc(arena, size, align, DN_ZeroMem_No); - if (result) - DN_MEMCPY(result, data, size); - return result; -} - -DN_API void DN_Arena_PopTo(DN_Arena *arena, DN_U64 init_used) -{ - if (!arena || !arena->curr) - return; - DN_U64 used = DN_MAX(DN_ARENA_HEADER_SIZE, init_used); - DN_ArenaBlock *curr = arena->curr; - while (curr->reserve_sum >= used) { - DN_ArenaBlock *block_to_free = curr; - arena->stats.info.used -= block_to_free->used; - arena->stats.info.commit -= block_to_free->commit; - arena->stats.info.reserve -= block_to_free->reserve; - arena->stats.info.blocks -= 1; - if (arena->flags & DN_ArenaFlags_UserBuffer) - break; - curr = curr->prev; - DN_Arena_BlockDeinit_(arena, block_to_free); - } - - arena->stats.info.used -= curr->used; - arena->curr = curr; - curr->used = used - curr->reserve_sum; - char *poison_ptr = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT); - DN_USize poison_size = ((char *)curr + curr->commit) - poison_ptr; - DN_ASAN_PoisonMemoryRegion(poison_ptr, poison_size); - arena->stats.info.used += curr->used; -} - -DN_API void DN_Arena_Pop(DN_Arena *arena, DN_U64 amount) -{ - DN_ArenaBlock *curr = arena->curr; - DN_USize used_sum = curr->reserve_sum + curr->used; - if (!DN_CHECK(amount <= used_sum)) - amount = used_sum; - DN_USize pop_to = used_sum - amount; - DN_Arena_PopTo(arena, pop_to); -} - -DN_API DN_U64 DN_Arena_Pos(DN_Arena const *arena) -{ - DN_U64 result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0; - return result; -} - -DN_API void DN_Arena_Clear(DN_Arena *arena) -{ - DN_Arena_PopTo(arena, 0); -} - -DN_API bool DN_Arena_OwnsPtr(DN_Arena const *arena, void *ptr) -{ - bool result = false; - uintptr_t uint_ptr = DN_CAST(uintptr_t)ptr; - for (DN_ArenaBlock const *block = arena ? arena->curr : nullptr; !result && block; block = block->prev) { - uintptr_t begin = DN_CAST(uintptr_t) block + DN_ARENA_HEADER_SIZE; - uintptr_t end = begin + block->reserve; - result = uint_ptr >= begin && uint_ptr <= end; - } - return result; -} - - -DN_API DN_ArenaStats DN_Arena_SumStatsArray(DN_ArenaStats const *array, DN_USize size) -{ - DN_ArenaStats result = {}; - DN_FOR_UINDEX(index, size) { - DN_ArenaStats stats = array[index]; - result.info.used += stats.info.used; - result.info.commit += stats.info.commit; - result.info.reserve += stats.info.reserve; - result.info.blocks += stats.info.blocks; - - result.hwm.used = DN_MAX(result.hwm.used, result.info.used); - result.hwm.commit = DN_MAX(result.hwm.commit, result.info.commit); - result.hwm.reserve = DN_MAX(result.hwm.reserve, result.info.reserve); - result.hwm.blocks = DN_MAX(result.hwm.blocks, result.info.blocks); - } - return result; -} - -DN_API DN_ArenaStats DN_Arena_SumStats(DN_ArenaStats lhs, DN_ArenaStats rhs) -{ - DN_ArenaStats array[] = {lhs, rhs}; - DN_ArenaStats result = DN_Arena_SumStatsArray(array, DN_ARRAY_UCOUNT(array)); - return result; -} - -DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats(DN_Arena const *array, DN_USize size) -{ - DN_ArenaStats result = {}; - for (DN_USize index = 0; index < size; index++) { - DN_Arena const *arena = array + index; - result = DN_Arena_SumStats(result, arena->stats); - } - return result; -} - -DN_API DN_ArenaTempMem DN_Arena_TempMemBegin(DN_Arena *arena) -{ - DN_ArenaTempMem result = {}; - if (arena) { - DN_ArenaBlock *curr = arena->curr; - result = {arena, curr ? curr->reserve_sum + curr->used : 0}; - } - return result; -}; - -DN_API void DN_Arena_TempMemEnd(DN_ArenaTempMem mem) -{ - DN_Arena_PopTo(mem.arena, mem.used_sum); -}; - -DN_ArenaTempMemScope::DN_ArenaTempMemScope(DN_Arena *arena) -{ - mem = DN_Arena_TempMemBegin(arena); -} - -DN_ArenaTempMemScope::~DN_ArenaTempMemScope() -{ - DN_Arena_TempMemEnd(mem); -} - -// NOTE: [$POOL] DN_Pool ////////////////////////////////////////////////////////////////////////// -DN_API DN_Pool DN_Pool_Init(DN_Arena *arena, uint8_t align) -{ - DN_Pool result = {}; - if (arena) { - result.arena = arena; - result.align = align ? align : DN_POOL_DEFAULT_ALIGN; - } - return result; -} - -DN_API bool DN_Pool_IsValid(DN_Pool const *pool) -{ - bool result = pool && pool->arena && pool->align; - return result; -} - -DN_API void *DN_Pool_Alloc(DN_Pool *pool, DN_USize size) -{ - void *result = nullptr; - if (!DN_Pool_IsValid(pool)) - return result; - - DN_USize const required_size = sizeof(DN_PoolSlot) + pool->align + size; - DN_USize const size_to_slot_offset = 5; // __lzcnt64(32) e.g. DN_PoolSlotSize_32B - DN_USize slot_index = 0; - if (required_size > 32) { - // NOTE: Round up if not PoT as the low bits are set. - DN_USize dist_to_next_msb = DN_CountLeadingZerosU64(required_size) + 1; - dist_to_next_msb -= DN_CAST(DN_USize)(!DN_IsPowerOfTwo(required_size)); - - DN_USize const register_size = sizeof(DN_USize) * 8; - DN_ASSERT(register_size >= dist_to_next_msb + size_to_slot_offset); - slot_index = register_size - dist_to_next_msb - size_to_slot_offset; - } - - if (!DN_CHECKF(slot_index < DN_PoolSlotSize_Count, "Chunk pool does not support the requested allocation size")) - return result; - - DN_USize slot_size_in_bytes = 1ULL << (slot_index + size_to_slot_offset); - DN_ASSERT(required_size <= (slot_size_in_bytes << 0)); - DN_ASSERT(required_size >= (slot_size_in_bytes >> 1)); - - DN_PoolSlot *slot = nullptr; - if (pool->slots[slot_index]) { - slot = pool->slots[slot_index]; - pool->slots[slot_index] = slot->next; - DN_MEMSET(slot->data, 0, size); - DN_ASSERT(DN_IsPowerOfTwoAligned(slot->data, pool->align)); - } else { - void *bytes = DN_Arena_Alloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZeroMem_Yes); - slot = DN_CAST(DN_PoolSlot *) bytes; - - // NOTE: The raw pointer is round up to the next 'pool->align'-ed - // address ensuring at least 1 byte of padding between the raw pointer - // and the pointer given to the user and that the user pointer is - // aligned to the pool's alignment. - // - // This allows us to smuggle 1 byte behind the user pointer that has - // the offset to the original pointer. - slot->data = DN_CAST(void *)DN_AlignDownPowerOfTwo(DN_CAST(uintptr_t)slot + sizeof(DN_PoolSlot) + pool->align, pool->align); - - uintptr_t offset_to_original_ptr = DN_CAST(uintptr_t)slot->data - DN_CAST(uintptr_t)bytes; - DN_ASSERT(slot->data > bytes); - DN_ASSERT(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); - - // NOTE: Store the offset to the original pointer behind the user's - // pointer. - char *offset_to_original_storage = DN_CAST(char *)slot->data - 1; - DN_MEMCPY(offset_to_original_storage, &offset_to_original_ptr, 1); - } - - // NOTE: Smuggle the slot type in the next pointer so that we know, when the - // pointer gets returned which free list to return the pointer to. - result = slot->data; - slot->next = DN_CAST(DN_PoolSlot *)slot_index; - return result; -} - -DN_API DN_Str8 DN_Pool_AllocStr8FV(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 result = {}; - if (!DN_Pool_IsValid(pool)) - return result; - - DN_USize size_required = DN_CStr8_FVSize(fmt, args); - result.data = DN_CAST(char *) DN_Pool_Alloc(pool, size_required + 1); - if (result.data) { - result.size = size_required; - DN_VSNPRINTF(result.data, DN_CAST(int)(result.size + 1), fmt, args); - } - return result; -} - -DN_API DN_Str8 DN_Pool_AllocStr8F(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 result = DN_Pool_AllocStr8FV(pool, fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Pool_AllocStr8Copy(DN_Pool *pool, DN_Str8 string) -{ - DN_Str8 result = {}; - if (!DN_Pool_IsValid(pool)) - return result; - - if (!DN_Str8_HasData(string)) - return result; - - char *data = DN_CAST(char *)DN_Pool_Alloc(pool, string.size + 1); - if (!data) - return result; - - DN_MEMCPY(data, string.data, string.size); - data[string.size] = 0; - result = DN_Str8_Init(data, string.size); - return result; -} - -DN_API void DN_Pool_Dealloc(DN_Pool *pool, void *ptr) -{ - if (!DN_Pool_IsValid(pool) || !ptr) - return; - - DN_ASSERT(DN_Arena_OwnsPtr(pool->arena, ptr)); - - char const *one_byte_behind_ptr = DN_CAST(char *) ptr - 1; - DN_USize offset_to_original_ptr = 0; - DN_MEMCPY(&offset_to_original_ptr, one_byte_behind_ptr, 1); - DN_ASSERT(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align); - - char *original_ptr = DN_CAST(char *) ptr - offset_to_original_ptr; - DN_PoolSlot *slot = DN_CAST(DN_PoolSlot *) original_ptr; - DN_PoolSlotSize slot_index = DN_CAST(DN_PoolSlotSize)(DN_CAST(uintptr_t) slot->next); - DN_ASSERT(slot_index < DN_PoolSlotSize_Count); - - slot->next = pool->slots[slot_index]; - pool->slots[slot_index] = slot; -} - -DN_API void *DN_Pool_Copy(DN_Pool *pool, void const *data, DN_U64 size, uint8_t align) -{ - if (!pool || !data || size == 0) - return nullptr; - - // TODO: Hmm should align be part of the alloc interface in general? I'm not going to worry - // about this until we crash because of misalignment. - DN_ASSERT(pool->align >= align); - - void *result = DN_Pool_Alloc(pool, size); - if (result) - DN_MEMCPY(result, data, size); - return result; -} - -// NOTE: [$ACAT] DN_ArenaCatalog ////////////////////////////////////////////////////////////////// -DN_API void DN_ArenaCatalog_Init(DN_ArenaCatalog *catalog, DN_Pool *pool) -{ - catalog->pool = pool; - catalog->sentinel.next = &catalog->sentinel; - catalog->sentinel.prev = &catalog->sentinel; -} - -DN_API DN_ArenaCatalogItem *DN_ArenaCatalog_Find(DN_ArenaCatalog *catalog, DN_Str8 label) -{ - DN_TicketMutex_Begin(&catalog->ticket_mutex); - DN_ArenaCatalogItem *result = &catalog->sentinel; - for (DN_ArenaCatalogItem *item = catalog->sentinel.next; item != &catalog->sentinel; item = item->next) { - if (item->label == label) { - result = item; - break; - } - } - DN_TicketMutex_End(&catalog->ticket_mutex); - return result; -} - -static void DN_ArenaCatalog_AddInternal_(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_Str8 label, bool arena_pool_allocated) -{ - // NOTE: We could use an atomic for appending to the sentinel but it is such - // a rare operation to append to the catalog that we don't bother. - DN_TicketMutex_Begin(&catalog->ticket_mutex); - - // NOTE: Create item in the catalog - DN_ArenaCatalogItem *result = DN_Pool_New(catalog->pool, DN_ArenaCatalogItem); - if (result) { - result->arena = arena; - result->label = label; - result->arena_pool_allocated = arena_pool_allocated; - - // NOTE: Add to the catalog (linked list) - DN_ArenaCatalogItem *sentinel = &catalog->sentinel; - result->next = sentinel; - result->prev = sentinel->prev; - result->next->prev = result; - result->prev->next = result; - DN_Atomic_AddU32(&catalog->arena_count, 1); - } - DN_TicketMutex_End(&catalog->ticket_mutex); -} - -DN_API void DN_ArenaCatalog_AddF(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_TicketMutex_Begin(&catalog->ticket_mutex); - DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args); - DN_TicketMutex_End(&catalog->ticket_mutex); - va_end(args); - DN_ArenaCatalog_AddInternal_(catalog, arena, label, false /*arena_pool_allocated*/); -} - -DN_API void DN_ArenaCatalog_AddFV(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TicketMutex_Begin(&catalog->ticket_mutex); - DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args); - DN_TicketMutex_End(&catalog->ticket_mutex); - DN_ArenaCatalog_AddInternal_(catalog, arena, label, false /*arena_pool_allocated*/); -} - -DN_API DN_Arena *DN_ArenaCatalog_AllocFV(DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TicketMutex_Begin(&catalog->ticket_mutex); - DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args); - DN_Arena *result = DN_Pool_New(catalog->pool, DN_Arena); - DN_TicketMutex_End(&catalog->ticket_mutex); - - *result = DN_Arena_InitSize(reserve, commit, arena_flags); - DN_ArenaCatalog_AddInternal_(catalog, result, label, true /*arena_pool_allocated*/); - return result; -} - -DN_API DN_Arena *DN_ArenaCatalog_AllocF(DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_TicketMutex_Begin(&catalog->ticket_mutex); - DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args); - DN_Arena *result = DN_Pool_New(catalog->pool, DN_Arena); - DN_TicketMutex_End(&catalog->ticket_mutex); - va_end(args); - - *result = DN_Arena_InitSize(reserve, commit, arena_flags); - DN_ArenaCatalog_AddInternal_(catalog, result, label, true /*arena_pool_allocated*/); - return result; -} - -DN_API bool DN_ArenaCatalog_Erase(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena) -{ - bool result = false; - DN_TicketMutex_Begin(&catalog->ticket_mutex); - for (DN_ArenaCatalogItem *item = catalog->sentinel.next; item != &catalog->sentinel; item = item->next) { - if (item->arena == arena) { - item->next->prev = item->prev; - item->prev->next = item->next; - if (item->arena_pool_allocated) { - if (free_arena == DN_ArenaCatalogFreeArena_Yes) - DN_Arena_Deinit(item->arena); - DN_Pool_Dealloc(catalog->pool, item->arena); - } - DN_Pool_Dealloc(catalog->pool, item->label.data); - DN_Pool_Dealloc(catalog->pool, item); - result = true; - break; - } - } - DN_TicketMutex_End(&catalog->ticket_mutex); - return result; -} diff --git a/dqn_allocator.h b/dqn_allocator.h deleted file mode 100644 index 0c9bab9..0000000 --- a/dqn_allocator.h +++ /dev/null @@ -1,220 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ -// $$ __$$\ $$ | $$ | $$ __$$\ $$ __$$\ $$ __$$\\__$$ __|$$ __$$\ $$ __$$\ -// $$ / $$ |$$ | $$ | $$ / $$ |$$ / \__|$$ / $$ | $$ | $$ / $$ |$$ | $$ | -// $$$$$$$$ |$$ | $$ | $$ | $$ |$$ | $$$$$$$$ | $$ | $$ | $$ |$$$$$$$ | -// $$ __$$ |$$ | $$ | $$ | $$ |$$ | $$ __$$ | $$ | $$ | $$ |$$ __$$< -// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$\ $$ | $$ | $$ | $$ | $$ |$$ | $$ | -// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$$$$$ |\$$$$$$ |$$ | $$ | $$ | $$$$$$ |$$ | $$ | -// \__| \__|\________|\________|\______/ \______/ \__| \__| \__| \______/ \__| \__| -// -// dqn_allocator.h -- Custom memory allocators -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$AREN] DN_Arena -- Growing bump allocator -// [$CHUN] DN_Pool -- Allocates reusable, free-able memory in PoT chunks -// [$POOL] DN_Pool -- TODO -// [$ACAT] DN_ArenaCatalog -- Collate, create & manage arenas in a catalog -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$AREN] DN_Arena ///////////////////////////////////////////////////////////////////////// -#if !defined(DN_ARENA_RESERVE_SIZE) - #define DN_ARENA_RESERVE_SIZE DN_MEGABYTES(64) -#endif -#if !defined(DN_ARENA_COMMIT_SIZE) - #define DN_ARENA_COMMIT_SIZE DN_KILOBYTES(64) -#endif - -struct DN_ArenaBlock -{ - DN_ArenaBlock *prev; - DN_U64 used; - DN_U64 commit; - DN_U64 reserve; - DN_U64 reserve_sum; -}; - -typedef uint32_t DN_ArenaFlags; -enum DN_ArenaFlags_ -{ - DN_ArenaFlags_Nil = 0, - DN_ArenaFlags_NoGrow = 1 << 0, - DN_ArenaFlags_NoPoison = 1 << 1, - DN_ArenaFlags_NoAllocTrack = 1 << 2, - DN_ArenaFlags_AllocCanLeak = 1 << 3, - DN_ArenaFlags_UserBuffer = 1 << 4, -}; - -struct DN_ArenaInfo -{ - DN_U64 used; - DN_U64 commit; - DN_U64 reserve; - DN_U64 blocks; -}; - -struct DN_ArenaStats -{ - DN_ArenaInfo info; - DN_ArenaInfo hwm; -}; - -struct DN_Arena -{ - DN_ArenaBlock *curr; - DN_ArenaStats stats; - DN_ArenaFlags flags; - DN_Str8 label; - DN_Arena *prev, *next; -}; - -struct DN_ArenaTempMem -{ - DN_Arena *arena; - DN_U64 used_sum; -}; - -struct DN_ArenaTempMemScope -{ - DN_ArenaTempMemScope(DN_Arena *arena); - ~DN_ArenaTempMemScope(); - DN_ArenaTempMem mem; -}; - -DN_USize const DN_ARENA_HEADER_SIZE = DN_AlignUpPowerOfTwo(sizeof(DN_Arena), 64); - -// NOTE: [$CHUN] DN_Pool ///////////////////////////////////////////////////////////////////// -#if !defined(DN_POOL_DEFAULT_ALIGN) - #define DN_POOL_DEFAULT_ALIGN 16 -#endif - -struct DN_PoolSlot -{ - void *data; - DN_PoolSlot *next; -}; - -enum DN_PoolSlotSize -{ - DN_PoolSlotSize_32B, - DN_PoolSlotSize_64B, - DN_PoolSlotSize_128B, - DN_PoolSlotSize_256B, - DN_PoolSlotSize_512B, - DN_PoolSlotSize_1KiB, - DN_PoolSlotSize_2KiB, - DN_PoolSlotSize_4KiB, - DN_PoolSlotSize_8KiB, - DN_PoolSlotSize_16KiB, - DN_PoolSlotSize_32KiB, - DN_PoolSlotSize_64KiB, - DN_PoolSlotSize_128KiB, - DN_PoolSlotSize_256KiB, - DN_PoolSlotSize_512KiB, - DN_PoolSlotSize_1MiB, - DN_PoolSlotSize_2MiB, - DN_PoolSlotSize_4MiB, - DN_PoolSlotSize_8MiB, - DN_PoolSlotSize_16MiB, - DN_PoolSlotSize_32MiB, - DN_PoolSlotSize_64MiB, - DN_PoolSlotSize_128MiB, - DN_PoolSlotSize_256MiB, - DN_PoolSlotSize_512MiB, - DN_PoolSlotSize_1GiB, - DN_PoolSlotSize_2GiB, - DN_PoolSlotSize_4GiB, - DN_PoolSlotSize_8GiB, - DN_PoolSlotSize_16GiB, - DN_PoolSlotSize_32GiB, - DN_PoolSlotSize_Count, -}; - -struct DN_Pool -{ - DN_Arena *arena; - DN_PoolSlot *slots[DN_PoolSlotSize_Count]; - uint8_t align; -}; - -// NOTE: [$ACAT] DN_ArenaCatalog ////////////////////////////////////////////////////////////////// -struct DN_ArenaCatalogItem -{ - DN_Arena *arena; - DN_Str8 label; - bool arena_pool_allocated; - DN_ArenaCatalogItem *next; - DN_ArenaCatalogItem *prev; -}; - -struct DN_ArenaCatalog -{ - DN_TicketMutex ticket_mutex; // Mutex for adding to the linked list of arenas - DN_Pool *pool; - DN_ArenaCatalogItem sentinel; - uint16_t arena_count; -}; - -enum DN_ArenaCatalogFreeArena -{ - DN_ArenaCatalogFreeArena_No, - DN_ArenaCatalogFreeArena_Yes, -}; - -// NOTE: [$AREN] DN_Arena ///////////////////////////////////////////////////////////////////////// -DN_API DN_Arena DN_Arena_InitBuffer (void *buffer, DN_USize size, DN_ArenaFlags flags); -DN_API DN_Arena DN_Arena_InitSize (DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags); -DN_API void DN_Arena_Deinit (DN_Arena *arena); -DN_API bool DN_Arena_Commit (DN_Arena *arena, DN_U64 size); -DN_API bool DN_Arena_CommitTo (DN_Arena *arena, DN_U64 pos); -DN_API bool DN_Arena_Grow (DN_Arena *arena, DN_U64 reserve, DN_U64 commit); -DN_API void * DN_Arena_Alloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); -DN_API void * DN_Arena_AllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem); -DN_API void * DN_Arena_Copy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); -DN_API void DN_Arena_PopTo (DN_Arena *arena, DN_U64 init_used); -DN_API void DN_Arena_Pop (DN_Arena *arena, DN_U64 amount); -DN_API DN_U64 DN_Arena_Pos (DN_Arena const *arena); -DN_API void DN_Arena_Clear (DN_Arena *arena); -DN_API bool DN_Arena_OwnsPtr (DN_Arena const *arena, void *ptr); -DN_API DN_ArenaStats DN_Arena_SumStatsArray (DN_ArenaStats const *array, DN_USize size); -DN_API DN_ArenaStats DN_Arena_SumStats (DN_ArenaStats lhs, DN_ArenaStats rhs); -DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats (DN_Arena const *array, DN_USize size); -DN_API DN_ArenaTempMem DN_Arena_TempMemBegin (DN_Arena *arena); -DN_API void DN_Arena_TempMemEnd (DN_ArenaTempMem mem); -#define DN_Arena_New_Frame(T, zero_mem) (T *)DN_Arena_Alloc(DN_TLS_Get()->frame_arena, sizeof(T), alignof(T), zero_mem) -#define DN_Arena_New(arena, T, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T), alignof(T), zero_mem) -#define DN_Arena_NewArray(arena, T, count, zero_mem) (T *)DN_Arena_Alloc(arena, sizeof(T) * (count), alignof(T), zero_mem) -#define DN_Arena_NewCopy(arena, T, src) (T *)DN_Arena_Copy (arena, (src), sizeof(T), alignof(T)) -#define DN_Arena_NewArrayCopy(arena, T, src, count) (T *)DN_Arena_Copy (arena, (src), sizeof(T) * (count), alignof(T)) - -// NOTE: [$CHUN] DN_Pool ///////////////////////////////////////////////////////////////////// -DN_API DN_Pool DN_Pool_Init (DN_Arena *arena, uint8_t align); -DN_API bool DN_Pool_IsValid (DN_Pool const *pool); -DN_API void * DN_Pool_Alloc (DN_Pool *pool, DN_USize size); -DN_API DN_Str8 DN_Pool_AllocStr8FV (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Str8 DN_Pool_AllocStr8F (DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Pool_AllocStr8Copy (DN_Pool *pool, DN_Str8 string); -DN_API void DN_Pool_Dealloc (DN_Pool *pool, void *ptr); -DN_API void * DN_Pool_Copy (DN_Pool *pool, void const *data, DN_U64 size, uint8_t align); - -#define DN_Pool_New(pool, T) (T *)DN_Pool_Alloc(pool, sizeof(T)) -#define DN_Pool_NewArray(pool, T, count) (T *)DN_Pool_Alloc(pool, count * sizeof(T)) -#define DN_Pool_NewCopy(arena, T, src) (T *)DN_Pool_Copy (arena, (src), sizeof(T), alignof(T)) -#define DN_Pool_NewArrayCopy(arena, T, src, count) (T *)DN_Pool_Copy (arena, (src), sizeof(T) * (count), alignof(T)) - -// NOTE: [$ACAT] DN_ArenaCatalog ////////////////////////////////////////////////////////////////// -DN_API void DN_ArenaCatalog_Init (DN_ArenaCatalog *catalog, DN_Pool *pool); -DN_API DN_ArenaCatalogItem *DN_ArenaCatalog_Find (DN_ArenaCatalog *catalog, DN_Str8 label); -DN_API void DN_ArenaCatalog_AddF (DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_ArenaCatalog_AddFV (DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Arena * DN_ArenaCatalog_AllocFV (DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API DN_Arena * DN_ArenaCatalog_AllocF (DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_ArenaCatalog_Erase (DN_ArenaCatalog *catalog, DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena); diff --git a/dqn_avx512f.cpp b/dqn_avx512f.cpp deleted file mode 100644 index eb369d7..0000000 --- a/dqn_avx512f.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#pragma once -#include "dqn.h" -#include - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// /$$$$$$ /$$ /$$ /$$ /$$ /$$$$$$$ /$$ /$$$$$$ /$$$$$$$$ -// /$$__ $$| $$ | $$| $$ / $$ | $$____/ /$$$$ /$$__ $$| $$_____/ -// | $$ \ $$| $$ | $$| $$/ $$/ | $$ |_ $$ |__/ \ $$| $$ -// | $$$$$$$$| $$ / $$/ \ $$$$/ /$$$$$$| $$$$$$$ | $$ /$$$$$$/| $$$$$ -// | $$__ $$ \ $$ $$/ >$$ $$|______/|_____ $$ | $$ /$$____/ | $$__/ -// | $$ | $$ \ $$$/ /$$/\ $$ /$$ \ $$ | $$ | $$ | $$ -// | $$ | $$ \ $/ | $$ \ $$ | $$$$$$//$$$$$$| $$$$$$$$| $$ -// |__/ |__/ \_/ |__/ |__/ \______/|______/|________/|__/ -// -// dqn_avx512f.h -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -DN_API DN_Str8FindResult DN_Str8_FindStr8AVX512F(DN_Str8 string, DN_Str8 find) -{ - // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html - DN_Str8FindResult result = {}; - if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size) - return result; - - __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); - __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); - - DN_USize const search_size = string.size - find.size; - DN_USize simd_iterations = search_size / sizeof(__m512i); - char const *ptr = string.data; - - while (simd_iterations--) { - __m512i find_first_ch_block = _mm512_loadu_si512(ptr); - __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); - - // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit - // where matches are found. - __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); - - // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one - // operation using the ternarylogic intrinsic. - // - // A = first_ch_matches (find_first_ch_block ^ find_first_ch) - // B = find_last_ch_block - // C = find_last_ch - // - // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 - // - // / A / B / C / B ^ C / A | (B ^ C) / - // | 0 | 0 | 0 | 0 | 0 | - // | 0 | 0 | 1 | 1 | 1 | - // | 0 | 1 | 0 | 1 | 1 | - // | 0 | 1 | 1 | 0 | 0 | - // | 1 | 0 | 0 | 0 | 1 | - // | 1 | 0 | 1 | 1 | 1 | - // | 1 | 1 | 0 | 1 | 1 | - // | 1 | 1 | 1 | 0 | 1 | - - __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); - - // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask - // out which 32 bit elements in the vector had zero bytes. This uses a - // bit twiddling trick - // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord - __mmask16 zero_byte_mask = {}; - { - const __m512i v01 = _mm512_set1_epi32(0x01010101u); - const __m512i v80 = _mm512_set1_epi32(0x80808080u); - const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); - const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); - zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); - } - - while (zero_byte_mask) { - uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); - char const *base_ptr = ptr + (4 * lsb_zero_pos); - - if (DN_MEMCMP(base_ptr + 0, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data; - } else if (DN_MEMCMP(base_ptr + 1, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 1; - } else if (DN_MEMCMP(base_ptr + 2, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 2; - } else if (DN_MEMCMP(base_ptr + 3, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 3; - } - - if (result.found) { - result.start_to_before_match = DN_Str8_Init(string.data, result.index); - result.match = DN_Str8_Init(string.data + result.index, find.size); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - result.index); - result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find.size); - return result; - } - - zero_byte_mask = DN_Bit_ClearNextLSB(zero_byte_mask); - } - - ptr += sizeof(__m512i); - } - - for (DN_USize index = ptr - string.data; index < string.size; index++) { - DN_Str8 string_slice = DN_Str8_Slice(string, index, find.size); - if (DN_Str8_Eq(string_slice, find)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8_Init(string.data, index); - result.match = DN_Str8_Init(string.data + index, find.size); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find.size); - return result; - } - } - - return result; -} - -DN_API DN_Str8FindResult DN_Str8_FindLastStr8AVX512F(DN_Str8 string, DN_Str8 find) -{ - // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html - DN_Str8FindResult result = {}; - if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size) - return result; - - __m512i const find_first_ch = _mm512_set1_epi8(find.data[0]); - __m512i const find_last_ch = _mm512_set1_epi8(find.data[find.size - 1]); - - DN_USize const search_size = string.size - find.size; - DN_USize simd_iterations = search_size / sizeof(__m512i); - char const *ptr = string.data + search_size + 1; - - while (simd_iterations--) { - ptr -= sizeof(__m512i); - __m512i find_first_ch_block = _mm512_loadu_si512(ptr); - __m512i find_last_ch_block = _mm512_loadu_si512(ptr + find.size - 1); - - // NOTE: AVX512F does not have a cmpeq so we use XOR to place a 0 bit - // where matches are found. - __m512i first_ch_matches = _mm512_xor_si512(find_first_ch_block, find_first_ch); - - // NOTE: We can combine the 2nd XOR and merge the 2 XOR results into one - // operation using the ternarylogic intrinsic. - // - // A = first_ch_matches (find_first_ch_block ^ find_first_ch) - // B = find_last_ch_block - // C = find_last_ch - // - // ternarylogic op => A | (B ^ C) => 0b1111'0110 => 0xf6 - // - // / A / B / C / B ^ C / A | (B ^ C) / - // | 0 | 0 | 0 | 0 | 0 | - // | 0 | 0 | 1 | 1 | 1 | - // | 0 | 1 | 0 | 1 | 1 | - // | 0 | 1 | 1 | 0 | 0 | - // | 1 | 0 | 0 | 0 | 1 | - // | 1 | 0 | 1 | 1 | 1 | - // | 1 | 1 | 0 | 1 | 1 | - // | 1 | 1 | 1 | 0 | 1 | - - __m512i ch_matches = _mm512_ternarylogic_epi32(first_ch_matches, find_last_ch_block, find_last_ch, 0xf6); - - // NOTE: Matches were XOR-ed and are hence indicated as zero so we mask - // out which 32 bit elements in the vector had zero bytes. This uses a - // bit twiddling trick - // https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord - __mmask16 zero_byte_mask = {}; - { - const __m512i v01 = _mm512_set1_epi32(0x01010101u); - const __m512i v80 = _mm512_set1_epi32(0x80808080u); - const __m512i v1 = _mm512_sub_epi32(ch_matches, v01); - const __m512i tmp1 = _mm512_ternarylogic_epi32(v1, ch_matches, v80, 0x20); - zero_byte_mask = _mm512_test_epi32_mask(tmp1, tmp1); - } - - while (zero_byte_mask) { - uint64_t const lsb_zero_pos = _tzcnt_u64(zero_byte_mask); - char const *base_ptr = ptr + (4 * lsb_zero_pos); - - if (DN_MEMCMP(base_ptr + 0, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data; - } else if (DN_MEMCMP(base_ptr + 1, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 1; - } else if (DN_MEMCMP(base_ptr + 2, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 2; - } else if (DN_MEMCMP(base_ptr + 3, find.data, find.size) == 0) { - result.found = true; - result.index = base_ptr - string.data + 3; - } - - if (result.found) { - result.start_to_before_match = DN_Str8_Init(string.data, result.index); - result.match = DN_Str8_Init(string.data + result.index, find.size); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - result.index); - return result; - } - - zero_byte_mask = DN_Bit_ClearNextLSB(zero_byte_mask); - } - } - - for (DN_USize index = ptr - string.data - 1; index < string.size; index--) { - DN_Str8 string_slice = DN_Str8_Slice(string, index, find.size); - if (DN_Str8_Eq(string_slice, find)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8_Init(string.data, index); - result.match = DN_Str8_Init(string.data + index, find.size); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); - return result; - } - } - - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitAVX512F(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BinarySplitResult result = {}; - DN_Str8FindResult find_result = DN_Str8_FindStr8AVX512F(string, find); - if (find_result.found) { - result.lhs.data = string.data; - result.lhs.size = find_result.index; - result.rhs = DN_Str8_Advance(find_result.match_to_end_of_buffer, find.size); - } else { - result.lhs = string; - } - - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastAVX512F(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BinarySplitResult result = {}; - DN_Str8FindResult find_result = DN_Str8_FindLastStr8AVX512F(string, find); - if (find_result.found) { - result.lhs.data = string.data; - result.lhs.size = find_result.index; - result.rhs = DN_Str8_Advance(find_result.match_to_end_of_buffer, find.size); - } else { - result.lhs = string; - } - - return result; -} - -DN_API DN_USize DN_Str8_SplitAVX512F(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode) -{ - DN_USize result = 0; // The number of splits in the actual string. - if (!DN_Str8_HasData(string) || !DN_Str8_HasData(delimiter) || delimiter.size <= 0) - return result; - - DN_Str8BinarySplitResult split = {}; - DN_Str8 first = string; - do { - split = DN_Str8_BinarySplitAVX512F(first, delimiter); - if (split.lhs.size || mode == DN_Str8SplitIncludeEmptyStrings_Yes) { - if (splits && result < splits_count) - splits[result] = split.lhs; - result++; - } - first = split.rhs; - } while (first.size); - - return result; -} - -DN_API DN_Slice DN_Str8_SplitAllocAVX512F(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) -{ - DN_Slice result = {}; - DN_USize splits_required = DN_Str8_SplitAVX512F(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); - result.data = DN_Arena_NewArray(arena, DN_Str8, splits_required, DN_ZeroMem_No); - if (result.data) { - result.size = DN_Str8_SplitAVX512F(string, delimiter, result.data, splits_required, mode); - DN_ASSERT(splits_required == result.size); - } - return result; -} diff --git a/dqn_base.cpp b/dqn_base.cpp deleted file mode 100644 index f9bdbac..0000000 --- a/dqn_base.cpp +++ /dev/null @@ -1,837 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ -// $$ __$$\ -// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ -// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ -// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | -// $$ | $$ |$$ __$$ | \____$$\ $$ ____| -// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ -// \_______/ \_______|\_______/ \_______| -// -// dn_base.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// -#if !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) -#if defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) -#include -#endif - -DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; - -DN_API DN_CPUIDResult DN_CPU_ID(DN_CPUIDArgs args) -{ - DN_CPUIDResult result = {}; - __cpuidex(result.values, args.eax, args.ecx); - return result; -} - -DN_API DN_USize DN_CPU_HasFeatureArray(DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size) -{ - DN_USize result = 0; - DN_USize const BITS = sizeof(report->features[0]) * 8; - DN_FOR_UINDEX(feature_index, features_size) { - DN_CPUFeatureQuery *query = features + feature_index; - DN_USize chunk_index = query->feature / BITS; - DN_USize chunk_bit = query->feature % BITS; - uint64_t chunk = report->features[chunk_index]; - query->available = chunk & (1ULL << chunk_bit); - result += DN_CAST(int)query->available; - } - - return result; -} - -DN_API bool DN_CPU_HasFeature(DN_CPUReport const *report, DN_CPUFeature feature) -{ - DN_CPUFeatureQuery query = {}; - query.feature = feature; - bool result = DN_CPU_HasFeatureArray(report, &query, 1) == 1; - return result; -} - -DN_API bool DN_CPU_HasAllFeatures(DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size) -{ - bool result = true; - for (DN_USize index = 0; result && index < features_size; index++) - result &= DN_CPU_HasFeature(report, features[index]); - return result; -} - -DN_API void DN_CPU_SetFeature(DN_CPUReport *report, DN_CPUFeature feature) -{ - DN_ASSERT(feature < DN_CPUFeature_Count); - DN_USize const BITS = sizeof(report->features[0]) * 8; - DN_USize chunk_index = feature / BITS; - DN_USize chunk_bit = feature % BITS; - report->features[chunk_index] |= (1ULL << chunk_bit); -} - -DN_API DN_CPUReport DN_CPU_Report() -{ - DN_CPUReport result = {}; - DN_CPUIDResult fn_0000_[500] = {}; - DN_CPUIDResult fn_8000_[500] = {}; - int const EXTENDED_FUNC_BASE_EAX = 0x8000'0000; - int const REGISTER_SIZE = sizeof(fn_0000_[0].reg.eax); - - // NOTE: Query standard/extended numbers /////////////////////////////////////////////////////// - { - DN_CPUIDArgs args = {}; - - // NOTE: Query standard function (e.g. eax = 0x0) for function count + cpu vendor - args = {}; - fn_0000_[0] = DN_CPU_ID(args); - - // NOTE: Query extended function (e.g. eax = 0x8000'0000) for function count + cpu vendor - args = {}; - args.eax = DN_CAST(int) EXTENDED_FUNC_BASE_EAX; - fn_8000_[0] = DN_CPU_ID(args); - } - - // NOTE: Extract function count //////////////////////////////////////////////////////////////// - int const STANDARD_FUNC_MAX_EAX = fn_0000_[0x0000].reg.eax; - int const EXTENDED_FUNC_MAX_EAX = fn_8000_[0x0000].reg.eax; - - // NOTE: Enumerate all CPUID results for the known function counts ///////////////////////////// - { - DN_ASSERTF((STANDARD_FUNC_MAX_EAX + 1) <= DN_ARRAY_ICOUNT(fn_0000_), - "Max standard count is %d", STANDARD_FUNC_MAX_EAX + 1); - DN_ASSERTF((DN_CAST(DN_ISize)EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1) <= DN_ARRAY_ICOUNT(fn_8000_), - "Max extended count is %zu", DN_CAST(DN_ISize)EXTENDED_FUNC_MAX_EAX - EXTENDED_FUNC_BASE_EAX + 1); - - for (int eax = 1; eax <= STANDARD_FUNC_MAX_EAX; eax++) { - DN_CPUIDArgs args = {}; - args.eax = eax; - fn_0000_[eax] = DN_CPU_ID(args); - } - - for (int eax = EXTENDED_FUNC_BASE_EAX + 1, index = 1; eax <= EXTENDED_FUNC_MAX_EAX; eax++, index++) { - DN_CPUIDArgs args = {}; - args.eax = eax; - fn_8000_[index] = DN_CPU_ID(args); - } - } - - // NOTE: Query CPU vendor ////////////////////////////////////////////////////////////////////// - { - DN_MEMCPY(result.vendor + 0, &fn_8000_[0x0000].reg.ebx, REGISTER_SIZE); - DN_MEMCPY(result.vendor + 4, &fn_8000_[0x0000].reg.edx, REGISTER_SIZE); - DN_MEMCPY(result.vendor + 8, &fn_8000_[0x0000].reg.ecx, REGISTER_SIZE); - } - - // NOTE: Query CPU brand /////////////////////////////////////////////////////////////////////// - if (EXTENDED_FUNC_MAX_EAX >= (EXTENDED_FUNC_BASE_EAX + 4)) { - DN_MEMCPY(result.brand + 0, &fn_8000_[0x0002].reg.eax, REGISTER_SIZE); - DN_MEMCPY(result.brand + 4, &fn_8000_[0x0002].reg.ebx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 8, &fn_8000_[0x0002].reg.ecx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 12, &fn_8000_[0x0002].reg.edx, REGISTER_SIZE); - - DN_MEMCPY(result.brand + 16, &fn_8000_[0x0003].reg.eax, REGISTER_SIZE); - DN_MEMCPY(result.brand + 20, &fn_8000_[0x0003].reg.ebx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 24, &fn_8000_[0x0003].reg.ecx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 28, &fn_8000_[0x0003].reg.edx, REGISTER_SIZE); - - DN_MEMCPY(result.brand + 32, &fn_8000_[0x0004].reg.eax, REGISTER_SIZE); - DN_MEMCPY(result.brand + 36, &fn_8000_[0x0004].reg.ebx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 40, &fn_8000_[0x0004].reg.ecx, REGISTER_SIZE); - DN_MEMCPY(result.brand + 44, &fn_8000_[0x0004].reg.edx, REGISTER_SIZE); - - DN_ASSERT(result.brand[sizeof(result.brand) - 1] == 0); - } - - // NOTE: Query CPU features ////////////////////////////////////////////////////////////////// - for (DN_USize ext_index = 0; ext_index < DN_CPUFeature_Count; ext_index++) { - bool available = false; - - // NOTE: Mask bits taken from various manuals - // - AMD64 Architecture Programmer's Manual, Volumes 1-5 - // - https://en.wikipedia.org/wiki/CPUID#Calling_CPUID - switch (DN_CAST(DN_CPUFeature)ext_index) { - case DN_CPUFeature_3DNow: available = (fn_8000_[0x0001].reg.edx & (1 << 31)); break; - case DN_CPUFeature_3DNowExt: available = (fn_8000_[0x0001].reg.edx & (1 << 30)); break; - case DN_CPUFeature_ABM: available = (fn_8000_[0x0001].reg.ecx & (1 << 5)); break; - case DN_CPUFeature_AES: available = (fn_0000_[0x0001].reg.ecx & (1 << 25)); break; - case DN_CPUFeature_AVX: available = (fn_0000_[0x0001].reg.ecx & (1 << 28)); break; - case DN_CPUFeature_AVX2: available = (fn_0000_[0x0007].reg.ebx & (1 << 0)); break; - case DN_CPUFeature_AVX512F: available = (fn_0000_[0x0007].reg.ebx & (1 << 16)); break; - case DN_CPUFeature_AVX512DQ: available = (fn_0000_[0x0007].reg.ebx & (1 << 17)); break; - case DN_CPUFeature_AVX512IFMA: available = (fn_0000_[0x0007].reg.ebx & (1 << 21)); break; - case DN_CPUFeature_AVX512PF: available = (fn_0000_[0x0007].reg.ebx & (1 << 26)); break; - case DN_CPUFeature_AVX512ER: available = (fn_0000_[0x0007].reg.ebx & (1 << 27)); break; - case DN_CPUFeature_AVX512CD: available = (fn_0000_[0x0007].reg.ebx & (1 << 28)); break; - case DN_CPUFeature_AVX512BW: available = (fn_0000_[0x0007].reg.ebx & (1 << 30)); break; - case DN_CPUFeature_AVX512VL: available = (fn_0000_[0x0007].reg.ebx & (1 << 31)); break; - case DN_CPUFeature_AVX512VBMI: available = (fn_0000_[0x0007].reg.ecx & (1 << 1)); break; - case DN_CPUFeature_AVX512VBMI2: available = (fn_0000_[0x0007].reg.ecx & (1 << 6)); break; - case DN_CPUFeature_AVX512VNNI: available = (fn_0000_[0x0007].reg.ecx & (1 << 11)); break; - case DN_CPUFeature_AVX512BITALG: available = (fn_0000_[0x0007].reg.ecx & (1 << 12)); break; - case DN_CPUFeature_AVX512VPOPCNTDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 14)); break; - case DN_CPUFeature_AVX5124VNNIW: available = (fn_0000_[0x0007].reg.edx & (1 << 2)); break; - case DN_CPUFeature_AVX5124FMAPS: available = (fn_0000_[0x0007].reg.edx & (1 << 3)); break; - case DN_CPUFeature_AVX512VP2INTERSECT: available = (fn_0000_[0x0007].reg.edx & (1 << 8)); break; - case DN_CPUFeature_AVX512FP16: available = (fn_0000_[0x0007].reg.edx & (1 << 23)); break; - case DN_CPUFeature_CLZERO: available = (fn_8000_[0x0008].reg.ebx & (1 << 0)); break; - case DN_CPUFeature_CMPXCHG8B: available = (fn_0000_[0x0001].reg.edx & (1 << 8)); break; - case DN_CPUFeature_CMPXCHG16B: available = (fn_0000_[0x0001].reg.ecx & (1 << 13)); break; - case DN_CPUFeature_F16C: available = (fn_0000_[0x0001].reg.ecx & (1 << 29)); break; - case DN_CPUFeature_FMA: available = (fn_0000_[0x0001].reg.ecx & (1 << 12)); break; - case DN_CPUFeature_FMA4: available = (fn_8000_[0x0001].reg.ecx & (1 << 16)); break; - case DN_CPUFeature_FP128: available = (fn_8000_[0x001A].reg.eax & (1 << 0)); break; - case DN_CPUFeature_FP256: available = (fn_8000_[0x001A].reg.eax & (1 << 2)); break; - case DN_CPUFeature_FPU: available = (fn_0000_[0x0001].reg.edx & (1 << 0)); break; - case DN_CPUFeature_MMX: available = (fn_0000_[0x0001].reg.edx & (1 << 23)); break; - case DN_CPUFeature_MONITOR: available = (fn_0000_[0x0001].reg.ecx & (1 << 3)); break; - case DN_CPUFeature_MOVBE: available = (fn_0000_[0x0001].reg.ecx & (1 << 22)); break; - case DN_CPUFeature_MOVU: available = (fn_8000_[0x001A].reg.eax & (1 << 1)); break; - case DN_CPUFeature_MmxExt: available = (fn_8000_[0x0001].reg.edx & (1 << 22)); break; - case DN_CPUFeature_PCLMULQDQ: available = (fn_0000_[0x0001].reg.ecx & (1 << 1)); break; - case DN_CPUFeature_POPCNT: available = (fn_0000_[0x0001].reg.ecx & (1 << 23)); break; - case DN_CPUFeature_RDRAND: available = (fn_0000_[0x0001].reg.ecx & (1 << 30)); break; - case DN_CPUFeature_RDSEED: available = (fn_0000_[0x0007].reg.ebx & (1 << 18)); break; - case DN_CPUFeature_RDTSCP: available = (fn_8000_[0x0001].reg.edx & (1 << 27)); break; - case DN_CPUFeature_SHA: available = (fn_0000_[0x0007].reg.ebx & (1 << 29)); break; - case DN_CPUFeature_SSE: available = (fn_0000_[0x0001].reg.edx & (1 << 25)); break; - case DN_CPUFeature_SSE2: available = (fn_0000_[0x0001].reg.edx & (1 << 26)); break; - case DN_CPUFeature_SSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 0)); break; - case DN_CPUFeature_SSE41: available = (fn_0000_[0x0001].reg.ecx & (1 << 19)); break; - case DN_CPUFeature_SSE42: available = (fn_0000_[0x0001].reg.ecx & (1 << 20)); break; - case DN_CPUFeature_SSE4A: available = (fn_8000_[0x0001].reg.ecx & (1 << 6)); break; - case DN_CPUFeature_SSSE3: available = (fn_0000_[0x0001].reg.ecx & (1 << 9)); break; - case DN_CPUFeature_TSC: available = (fn_0000_[0x0001].reg.edx & (1 << 4)); break; - case DN_CPUFeature_TscInvariant: available = (fn_8000_[0x0007].reg.edx & (1 << 8)); break; - case DN_CPUFeature_VAES: available = (fn_0000_[0x0007].reg.ecx & (1 << 9)); break; - case DN_CPUFeature_VPCMULQDQ: available = (fn_0000_[0x0007].reg.ecx & (1 << 10)); break; - case DN_CPUFeature_Count: DN_INVALID_CODE_PATH; break; - } - - if (available) - DN_CPU_SetFeature(&result, DN_CAST(DN_CPUFeature)ext_index); - } - - return result; -} -#endif // !defined(DN_PLATFORM_ARM64) && !defined(DN_PLATFORM_EMSCRIPTEN) - -// NOTE: [$TMUT] DN_TicketMutex /////////////////////////////////////////////////////////////////// -DN_API void DN_TicketMutex_Begin(DN_TicketMutex *mutex) -{ - unsigned int ticket = DN_Atomic_AddU32(&mutex->ticket, 1); - DN_TicketMutex_BeginTicket(mutex, ticket); -} - -DN_API void DN_TicketMutex_End(DN_TicketMutex *mutex) -{ - DN_Atomic_AddU32(&mutex->serving, 1); -} - -DN_API DN_UInt DN_TicketMutex_MakeTicket(DN_TicketMutex *mutex) -{ - DN_UInt result = DN_Atomic_AddU32(&mutex->ticket, 1); - return result; -} - -DN_API void DN_TicketMutex_BeginTicket(DN_TicketMutex const *mutex, DN_UInt ticket) -{ - DN_ASSERTF(mutex->serving <= ticket, - "Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, " - "mutex->serving = %u", - ticket, - mutex->serving); - while (ticket != mutex->serving) { - // NOTE: Use spinlock intrinsic - _mm_pause(); - } -} - -DN_API bool DN_TicketMutex_CanLock(DN_TicketMutex const *mutex, DN_UInt ticket) -{ - bool result = (ticket == mutex->serving); - return result; -} - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #if !defined(DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED) - #undef _CRT_SECURE_NO_WARNINGS - #endif -#endif - -// NOTE: [$PRIN] DN_Print ///////////////////////////////////////////////////////////////////////// -DN_API DN_PrintStyle DN_Print_StyleColour(uint8_t r, uint8_t g, uint8_t b, DN_PrintBold bold) -{ - DN_PrintStyle result = {}; - result.bold = bold; - result.colour = true; - result.r = r; - result.g = g; - result.b = b; - return result; -} - -DN_API DN_PrintStyle DN_Print_StyleColourU32(uint32_t rgb, DN_PrintBold bold) -{ - uint8_t r = (rgb >> 24) & 0xFF; - uint8_t g = (rgb >> 16) & 0xFF; - uint8_t b = (rgb >> 8) & 0xFF; - DN_PrintStyle result = DN_Print_StyleColour(r, g, b, bold); - return result; -} - -DN_API DN_PrintStyle DN_Print_StyleBold() -{ - DN_PrintStyle result = {}; - result.bold = DN_PrintBold_Yes; - return result; -} - -DN_API void DN_Print_Std(DN_PrintStd std_handle, DN_Str8 string) -{ - DN_ASSERT(std_handle == DN_PrintStd_Out || std_handle == DN_PrintStd_Err); - - #if defined(DN_OS_WIN32) - // NOTE: Get the output handles from kernel //////////////////////////////////////////////////// - DN_THREAD_LOCAL void *std_out_print_handle = nullptr; - DN_THREAD_LOCAL void *std_err_print_handle = nullptr; - DN_THREAD_LOCAL bool std_out_print_to_console = false; - DN_THREAD_LOCAL bool std_err_print_to_console = false; - - if (!std_out_print_handle) { - unsigned long mode = 0; (void)mode; - std_out_print_handle = GetStdHandle(STD_OUTPUT_HANDLE); - std_out_print_to_console = GetConsoleMode(std_out_print_handle, &mode) != 0; - - std_err_print_handle = GetStdHandle(STD_ERROR_HANDLE); - std_err_print_to_console = GetConsoleMode(std_err_print_handle, &mode) != 0; - } - - // NOTE: Select the output handle ////////////////////////////////////////////////////////////// - void *print_handle = std_out_print_handle; - bool print_to_console = std_out_print_to_console; - if (std_handle == DN_PrintStd_Err) { - print_handle = std_err_print_handle; - print_to_console = std_err_print_to_console; - } - - // NOTE: Write the string ////////////////////////////////////////////////////////////////////// - DN_ASSERT(string.size < DN_CAST(unsigned long)-1); - unsigned long bytes_written = 0; (void)bytes_written; - if (print_to_console) { - WriteConsoleA(print_handle, string.data, DN_CAST(unsigned long)string.size, &bytes_written, nullptr); - } else { - WriteFile(print_handle, string.data, DN_CAST(unsigned long)string.size, &bytes_written, nullptr); - } - #else - fprintf(std_handle == DN_PrintStd_Out ? stdout : stderr, "%.*s", DN_STR_FMT(string)); - #endif -} - -DN_API void DN_Print_StdStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_Str8 string) -{ - if (string.data && string.size) { - if (style.colour) - DN_Print_Std(std_handle, DN_Print_ESCColourFgStr8(style.r, style.g, style.b)); - if (style.bold == DN_PrintBold_Yes) - DN_Print_Std(std_handle, DN_Print_ESCBoldStr8); - DN_Print_Std(std_handle, string); - if (style.colour || style.bold == DN_PrintBold_Yes) - DN_Print_Std(std_handle, DN_Print_ESCResetStr8); - } -} - -DN_API void DN_Print_StdF(DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Print_StdFV(std_handle, fmt, args); - va_end(args); -} - -DN_API void DN_Print_StdFStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Print_StdFVStyle(std_handle, style, fmt, args); - va_end(args); -} - -#if !defined(DN_USE_STD_PRINTF) -DN_FILE_SCOPE char *DN_Print_VSPrintfChunker_(const char *buf, void *user, int len) -{ - DN_Str8 string = {}; - string.data = DN_CAST(char *)buf; - string.size = len; - - DN_PrintStd std_handle = DN_CAST(DN_PrintStd)DN_CAST(uintptr_t)user; - DN_Print_Std(std_handle, string); - return (char *)buf; -} -#endif - -DN_API void DN_Print_StdFV(DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - #if defined(DN_USE_STD_PRINTF) - vfprintf(std_handle == DN_PrintStd_Out ? stdout : stderr, fmt, args); - #else - char buffer[STB_SPRINTF_MIN]; - STB_SPRINTF_DECORATE(vsprintfcb)(DN_Print_VSPrintfChunker_, DN_CAST(void *)DN_CAST(uintptr_t)std_handle, buffer, fmt, args); - #endif -} - -DN_API void DN_Print_StdFVStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (fmt) { - if (style.colour) - DN_Print_Std(std_handle, DN_Print_ESCColourFgStr8(style.r, style.g, style.b)); - if (style.bold == DN_PrintBold_Yes) - DN_Print_Std(std_handle, DN_Print_ESCBoldStr8); - DN_Print_StdFV(std_handle, fmt, args); - if (style.colour || style.bold == DN_PrintBold_Yes) - DN_Print_Std(std_handle, DN_Print_ESCResetStr8); - } -} - -DN_API void DN_Print_StdLn(DN_PrintStd std_handle, DN_Str8 string) -{ - DN_Print_Std(std_handle, string); - DN_Print_Std(std_handle, DN_STR8("\n")); -} - -DN_API void DN_Print_StdLnF(DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Print_StdLnFV(std_handle, fmt, args); - va_end(args); -} - -DN_API void DN_Print_StdLnFV(DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Print_StdFV(std_handle, fmt, args); - DN_Print_Std(std_handle, DN_STR8("\n")); -} - -DN_API void DN_Print_StdLnStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_Str8 string) -{ - DN_Print_StdStyle(std_handle, style, string); - DN_Print_Std(std_handle, DN_STR8("\n")); -} - -DN_API void DN_Print_StdLnFStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Print_StdLnFVStyle(std_handle, style, fmt, args); - va_end(args); -} - -DN_API void DN_Print_StdLnFVStyle(DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Print_StdFVStyle(std_handle, style, fmt, args); - DN_Print_Std(std_handle, DN_STR8("\n")); -} - -DN_API DN_Str8 DN_Print_ESCColourStr8(DN_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b) -{ - DN_THREAD_LOCAL char buffer[32]; - buffer[0] = 0; - DN_Str8 result = {}; - result.size = DN_SNPRINTF(buffer, - DN_ARRAY_UCOUNT(buffer), - "\x1b[%d;2;%u;%u;%um", - colour == DN_PrintESCColour_Fg ? 38 : 48, - r, g, b); - result.data = buffer; - return result; -} - -DN_API DN_Str8 DN_Print_ESCColourU32Str8(DN_PrintESCColour colour, uint32_t value) -{ - uint8_t r = DN_CAST(uint8_t)(value >> 24); - uint8_t g = DN_CAST(uint8_t)(value >> 16); - uint8_t b = DN_CAST(uint8_t)(value >> 8); - DN_Str8 result = DN_Print_ESCColourStr8(colour, r, g, b); - return result; -} - -// NOTE: [$LLOG] DN_Log /////////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_Log_MakeStr8(DN_Arena *arena, - bool colour, - DN_Str8 type, - int log_type, - DN_CallSite call_site, - DN_FMT_ATTRIB char const *fmt, - va_list args) -{ - DN_LOCAL_PERSIST DN_USize max_type_length = 0; - max_type_length = DN_MAX(max_type_length, type.size); - int type_padding = DN_CAST(int)(max_type_length - type.size); - - DN_Str8 colour_esc = {}; - DN_Str8 bold_esc = {}; - DN_Str8 reset_esc = {}; - if (colour) { - bold_esc = DN_Print_ESCBoldStr8; - reset_esc = DN_Print_ESCResetStr8; - switch (log_type) { - case DN_LogType_Debug: break; - case DN_LogType_Info: colour_esc = DN_Print_ESCColourFgU32Str8(DN_LogTypeColourU32_Info); break; - case DN_LogType_Warning: colour_esc = DN_Print_ESCColourFgU32Str8(DN_LogTypeColourU32_Warning); break; - case DN_LogType_Error: colour_esc = DN_Print_ESCColourFgU32Str8(DN_LogTypeColourU32_Error); break; - } - } - - DN_Str8 file_name = DN_Str8_FileNameFromPath(call_site.file); - DN_OSDateTimeStr8 const time = DN_OS_DateLocalTimeStr8Now(); - DN_Str8 header = DN_Str8_InitF(arena, - "%.*s " // date - "%.*s " // hms - "%.*s" // colour - "%.*s" // bold - "%.*s" // type - "%*s" // type padding - "%.*s" // reset - " %.*s" // file name - ":%05I32u " // line number - , - DN_CAST(int)time.date_size - 2, time.date + 2, // date - DN_CAST(int)time.hms_size, time.hms, // hms - DN_STR_FMT(colour_esc), // colour - DN_STR_FMT(bold_esc), // bold - DN_STR_FMT(type), // type - DN_CAST(int)type_padding, "", // type padding - DN_STR_FMT(reset_esc), // reset - DN_STR_FMT(file_name), // file name - call_site.line); // line number - DN_USize header_size_no_ansi_codes = header.size - colour_esc.size - DN_Print_ESCResetStr8.size; - - // NOTE: Header padding //////////////////////////////////////////////////////////////////////// - DN_LOCAL_PERSIST DN_USize max_header_length = 0; - max_header_length = DN_MAX(max_header_length, header_size_no_ansi_codes); - DN_USize header_padding = max_header_length - header_size_no_ansi_codes; - - // NOTE: Construct final log /////////////////////////////////////////////////////////////////// - DN_Str8 user_msg = DN_Str8_InitFV(arena, fmt, args); - DN_Str8 result = DN_Str8_Alloc(arena, header.size + header_padding + user_msg.size, DN_ZeroMem_No); - if (DN_Str8_HasData(result)) { - DN_MEMCPY(result.data, header.data, header.size); - DN_MEMSET(result.data + header.size, ' ', header_padding); - if (DN_Str8_HasData(user_msg)) - DN_MEMCPY(result.data + header.size + header_padding, user_msg.data, user_msg.size); - } - return result; -} - -DN_FILE_SCOPE void DN_Log_FVDefault_(DN_Str8 type, int log_type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Core *core = g_dn_core; - (void)log_type; - (void)user_data; - - // NOTE: Open log file for appending if requested ////////////////////////// - DN_TicketMutex_Begin(&core->log_file_mutex); - if (core->log_to_file && !core->log_file.handle && !core->log_file.error) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 log_path = DN_OS_PathF(tmem.arena, "%.*s/dn.log", DN_STR_FMT(core->exe_dir)); - core->log_file = DN_OS_FileOpen(log_path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_AppendOnly, nullptr); - } - DN_TicketMutex_End(&core->log_file_mutex); - - // NOTE: Generate the log header /////////////////////////////////////////// - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 log_line = DN_Log_MakeStr8(tmem.arena, !core->log_no_colour, type, log_type, call_site, fmt, args); - - // NOTE: Print log ///////////////////////////////////////////////////////// - DN_Print_StdLn(log_type == DN_LogType_Error ? DN_PrintStd_Err : DN_PrintStd_Out, log_line); - - DN_TicketMutex_Begin(&core->log_file_mutex); - DN_OS_FileWrite(&core->log_file, log_line, nullptr); - DN_OS_FileWrite(&core->log_file, DN_STR8("\n"), nullptr); - DN_TicketMutex_End(&core->log_file_mutex); -} - -DN_API void DN_Log_FVCallSite(DN_Str8 type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (g_dn_core) { - DN_LogProc *logging_function = g_dn_core->log_callback ? g_dn_core->log_callback : DN_Log_FVDefault_; - logging_function(type, -1 /*log_type*/, g_dn_core->log_user_data, call_site, fmt, args); - } else { - // NOTE: Rarely taken branch, only when trying to use this library without initialising it - DN_Print_StdLnFV(DN_PrintStd_Out, fmt, args); - } -} - -DN_API void DN_Log_FCallSite(DN_Str8 type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Log_FVCallSite(type, call_site, fmt, args); - va_end(args); -} - -DN_API void DN_Log_TypeFVCallSite(DN_LogType type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 type_string = DN_STR8("DN-BAD-LOG-TYPE"); - switch (type) { - case DN_LogType_Error: type_string = DN_STR8("ERROR"); break; - case DN_LogType_Info: type_string = DN_STR8("INFO"); break; - case DN_LogType_Warning: type_string = DN_STR8("WARN"); break; - case DN_LogType_Debug: type_string = DN_STR8("DEBUG"); break; - case DN_LogType_Count: type_string = DN_STR8("BADXX"); break; - } - DN_Log_FVCallSite(type_string, call_site, fmt, args); -} - -DN_API void DN_Log_TypeFCallSite(DN_LogType type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Log_TypeFVCallSite(type, call_site, fmt, args); - va_end(args); -} - -// NOTE: [$ERRS] DN_ErrSink ///////////////////////////////////////////////////////////////////// -DN_FILE_SCOPE void DN_ErrSink_Check_(DN_ErrSink const *err) -{ - DN_ASSERTF(err->arena, "Arena should be assigned in TLS init"); - if (!err->stack) - return; - - DN_ErrSinkNode *node = err->stack; - DN_ASSERT(node->mode >= DN_ErrSinkMode_Nil && node->mode <= DN_ErrSinkMode_ExitOnError); - DN_ASSERTF(node->next != node, "Error nodes cannot point to themselves"); - DN_ASSERT(node->msg_sentinel); - - // NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a - // well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the - // walk limit or dereference a null pointer and assert) - size_t WALK_LIMIT = 99'999; - size_t walk = 0; - for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) { - DN_ASSERTF(it, "Encountered null pointer which should not happen in a sentinel DLL"); - DN_ASSERT(walk < WALK_LIMIT); - } -} - -DN_API DN_ErrSink *DN_ErrSink_Begin(DN_ErrSinkMode mode) -{ - DN_TLS *tls = DN_TLS_Get(); - DN_ErrSink *result = &tls->err_sink; - DN_USize arena_pos = DN_Arena_Pos(result->arena); - DN_ErrSinkNode *node = DN_Arena_New(result->arena, DN_ErrSinkNode, DN_ZeroMem_Yes); - if (node) { - node->next = result->stack; - node->arena_pos = arena_pos; - node->mode = mode; - DN_SentinelDLL_InitArena(node->msg_sentinel, DN_ErrSinkMsg, result->arena); - - // NOTE: Put the node at the front of the stack (e.g. sorted newest to oldest) - result->stack = node; - result->debug_open_close_counter++; - } - - // NOTE: Handle allocation error - if (!DN_CHECK(node && node->msg_sentinel)) - DN_Arena_PopTo(result->arena, arena_pos); - - return result; -} - -DN_API bool DN_ErrSink_HasError(DN_ErrSink *err) -{ - bool result = err && DN_SentinelDLL_HasItems(err->stack->msg_sentinel); - return result; -} - -DN_API DN_ErrSinkMsg *DN_ErrSink_End(DN_Arena *arena, DN_ErrSink *err) -{ - DN_ErrSinkMsg *result = nullptr; - DN_ErrSink_Check_(err); - if (!err || !err->stack) - return result; - - DN_ASSERTF(arena != err->arena, - "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); - - // NOTE: Walk the list and allocate it onto the user's arena - DN_ErrSinkNode *node = err->stack; - DN_ErrSinkMsg *prev = nullptr; - for (DN_ErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) { - DN_ErrSinkMsg *entry = DN_Arena_New(arena, DN_ErrSinkMsg, DN_ZeroMem_Yes); - entry->msg = DN_Str8_Copy(arena, it->msg); - entry->call_site = it->call_site; - entry->error_code = it->error_code; - if (!result) - result = entry; // Assign first entry if we haven't yet - if (prev) - prev->next = entry; // Link the prev message to the current one - prev = entry; // Update prev to latest - } - - // NOTE: Deallocate all the memory for this scope - err->stack = err->stack->next; - DN_ASSERT(err->debug_open_close_counter); - err->debug_open_close_counter--; - DN_Arena_PopTo(err->arena, node->arena_pos); - return result; -} - -static void DN_ErrSink_AddMsgToStr8Builder_(DN_Str8Builder *builder, DN_ErrSinkMsg *msg, DN_ErrSinkMsg *end) -{ - if (msg == end) // NOTE: No error messages to add - return; - - if (msg->next == end) { - DN_ErrSinkMsg *it = msg; - DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); - DN_Str8Builder_AppendF(builder, - "%.*s:%05I32u:%.*s %.*s", - DN_STR_FMT(file_name), - it->call_site.line, - DN_STR_FMT(it->call_site.function), - DN_STR_FMT(it->msg)); - } else { - // NOTE: More than one message - for (DN_ErrSinkMsg *it = msg; it != end; it = it->next) { - DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file); - DN_Str8Builder_AppendF(builder, - "%s - %.*s:%05I32u:%.*s%s%.*s", - it == msg ? "" : "\n", - DN_STR_FMT(file_name), - it->call_site.line, - DN_STR_FMT(it->call_site.function), - DN_Str8_HasData(it->msg) ? " " : "", - DN_STR_FMT(it->msg)); - } - } -} - -DN_API DN_Str8 DN_ErrSink_EndStr8(DN_Arena *arena, DN_ErrSink *err) -{ - DN_Str8 result = {}; - DN_ErrSink_Check_(err); - if (!err || !err->stack) - return result; - - DN_ASSERTF(arena != err->arena, - "You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost"); - - // NOTE: Walk the list and allocate it onto the user's arena - DN_TLSTMem tmem = DN_TLS_PushTMem(arena); - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - DN_ErrSink_AddMsgToStr8Builder_(&builder, err->stack->msg_sentinel->next, err->stack->msg_sentinel); - - // NOTE: Deallocate all the memory for this scope - uint64_t arena_pos = err->stack->arena_pos; - err->stack = err->stack->next; - DN_Arena_PopTo(err->arena, arena_pos); - DN_ASSERT(err->debug_open_close_counter); - err->debug_open_close_counter--; - - result = DN_Str8Builder_Build(&builder, arena); - return result; -} - -DN_API void DN_ErrSink_EndAndIgnore(DN_ErrSink *err) -{ - DN_ErrSink_End(nullptr, err); -} - -DN_API bool DN_ErrSink_EndAndLogError_(DN_ErrSink *err, DN_CallSite call_site, DN_Str8 err_msg) -{ - DN_ASSERTF(err->stack && err->stack->msg_sentinel, "Begin must be called before calling end"); - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_ErrSinkMode mode = err->stack->mode; - DN_ErrSinkMsg *msg = DN_ErrSink_End(tmem.arena, err); - if (!msg) - return false; - - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - if (DN_Str8_HasData(err_msg)) { - DN_Str8Builder_AppendRef(&builder, err_msg); - DN_Str8Builder_AppendRef(&builder, DN_STR8(":")); - } else { - DN_Str8Builder_AppendRef(&builder, DN_STR8("Error(s) encountered:")); - } - - if (msg->next) // NOTE: More than 1 message - DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); - DN_ErrSink_AddMsgToStr8Builder_(&builder, msg, nullptr); - - DN_Str8 log = DN_Str8Builder_Build_TLS(&builder); - DN_Log_TypeFCallSite(DN_LogType_Error, call_site, "%.*s", DN_STR_FMT(log)); - - if (mode == DN_ErrSinkMode_DebugBreakOnEndAndLog) { - DN_DEBUG_BREAK; - } - return true; -} - -DN_API bool DN_ErrSink_EndAndLogErrorFV_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); - bool result = DN_ErrSink_EndAndLogError_(err, call_site, log); - return result; -} - -DN_API bool DN_ErrSink_EndAndLogErrorF_(DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args); - bool result = DN_ErrSink_EndAndLogError_(err, call_site, log); - va_end(args); - return result; -} - -DN_API void DN_ErrSink_EndAndExitIfErrorFV_(DN_ErrSink *err, DN_CallSite call_site, uint32_t exit_val, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (DN_ErrSink_EndAndLogErrorFV_(err, call_site, fmt, args)) { - DN_DEBUG_BREAK; - DN_OS_Exit(exit_val); - } -} - -DN_API void DN_ErrSink_EndAndExitIfErrorF_(DN_ErrSink *err, DN_CallSite call_site, uint32_t exit_val, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_ErrSink_EndAndExitIfErrorFV_(err, call_site, exit_val, fmt, args); - va_end(args); -} - -DN_API void DN_ErrSink_AppendFV_(DN_ErrSink *err, uint32_t error_code, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - if (!err) - return; - - DN_ErrSinkNode *node = err->stack; - DN_ASSERTF(node, "Error sink must be begun by calling 'Begin' before using this function."); - - DN_ErrSinkMsg *msg = DN_Arena_New(err->arena, DN_ErrSinkMsg, DN_ZeroMem_Yes); - if (DN_CHECK(msg)) { - msg->msg = DN_Str8_InitFV(err->arena, fmt, args); - msg->error_code = error_code; - msg->call_site = DN_TLS_Get()->call_site; - DN_SentinelDLL_Prepend(node->msg_sentinel, msg); - - if (node->mode == DN_ErrSinkMode_ExitOnError) - DN_ErrSink_EndAndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code); - } -} - -DN_API void DN_ErrSink_AppendF_(DN_ErrSink *err, uint32_t error_code, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_ErrSink_AppendFV_(err, error_code, fmt, args); - va_end(args); -} diff --git a/dqn_base.h b/dqn_base.h deleted file mode 100644 index 26a9bcb..0000000 --- a/dqn_base.h +++ /dev/null @@ -1,937 +0,0 @@ -/* -/////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ -// $$ __$$\ -// $$ | $$ | $$$$$$\ $$$$$$$\ $$$$$$\ -// $$$$$$$\ | \____$$\ $$ _____|$$ __$$\ -// $$ __$$\ $$$$$$$ |\$$$$$$\ $$$$$$$$ | -// $$ | $$ |$$ __$$ | \____$$\ $$ ____| -// $$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ -// \_______/ \_______|\_______/ \_______| -// -// dqn_base.h -- Base primitives for the library -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$MACR] Macros -- General macros -// [$TYPE] Types -- Basic types and typedefs -// [$SDLL] DN_SentinelDLL -- Doubly linked list w/ sentinel macros -// [$INTR] Intrinsics -- Platform agnostic functions for CPU instructions (e.g. atomics, cpuid, ...) -// [$CALL] DN_CallSite -- Source code location/tracing -// [$TMUT] DN_TicketMutex -- Userland mutex via spinlocking atomics -// [$PRIN] DN_Print -- Console printing -// [$LLOG] DN_Log -- Console logging macros -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$MACR] Macros //////////////////////////////////////////////////////////////////////////// -#define DN_STRINGIFY(x) #x -#define DN_TOKEN_COMBINE2(x, y) x ## y -#define DN_TOKEN_COMBINE(x, y) DN_TOKEN_COMBINE2(x, y) - -// NOTE: Warning! Order is important here, clang-cl on Windows defines _MSC_VER -#if defined(_MSC_VER) - #if defined(__clang__) - #define DN_COMPILER_CLANG_CL - #define DN_COMPILER_CLANG - #else - #define DN_COMPILER_MSVC - #endif -#elif defined(__clang__) - #define DN_COMPILER_CLANG -#elif defined(__GNUC__) - #define DN_COMPILER_GCC -#endif - -// NOTE: Declare struct literals that work in both C and C++ because the syntax -// is different between languages. -#if 0 - struct Foo { int a; } - struct Foo foo = DN_LITERAL(Foo){32}; // Works on both C and C++ -#endif - -#if defined(__cplusplus) - #define DN_LITERAL(T) T -#else - #define DN_LITERAL(T) (T) -#endif - -#if defined(__cplusplus) - #define DN_THREAD_LOCAL thread_local -#else - #define DN_THREAD_LOCAL _Thread_local -#endif - -#if defined(_WIN32) - #define DN_OS_WIN32 -#elif defined(__gnu_linux__) || defined(__linux__) - #define DN_OS_UNIX -#endif - -#include -#include -#include -#include -#include // PRIu64... - -#if !defined(DN_OS_WIN32) -#include // exit() -#endif - -#if !defined(DN_PLATFORM_EMSCRIPTEN) && \ - !defined(DN_PLATFORM_POSIX) && \ - !defined(DN_PLATFORM_WIN32) - #if defined(__aarch64__) || defined(_M_ARM64) - #define DN_PLATFORM_ARM64 - #elif defined(__EMSCRIPTEN__) - #define DN_PLATFORM_EMSCRIPTEN - #elif defined(DN_OS_WIN32) - #define DN_PLATFORM_WIN32 - #else - #define DN_PLATFORM_POSIX - #endif -#endif - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #if defined(_CRT_SECURE_NO_WARNINGS) - #define DN_CRT_SECURE_NO_WARNINGS_PREVIOUSLY_DEFINED - #else - #define _CRT_SECURE_NO_WARNINGS - #endif -#endif - -#if defined(DN_COMPILER_MSVC) - #define DN_FMT_ATTRIB _Printf_format_string_ - #define DN_MSVC_WARNING_PUSH __pragma(warning(push)) - #define DN_MSVC_WARNING_DISABLE(...) __pragma(warning(disable: ##__VA_ARGS__)) - #define DN_MSVC_WARNING_POP __pragma(warning(pop)) -#else - #define DN_FMT_ATTRIB - #define DN_MSVC_WARNING_PUSH - #define DN_MSVC_WARNING_DISABLE(...) - #define DN_MSVC_WARNING_POP -#endif - -#if defined(DN_COMPILER_CLANG) || defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG_CL) - #define DN_GCC_WARNING_PUSH _Pragma("GCC diagnostic push") - #define DN_GCC_WARNING_DISABLE_HELPER_0(x) #x - #define DN_GCC_WARNING_DISABLE_HELPER_1(y) DN_GCC_WARNING_DISABLE_HELPER_0(GCC diagnostic ignored #y) - #define DN_GCC_WARNING_DISABLE(warning) _Pragma(DN_GCC_WARNING_DISABLE_HELPER_1(warning)) - #define DN_GCC_WARNING_POP _Pragma("GCC diagnostic pop") -#else - #define DN_GCC_WARNING_PUSH - #define DN_GCC_WARNING_DISABLE(...) - #define DN_GCC_WARNING_POP -#endif - -// NOTE: MSVC does not support the feature detection macro for instance so we -// compile it out -#if defined(__has_feature) - #define DN_HAS_FEATURE(expr) __has_feature(expr) -#else - #define DN_HAS_FEATURE(expr) 0 -#endif - -#define DN_FOR_UINDEX(index, size) for (DN_USize index = 0; index < size; index++) -#define DN_FOR_IINDEX(index, size) for (DN_isize index = 0; index < size; index++) - -#define DN_ForItSize(it, T, array, size) for (struct { USize index; T *data; } it = {0, &(array)[0]}; it.index < (size); it.index++, it.data++) -#define DN_ForIt(it, T, array) for (struct { USize index; T *data; } it = {0, &(array)->data[0]}; it.index < (array)->size; it.index++, it.data++) -#define DN_ForItCArray(it, T, array) for (struct { USize index; T *data; } it = {0, &(array)[0]}; it.index < DN_ARRAY_UCOUNT(array); it.index++, it.data++) - -#define DN_AlignUpPowerOfTwo(value, pot) (((uintptr_t)(value) + ((uintptr_t)(pot) - 1)) & ~((uintptr_t)(pot) - 1)) -#define DN_AlignDownPowerOfTwo(value, pot) ((uintptr_t)(value) & ~((uintptr_t)(pot) - 1)) -#define DN_IsPowerOfTwo(value) ((((uintptr_t)(value)) & (((uintptr_t)(value)) - 1)) == 0) -#define DN_IsPowerOfTwoAligned(value, pot) ((((uintptr_t)value) & (((uintptr_t)pot) - 1)) == 0) - -// NOTE: String.h Dependencies ///////////////////////////////////////////////////////////////////// -#if !defined(DN_MEMCPY) || !defined(DN_MEMSET) || !defined(DN_MEMCMP) || !defined(DN_MEMMOVE) - #include - #if !defined(DN_MEMCPY) - #define DN_MEMCPY(dest, src, count) memcpy((dest), (src), (count)) - #endif - #if !defined(DN_MEMSET) - #define DN_MEMSET(dest, value, count) memset((dest), (value), (count)) - #endif - #if !defined(DN_MEMCMP) - #define DN_MEMCMP(lhs, rhs, count) memcmp((lhs), (rhs), (count)) - #endif - #if !defined(DN_MEMMOVE) - #define DN_MEMMOVE(dest, src, count) memmove((dest), (src), (count)) - #endif -#endif - -// NOTE: Math.h Dependencies /////////////////////////////////////////////////////////////////////// -#if !defined(DN_SQRTF) || !defined(DN_SINF) || !defined(DN_COSF) || !defined(DN_TANF) - #include - #if !defined(DN_SQRTF) - #define DN_SQRTF(val) sqrtf(val) - #endif - #if !defined(DN_SINF) - #define DN_SINF(val) sinf(val) - #endif - #if !defined(DN_COSF) - #define DN_COSF(val) cosf(val) - #endif - #if !defined(DN_TANF) - #define DN_TANF(val) tanf(val) - #endif -#endif - -// NOTE: Math ////////////////////////////////////////////////////////////////////////////////////// -#define DN_PI 3.14159265359f - -#define DN_DEGREE_TO_RADIAN(degrees) ((degrees) * (DN_PI / 180.0f)) -#define DN_RADIAN_TO_DEGREE(radians) ((radians) * (180.f * DN_PI)) - -#define DN_ABS(val) (((val) < 0) ? (-(val)) : (val)) -#define DN_MAX(a, b) (((a) > (b)) ? (a) : (b)) -#define DN_MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define DN_CLAMP(val, lo, hi) DN_MAX(DN_MIN(val, hi), lo) -#define DN_SQUARED(val) ((val) * (val)) - -#define DN_SWAP(a, b) \ - do \ - { \ - auto temp = a; \ - a = b; \ - b = temp; \ - } while (0) - -// NOTE: Function/Variable Annotations ///////////////////////////////////////////////////////////// -#if defined(DN_STATIC_API) - #define DN_API static -#else - #define DN_API -#endif - -#define DN_LOCAL_PERSIST static -#define DN_FILE_SCOPE static -#define DN_CAST(val) (val) - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #define DN_FORCE_INLINE __forceinline -#else - #define DN_FORCE_INLINE inline __attribute__((always_inline)) -#endif - -// NOTE: Size ////////////////////////////////////////////////////////////////////////////////////// -#define DN_ISIZEOF(val) DN_CAST(ptrdiff_t)sizeof(val) -#define DN_ARRAY_UCOUNT(array) (sizeof(array)/(sizeof((array)[0]))) -#define DN_ARRAY_ICOUNT(array) (DN_ISize)DN_ARRAY_UCOUNT(array) -#define DN_CHAR_COUNT(string) (sizeof(string) - 1) - -// NOTE: SI Byte /////////////////////////////////////////////////////////////////////////////////// -#define DN_BYTES(val) ((uint64_t)val) -#define DN_KILOBYTES(val) ((uint64_t)1024 * DN_BYTES(val)) -#define DN_MEGABYTES(val) ((uint64_t)1024 * DN_KILOBYTES(val)) -#define DN_GIGABYTES(val) ((uint64_t)1024 * DN_MEGABYTES(val)) - -// NOTE: Time ////////////////////////////////////////////////////////////////////////////////////// -#define DN_SECONDS_TO_MS(val) ((val) * 1000) -#define DN_MINS_TO_S(val) ((val) * 60ULL) -#define DN_HOURS_TO_S(val) (DN_MINS_TO_S(val) * 60ULL) -#define DN_DAYS_TO_S(val) (DN_HOURS_TO_S(val) * 24ULL) -#define DN_WEEKS_TO_S(val) (DN_DAYS_TO_S(val) * 7ULL) -#define DN_YEARS_TO_S(val) (DN_WEEKS_TO_S(val) * 52ULL) - -#if defined(__has_builtin) - #define DN_HAS_BUILTIN(expr) __has_builtin(expr) -#else - #define DN_HAS_BUILTIN(expr) 0 -#endif - -// NOTE: Debug Break /////////////////////////////////////////////////////////////////////////////// -#if !defined(DN_DEBUG_BREAK) - #if defined(NDEBUG) - #define DN_DEBUG_BREAK - #else - #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #define DN_DEBUG_BREAK __debugbreak() - #elif DN_HAS_BUILTIN(__builtin_debugtrap) - #define DN_DEBUG_BREAK __builtin_debugtrap() - #elif DN_HAS_BUILTIN(__builtin_trap) || defined(DN_COMPILER_GCC) - #define DN_DEBUG_BREAK __builtin_trap() - #else - #include - #if defined(SIGTRAP) - #define DN_DEBUG_BREAK raise(SIGTRAP) - #else - #define DN_DEBUG_BREAK raise(SIGABRT) - #endif - #endif - #endif -#endif - -// NOTE: Assert Macros ///////////////////////////////////////////////////////////////////////////// -#define DN_HARD_ASSERT(expr) DN_HARD_ASSERTF(expr, "") -#define DN_HARD_ASSERTF(expr, fmt, ...) \ - do { \ - if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8CRT(128 /*limit*/, 2 /*skip*/); \ - DN_Log_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ - DN_STR_FMT(stack_trace_), \ - ##__VA_ARGS__); \ - DN_DEBUG_BREAK; \ - } \ - } while (0) - -#if defined(DN_NO_ASSERT) - #define DN_ASSERT(...) - #define DN_ASSERT_ONCE(...) - #define DN_ASSERTF(...) - #define DN_ASSERTF_ONCE(...) -#else - #define DN_ASSERT(expr) DN_ASSERTF((expr), "") - #define DN_ASSERT_ONCE(expr) DN_ASSERTF_ONCE((expr), "") - - #define DN_ASSERTF(expr, fmt, ...) \ - do { \ - if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8CRT(128 /*limit*/, 2 /*skip*/); \ - DN_Log_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ - DN_STR_FMT(stack_trace_), \ - ##__VA_ARGS__); \ - DN_DEBUG_BREAK; \ - } \ - } while (0) - - #define DN_ASSERTF_ONCE(expr, fmt, ...) \ - do { \ - static bool once = true; \ - if (!(expr) && once) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8CRT(128 /*limit*/, 2 /*skip*/); \ - DN_Log_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ - DN_STR_FMT(stack_trace_), \ - ##__VA_ARGS__); \ - once = false; \ - DN_DEBUG_BREAK; \ - } \ - } while (0) -#endif - -#define DN_INVALID_CODE_PATHF(fmt, ...) DN_HARD_ASSERTF(0, fmt, ##__VA_ARGS__) -#define DN_INVALID_CODE_PATH DN_INVALID_CODE_PATHF("Invalid code path triggered") - -// NOTE: Check macro /////////////////////////////////////////////////////////////////////////////// -#define DN_CHECK(expr) DN_CHECKF(expr, "") - -#if defined(DN_NO_CHECK_BREAK) - #define DN_CHECKF(expr, fmt, ...) \ - ((expr) ? true : (DN_Log_TypeFCallSite(DN_LogType_Warning, DN_CALL_SITE, fmt, ## __VA_ARGS__), false)) -#else - #define DN_CHECKF(expr, fmt, ...) \ - ((expr) ? true : (DN_Log_TypeFCallSite(DN_LogType_Error, DN_CALL_SITE, fmt, ## __VA_ARGS__), DN_StackTrace_Print(128 /*limit*/), DN_DEBUG_BREAK, false)) -#endif - -// NOTE: Zero initialisation macro ///////////////////////////////////////////////////////////////// -#if defined(__cplusplus) - #define DN_ZERO_INIT {} -#else - #define DN_ZERO_INIT {0} -#endif - -// NOTE: Defer Macro /////////////////////////////////////////////////////////////////////////////// -#if defined(__cplusplus) -template -struct DN_Defer -{ - Procedure proc; - DN_Defer(Procedure p) : proc(p) {} - ~DN_Defer() { proc(); } -}; - -struct DN_DeferHelper -{ - template - DN_Defer operator+(Lambda lambda) { return DN_Defer(lambda); }; -}; - -#define DN_UNIQUE_NAME(prefix) DN_TOKEN_COMBINE(prefix, __LINE__) -#define DN_DEFER const auto DN_UNIQUE_NAME(defer_lambda_) = DN_DeferHelper() + [&]() -#endif // defined(__cplusplus) - -#define DN_DEFER_LOOP(begin, end) \ - for (bool DN_UNIQUE_NAME(once) = (begin, true); \ - DN_UNIQUE_NAME(once); \ - end, DN_UNIQUE_NAME(once) = false) - -// NOTE: [$TYPE] Types ///////////////////////////////////////////////////////////////////////////// -typedef intptr_t DN_ISize; -typedef uintptr_t DN_USize; - -typedef int8_t DN_I8; -typedef int16_t DN_I16; -typedef int32_t DN_I32; -typedef int64_t DN_I64; - -typedef uint8_t DN_U8; -typedef uint16_t DN_U16; -typedef uint32_t DN_U32; -typedef uint64_t DN_U64; - -typedef float DN_F32; -typedef double DN_F64; -typedef unsigned int DN_UInt; -typedef int32_t DN_B32; - -#define DN_F32_MAX 3.402823466e+38F -#define DN_F32_MIN 1.175494351e-38F -#define DN_F64_MAX 1.7976931348623158e+308 -#define DN_F64_MIN 2.2250738585072014e-308 -#define DN_USIZE_MAX UINTPTR_MAX -#define DN_ISIZE_MAX INTPTR_MAX -#define DN_ISIZE_MIN INTPTR_MIN - -enum DN_ZeroMem -{ - DN_ZeroMem_No, // Memory can be handed out without zero-ing it out - DN_ZeroMem_Yes, // Memory should be zero-ed out before giving to the callee -}; - -struct DN_Str8 -{ - char *data; // The bytes of the string - DN_USize size; // The number of bytes in the string - - char const *begin() const { return data; } - char const *end () const { return data + size; } - char *begin() { return data; } - char *end () { return data + size; } -}; - -template struct DN_Slice // A pointer and length container of data -{ - T *data; - DN_USize size; - - T *begin() { return data; } - T *end () { return data + size; } - T const *begin() const { return data; } - T const *end () const { return data + size; } -}; - -// NOTE: [$CALL] DN_CallSite ////////////////////////////////////////////////////////////////////// -struct DN_CallSite -{ - DN_Str8 file; - DN_Str8 function; - uint32_t line; -}; -#define DN_CALL_SITE DN_CallSite{DN_STR8(__FILE__), DN_STR8(__func__), __LINE__} - -// NOTE: [$ErrS] DN_ErrSink ///////////////////////////////////////////////////////////////////// -enum DN_ErrSinkMode -{ - // Default behaviour to accumulate errors into the sink - DN_ErrSinkMode_Nil, - - // Break into the debugger (int3) when error is encountered and the sink is - // ended by the 'end and log' functions. - DN_ErrSinkMode_DebugBreakOnEndAndLog, - - // When an error is encountered, exit the program with the error code of the - // error that was caught. - DN_ErrSinkMode_ExitOnError, -}; - -struct DN_ErrSinkMsg -{ - int32_t error_code; - DN_Str8 msg; - DN_CallSite call_site; - DN_ErrSinkMsg *next; - DN_ErrSinkMsg *prev; -}; - -struct DN_ErrSinkNode -{ - DN_ErrSinkMode mode; // Controls how the sink behaves when an error is registered onto the sink. - DN_ErrSinkMsg *msg_sentinel; // List of error messages accumulated for the current scope - DN_ErrSinkNode *next; // Next error scope - uint64_t arena_pos; // Position to reset the arena when the scope is ended -}; - -struct DN_ErrSink -{ - // Arena solely for handling errors take from the thread's TLS - struct DN_Arena *arena; - - // Each entry in the stack represents the errors accumulated in the scope - // between a begin and end scope of the stink. The base sink is stored in - // the thread's TLS. - // - // The stack has the latest error scope at the front. - DN_ErrSinkNode *stack; - - size_t debug_open_close_counter; -}; - -// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// -// NOTE: DN_Atomic_Add/Exchange return the previous value store in the target -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #include - #define DN_Atomic_CompareExchange64(dest, desired_val, prev_val) _InterlockedCompareExchange64((__int64 volatile *)dest, desired_val, prev_val) - #define DN_Atomic_CompareExchange32(dest, desired_val, prev_val) _InterlockedCompareExchange((long volatile *)dest, desired_val, prev_val) - #define DN_Atomic_AddU32(target, value) _InterlockedExchangeAdd((long volatile *)target, value) - #define DN_Atomic_AddU64(target, value) _InterlockedExchangeAdd64((__int64 volatile *)target, value) - #define DN_Atomic_SubU32(target, value) DN_Atomic_AddU32(DN_CAST(long volatile *)target, (long)-value) - #define DN_Atomic_SubU64(target, value) DN_Atomic_AddU64(target, (uint64_t)-value) - - #define DN_CountLeadingZerosU64(value) __lzcnt64(value) - - #define DN_CPU_TSC() __rdtsc() - #define DN_CompilerReadBarrierAndCPUReadFence _ReadBarrier(); _mm_lfence() - #define DN_CompilerWriteBarrierAndCPUWriteFence _WriteBarrier(); _mm_sfence() -#elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - #if defined(__ANDROID__) - #elif defined(DN_PLATFORM_EMSCRIPTEN) - #include - #else - #include - #endif - - #define DN_Atomic_AddU32(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_AddU64(target, value) __atomic_fetch_add(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_SubU32(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - #define DN_Atomic_SubU64(target, value) __atomic_fetch_sub(target, value, __ATOMIC_ACQ_REL) - - #define DN_CountLeadingZerosU64(value) __builtin_clzll(value) - #if defined(DN_COMPILER_GCC) - #define DN_CPU_TSC() __rdtsc() - #else - #define DN_CPU_TSC() __builtin_readcyclecounter() - #endif - #if defined(DN_PLATFORM_EMSCRIPTEN) - #define DN_CompilerReadBarrierAndCPUReadFence - #define DN_CompilerWriteBarrierAndCPUWriteFence - #else - #define DN_CompilerReadBarrierAndCPUReadFence asm volatile("lfence" ::: "memory") - #define DN_CompilerWriteBarrierAndCPUWriteFence asm volatile("sfence" ::: "memory") - #endif -#else - #error "Compiler not supported" -#endif - -#if !defined(DN_PLATFORM_ARM64) -struct DN_CPURegisters -{ - int eax; - int ebx; - int ecx; - int edx; -}; - -union DN_CPUIDResult -{ - DN_CPURegisters reg; - int values[4]; -}; - -struct DN_CPUIDArgs -{ - int eax; - int ecx; -}; - -#define DN_CPU_FEAT_XMACRO \ - DN_CPU_FEAT_XENTRY(3DNow) \ - DN_CPU_FEAT_XENTRY(3DNowExt) \ - DN_CPU_FEAT_XENTRY(ABM) \ - DN_CPU_FEAT_XENTRY(AES) \ - DN_CPU_FEAT_XENTRY(AVX) \ - DN_CPU_FEAT_XENTRY(AVX2) \ - DN_CPU_FEAT_XENTRY(AVX512F) \ - DN_CPU_FEAT_XENTRY(AVX512DQ) \ - DN_CPU_FEAT_XENTRY(AVX512IFMA) \ - DN_CPU_FEAT_XENTRY(AVX512PF) \ - DN_CPU_FEAT_XENTRY(AVX512ER) \ - DN_CPU_FEAT_XENTRY(AVX512CD) \ - DN_CPU_FEAT_XENTRY(AVX512BW) \ - DN_CPU_FEAT_XENTRY(AVX512VL) \ - DN_CPU_FEAT_XENTRY(AVX512VBMI) \ - DN_CPU_FEAT_XENTRY(AVX512VBMI2) \ - DN_CPU_FEAT_XENTRY(AVX512VNNI) \ - DN_CPU_FEAT_XENTRY(AVX512BITALG) \ - DN_CPU_FEAT_XENTRY(AVX512VPOPCNTDQ) \ - DN_CPU_FEAT_XENTRY(AVX5124VNNIW) \ - DN_CPU_FEAT_XENTRY(AVX5124FMAPS) \ - DN_CPU_FEAT_XENTRY(AVX512VP2INTERSECT) \ - DN_CPU_FEAT_XENTRY(AVX512FP16) \ - DN_CPU_FEAT_XENTRY(CLZERO) \ - DN_CPU_FEAT_XENTRY(CMPXCHG8B) \ - DN_CPU_FEAT_XENTRY(CMPXCHG16B) \ - DN_CPU_FEAT_XENTRY(F16C) \ - DN_CPU_FEAT_XENTRY(FMA) \ - DN_CPU_FEAT_XENTRY(FMA4) \ - DN_CPU_FEAT_XENTRY(FP128) \ - DN_CPU_FEAT_XENTRY(FP256) \ - DN_CPU_FEAT_XENTRY(FPU) \ - DN_CPU_FEAT_XENTRY(MMX) \ - DN_CPU_FEAT_XENTRY(MONITOR) \ - DN_CPU_FEAT_XENTRY(MOVBE) \ - DN_CPU_FEAT_XENTRY(MOVU) \ - DN_CPU_FEAT_XENTRY(MmxExt) \ - DN_CPU_FEAT_XENTRY(PCLMULQDQ) \ - DN_CPU_FEAT_XENTRY(POPCNT) \ - DN_CPU_FEAT_XENTRY(RDRAND) \ - DN_CPU_FEAT_XENTRY(RDSEED) \ - DN_CPU_FEAT_XENTRY(RDTSCP) \ - DN_CPU_FEAT_XENTRY(SHA) \ - DN_CPU_FEAT_XENTRY(SSE) \ - DN_CPU_FEAT_XENTRY(SSE2) \ - DN_CPU_FEAT_XENTRY(SSE3) \ - DN_CPU_FEAT_XENTRY(SSE41) \ - DN_CPU_FEAT_XENTRY(SSE42) \ - DN_CPU_FEAT_XENTRY(SSE4A) \ - DN_CPU_FEAT_XENTRY(SSSE3) \ - DN_CPU_FEAT_XENTRY(TSC) \ - DN_CPU_FEAT_XENTRY(TscInvariant) \ - DN_CPU_FEAT_XENTRY(VAES) \ - DN_CPU_FEAT_XENTRY(VPCMULQDQ) - -enum DN_CPUFeature -{ - #define DN_CPU_FEAT_XENTRY(label) DN_CPUFeature_##label, - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - DN_CPUFeature_Count, -}; - -struct DN_CPUFeatureDecl -{ - DN_CPUFeature value; - DN_Str8 label; -}; - -struct DN_CPUFeatureQuery -{ - DN_CPUFeature feature; - bool available; -}; - -struct DN_CPUReport -{ - char vendor [4 /*bytes*/ * 3 /*EDX, ECX, EBX*/ + 1 /*null*/]; - char brand [48]; - uint64_t features[(DN_CPUFeature_Count / (sizeof(uint64_t) * 8)) + 1]; -}; - -extern DN_CPUFeatureDecl g_dn_cpu_feature_decl[DN_CPUFeature_Count]; -#endif // DN_PLATFORM_ARM64 - -// NOTE: [$TMUT] DN_TicketMutex /////////////////////////////////////////////////////////////////// -struct DN_TicketMutex -{ - unsigned int volatile ticket; // The next ticket to give out to the thread taking the mutex - unsigned int volatile serving; // The ticket ID to block the mutex on until it is returned -}; - -// NOTE: [$MUTX] DN_OSMutex /////////////////////////////////////////////////////////////////////// -struct DN_OSMutex -{ - #if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) - char win32_handle[48]; - #else - pthread_mutex_t posix_handle; - pthread_mutexattr_t posix_attribs; - #endif -}; - -// NOTE: [$PRIN] DN_Print ///////////////////////////////////////////////////////////////////////// -enum DN_PrintStd -{ - DN_PrintStd_Out, - DN_PrintStd_Err, -}; - -enum DN_PrintBold -{ - DN_PrintBold_No, - DN_PrintBold_Yes, -}; - -struct DN_PrintStyle -{ - DN_PrintBold bold; - bool colour; - uint8_t r, g, b; -}; - -enum DN_PrintESCColour -{ - DN_PrintESCColour_Fg, - DN_PrintESCColour_Bg, -}; - - -// NOTE: [$LLOG] DN_Log /////////////////////////////////////////////////////////////////////////// -enum DN_LogType -{ - DN_LogType_Debug, - DN_LogType_Info, - DN_LogType_Warning, - DN_LogType_Error, - DN_LogType_Count, -}; - -typedef void DN_LogProc(DN_Str8 type, - int log_type, - void *user_data, - DN_CallSite call_site, - DN_FMT_ATTRIB char const *fmt, - va_list va); - -// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// -DN_FORCE_INLINE uint64_t DN_Atomic_SetValue64 (uint64_t volatile *target, uint64_t value); -DN_FORCE_INLINE long DN_Atomic_SetValue32 (long volatile *target, long value); -#if !defined(DN_PLATFORM_ARM64) -DN_API DN_CPUIDResult DN_CPU_ID (DN_CPUIDArgs args); -DN_API DN_USize DN_CPU_HasFeatureArray (DN_CPUReport const *report, DN_CPUFeatureQuery *features, DN_USize features_size); -DN_API bool DN_CPU_HasFeature (DN_CPUReport const *report, DN_CPUFeature feature); -DN_API bool DN_CPU_HasAllFeatures (DN_CPUReport const *report, DN_CPUFeature const *features, DN_USize features_size); -template bool DN_CPU_HasAllFeaturesCArray (DN_CPUReport const *report, DN_CPUFeature const (&features)[N]); -DN_API void DN_CPU_SetFeature (DN_CPUReport *report, DN_CPUFeature feature); -DN_API DN_CPUReport DN_CPU_Report (); -#endif - -// NOTE: [$TMUT] DN_TicketMutex /////////////////////////////////////////////////////////////////// -DN_API void DN_TicketMutex_Begin (DN_TicketMutex *mutex); -DN_API void DN_TicketMutex_End (DN_TicketMutex *mutex); -DN_API DN_UInt DN_TicketMutex_MakeTicket (DN_TicketMutex *mutex); -DN_API void DN_TicketMutex_BeginTicket (DN_TicketMutex const *mutex, DN_UInt ticket); -DN_API bool DN_TicketMutex_CanLock (DN_TicketMutex const *mutex, DN_UInt ticket); - -// NOTE: [$PRIN] DN_Print ///////////////////////////////////////////////////////////////////////// -// NOTE: Print Style /////////////////////////////////////////////////////////////////////////////// -DN_API DN_PrintStyle DN_Print_StyleColour (uint8_t r, uint8_t g, uint8_t b, DN_PrintBold bold); -DN_API DN_PrintStyle DN_Print_StyleColourU32 (uint32_t rgb, DN_PrintBold bold); -DN_API DN_PrintStyle DN_Print_StyleBold (); - -// NOTE: Print Macros ////////////////////////////////////////////////////////////////////////////// -#define DN_Print(string) DN_Print_Std(DN_PrintStd_Out, string) -#define DN_Print_F(fmt, ...) DN_Print_StdF(DN_PrintStd_Out, fmt, ## __VA_ARGS__) -#define DN_Print_FV(fmt, args) DN_Print_StdFV(DN_PrintStd_Out, fmt, args) - -#define DN_Print_Style(style, string) DN_Print_StdStyle(DN_PrintStd_Out, style, string) -#define DN_Print_FStyle(style, fmt, ...) DN_Print_StdFStyle(DN_PrintStd_Out, style, fmt, ## __VA_ARGS__) -#define DN_Print_FVStyle(style, fmt, args, ...) DN_Print_StdFVStyle(DN_PrintStd_Out, style, fmt, args) - -#define DN_Print_Ln(string) DN_Print_StdLn(DN_PrintStd_Out, string) -#define DN_Print_LnF(fmt, ...) DN_Print_StdLnF(DN_PrintStd_Out, fmt, ## __VA_ARGS__) -#define DN_Print_LnFV(fmt, args) DN_Print_StdLnFV(DN_PrintStd_Out, fmt, args) - -#define DN_Print_LnStyle(style, string) DN_Print_StdLnStyle(DN_PrintStd_Out, style, string); -#define DN_Print_LnFStyle(style, fmt, ...) DN_Print_StdLnFStyle(DN_PrintStd_Out, style, fmt, ## __VA_ARGS__); -#define DN_Print_LnFVStyle(style, fmt, args) DN_Print_StdLnFVStyle(DN_PrintStd_Out, style, fmt, args); - -#define DN_Print_Err(string) DN_Print_Std(DN_PrintStd_Err, string) -#define DN_Print_ErrF(fmt, ...) DN_Print_StdF(DN_PrintStd_Err, fmt, ## __VA_ARGS__) -#define DN_Print_ErrFV(fmt, args) DN_Print_StdFV(DN_PrintStd_Err, fmt, args) - -#define DN_Print_ErrStyle(style, string) DN_Print_StdStyle(DN_PrintStd_Err, style, string) -#define DN_Print_ErrFStyle(style, fmt, ...) DN_Print_StdFStyle(DN_PrintStd_Err, style, fmt, ## __VA_ARGS__) -#define DN_Print_ErrFVStyle(style, fmt, args, ...) DN_Print_StdFVStyle(DN_PrintStd_Err, style, fmt, args) - -#define DN_Print_ErrLn(string) DN_Print_StdLn(DN_PrintStd_Err, string) -#define DN_Print_ErrLnF(fmt, ...) DN_Print_StdLnF(DN_PrintStd_Err, fmt, ## __VA_ARGS__) -#define DN_Print_ErrLnFV(fmt, args) DN_Print_StdLnFV(DN_PrintStd_Err, fmt, args) - -#define DN_Print_ErrLnStyle(style, string) DN_Print_StdLnStyle(DN_PrintStd_Err, style, string); -#define DN_Print_ErrLnFStyle(style, fmt, ...) DN_Print_StdLnFStyle(DN_PrintStd_Err, style, fmt, ## __VA_ARGS__); -#define DN_Print_ErrLnFVStyle(style, fmt, args) DN_Print_StdLnFVStyle(DN_PrintStd_Err, style, fmt, args); -// NOTE: Print ///////////////////////////////////////////////////////////////////////////////////// -DN_API void DN_Print_Std (DN_PrintStd std_handle, DN_Str8 string); -DN_API void DN_Print_StdF (DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Print_StdFV (DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_Print_StdStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_Str8 string); -DN_API void DN_Print_StdFStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Print_StdFVStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_Print_StdLn (DN_PrintStd std_handle, DN_Str8 string); -DN_API void DN_Print_StdLnF (DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Print_StdLnFV (DN_PrintStd std_handle, DN_FMT_ATTRIB char const *fmt, va_list args); - -DN_API void DN_Print_StdLnStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_Str8 string); -DN_API void DN_Print_StdLnFStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Print_StdLnFVStyle (DN_PrintStd std_handle, DN_PrintStyle style, DN_FMT_ATTRIB char const *fmt, va_list args); - -// NOTE: ANSI Formatting Codes ///////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_Print_ESCColourStr8 (DN_PrintESCColour colour, uint8_t r, uint8_t g, uint8_t b); -DN_API DN_Str8 DN_Print_ESCColourU32Str8 (DN_PrintESCColour colour, uint32_t value); - -#define DN_Print_ESCColourFgStr8(r, g, b) DN_Print_ESCColourStr8(DN_PrintESCColour_Fg, r, g, b) -#define DN_Print_ESCColourBgStr8(r, g, b) DN_Print_ESCColourStr8(DN_PrintESCColour_Bg, r, g, b) -#define DN_Print_ESCColourFg(r, g, b) DN_Print_ESCColourStr8(DN_PrintESCColour_Fg, r, g, b).data -#define DN_Print_ESCColourBg(r, g, b) DN_Print_ESCColourStr8(DN_PrintESCColour_Bg, r, g, b).data - -#define DN_Print_ESCColourFgU32Str8(value) DN_Print_ESCColourU32Str8(DN_PrintESCColour_Fg, value) -#define DN_Print_ESCColourBgU32Str8(value) DN_Print_ESCColourU32Str8(DN_PrintESCColour_Bg, value) -#define DN_Print_ESCColourFgU32(value) DN_Print_ESCColourU32Str8(DN_PrintESCColour_Fg, value).data -#define DN_Print_ESCColourBgU32(value) DN_Print_ESCColourU32Str8(DN_PrintESCColour_Bg, value).data - -#define DN_Print_ESCReset "\x1b[0m" -#define DN_Print_ESCBold "\x1b[1m" -#define DN_Print_ESCResetStr8 DN_STR8(DN_Print_ESCReset) -#define DN_Print_ESCBoldStr8 DN_STR8(DN_Print_ESCBold) -// NOTE: [$LLOG] DN_Log /////////////////////////////////////////////////////////////////////////// -#define DN_LogTypeColourU32_Info 0x00'87'ff'ff // Blue -#define DN_LogTypeColourU32_Warning 0xff'ff'00'ff // Yellow -#define DN_LogTypeColourU32_Error 0xff'00'00'ff // Red - -#define DN_Log_DebugF(fmt, ...) DN_Log_TypeFCallSite (DN_LogType_Debug, DN_CALL_SITE, fmt, ## __VA_ARGS__) -#define DN_Log_InfoF(fmt, ...) DN_Log_TypeFCallSite (DN_LogType_Info, DN_CALL_SITE, fmt, ## __VA_ARGS__) -#define DN_Log_WarningF(fmt, ...) DN_Log_TypeFCallSite (DN_LogType_Warning, DN_CALL_SITE, fmt, ## __VA_ARGS__) -#define DN_Log_ErrorF(fmt, ...) DN_Log_TypeFCallSite (DN_LogType_Error, DN_CALL_SITE, fmt, ## __VA_ARGS__) -#define DN_Log_DebugFV(fmt, args) DN_Log_TypeFVCallSite(DN_LogType_Debug, DN_CALL_SITE, fmt, args) -#define DN_Log_InfoFV(fmt, args) DN_Log_TypeFVCallSite(DN_LogType_Info, DN_CALL_SITE, fmt, args) -#define DN_Log_WarningFV(fmt, args) DN_Log_TypeFVCallSite(DN_LogType_Warning, DN_CALL_SITE, fmt, args) -#define DN_Log_ErrorFV(fmt, args) DN_Log_TypeFVCallSite(DN_LogType_Error, DN_CALL_SITE, fmt, args) -#define DN_Log_TypeFV(type, fmt, args) DN_Log_TypeFVCallSite(type, DN_CALL_SITE, fmt, args) -#define DN_Log_TypeF(type, fmt, ...) DN_Log_TypeFCallSite (type, DN_CALL_SITE, fmt, ## __VA_ARGS__) -#define DN_Log_FV(type, fmt, args) DN_Log_FVCallSite (type, DN_CALL_SITE, fmt, args) -#define DN_Log_F(type, fmt, ...) DN_Log_FCallSite (type, DN_CALL_SITE, fmt, ## __VA_ARGS__) - -DN_API DN_Str8 DN_Log_MakeStr8 (struct DN_Arena *arena, bool colour, DN_Str8 type, int log_type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_Log_TypeFVCallSite (DN_LogType type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list va); -DN_API void DN_Log_TypeFCallSite (DN_LogType type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_Log_FVCallSite (DN_Str8 type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list va); -DN_API void DN_Log_FCallSite (DN_Str8 type, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); - -// NOTE: [$ERRS] DN_ErrSink ///////////////////////////////////////////////////////////////////// -DN_API DN_ErrSink * DN_ErrSink_Begin (DN_ErrSinkMode mode); -#define DN_ErrSink_BeginDefault() DN_ErrSink_Begin(DN_ErrSinkMode_Nil) -DN_API bool DN_ErrSink_HasError (DN_ErrSink *err); -DN_API DN_ErrSinkMsg* DN_ErrSink_End (DN_Arena *arena, DN_ErrSink *err); -DN_API DN_Str8 DN_ErrSink_EndStr8 (DN_Arena *arena, DN_ErrSink *err); -DN_API void DN_ErrSink_EndAndIgnore (DN_ErrSink *err); - -#define DN_ErrSink_EndAndLogError(err, err_msg) DN_ErrSink_EndAndLogError_ (err, DN_CALL_SITE, err_msg) -#define DN_ErrSink_EndAndLogErrorFV(err, fmt, args) DN_ErrSink_EndAndLogErrorFV_ (err, DN_CALL_SITE, fmt, args) -#define DN_ErrSink_EndAndLogErrorF(err, fmt, ...) DN_ErrSink_EndAndLogErrorF_ (err, DN_CALL_SITE, fmt, ##__VA_ARGS__) -#define DN_ErrSink_EndAndExitIfErrorFV(err, exit_val, fmt, args) DN_ErrSink_EndAndExitIfErrorFV_(err, DN_CALL_SITE, exit_val, fmt, args) -#define DN_ErrSink_EndAndExitIfErrorF(err, exit_val, fmt, ...) DN_ErrSink_EndAndExitIfErrorF_ (err, DN_CALL_SITE, exit_val, fmt, ##__VA_ARGS__) - -DN_API bool DN_ErrSink_EndAndLogError_ (DN_ErrSink *err, DN_CallSite call_site, DN_Str8 msg); -DN_API bool DN_ErrSink_EndAndLogErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_ErrSink_EndAndLogErrorF_ (DN_ErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_ErrSink_EndAndExitIfErrorF_ (DN_ErrSink *err, DN_CallSite call_site, uint32_t exit_val, DN_FMT_ATTRIB char const *fmt, ...); -DN_API void DN_ErrSink_EndAndExitIfErrorFV_ (DN_ErrSink *err, DN_CallSite call_site, uint32_t exit_val, DN_FMT_ATTRIB char const *fmt, va_list args); - -#define DN_ErrSink_AppendFV(error, error_code, fmt, args) do { DN_TLS_SaveCallSite; DN_ErrSink_AppendFV_(error, error_code, fmt, args); } while (0) -#define DN_ErrSink_AppendF(error, error_code, fmt, ...) do { DN_TLS_SaveCallSite; DN_ErrSink_AppendF_(error, error_code, fmt, ## __VA_ARGS__); } while (0) -DN_API void DN_ErrSink_AppendFV_ (DN_ErrSink *err, uint32_t error_code, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API void DN_ErrSink_AppendF_ (DN_ErrSink *err, uint32_t error_code, DN_FMT_ATTRIB char const *fmt, ...); - -// NOTE: [$SDLL] DN_SentinelDLL /////////////////////////////////////////////////////////////////// -#define DN_SentinelDLL_Init(list) \ - (list)->next = (list)->prev = (list) - -#define DN_SentinelDLL_InitArena(list, T, arena) \ - do { \ - (list) = DN_Arena_New(arena, T, DN_ZeroMem_Yes); \ - DN_SentinelDLL_Init(list); \ - } while (0) - -#define DN_SentinelDLL_InitPool(list, T, pool) \ - do { \ - (list) = DN_Pool_New(pool, T); \ - DN_SentinelDLL_Init(list); \ - } while (0) - -#define DN_SentinelDLL_Detach(item) \ - do { \ - if (item) { \ - (item)->prev->next = (item)->next; \ - (item)->next->prev = (item)->prev; \ - (item)->next = nullptr; \ - (item)->prev = nullptr; \ - } \ - } while (0) - -#define DN_SentinelDLL_Dequeue(list, dest_ptr) \ - if (DN_SentinelDLL_HasItems(list)) { \ - dest_ptr = (list)->next; \ - DN_SentinelDLL_Detach(dest_ptr); \ - } - -#define DN_SentinelDLL_Append(list, item) \ - do { \ - if (item) { \ - if ((item)->next) \ - DN_SentinelDLL_Detach(item); \ - (item)->next = (list)->next; \ - (item)->prev = (list); \ - (item)->next->prev = (item); \ - (item)->prev->next = (item); \ - } \ - } while (0) - -#define DN_SentinelDLL_Prepend(list, item) \ - do { \ - if (item) { \ - if ((item)->next) \ - DN_SentinelDLL_Detach(item); \ - (item)->next = (list); \ - (item)->prev = (list)->prev; \ - (item)->next->prev = (item); \ - (item)->prev->next = (item); \ - } \ - } while (0) - -#define DN_SentinelDLL_IsEmpty(list) \ - (!(list) || ((list) == (list)->next)) - -#define DN_SentinelDLL_IsInit(list) \ - ((list)->next && (list)->prev) - -#define DN_SentinelDLL_HasItems(list) \ - ((list) && ((list) != (list)->next)) - -#define DN_SentinelDLL_ForEach(it_name, list) \ - auto *it_name = (list)->next; (it_name) != (list); (it_name) = (it_name)->next - - -// NOTE: [$INTR] Intrinsics //////////////////////////////////////////////////////////////////////// -DN_FORCE_INLINE uint64_t DN_Atomic_SetValue64(uint64_t volatile *target, uint64_t value) -{ - #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - __int64 result; - do { - result = *target; - } while (DN_Atomic_CompareExchange64(target, value, result) != result); - return DN_CAST(uint64_t)result; - #elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - uint64_t result = __sync_lock_test_and_set(target, value); - return result; - #else - #error Unsupported compiler - #endif -} - -DN_FORCE_INLINE long DN_Atomic_SetValue32(long volatile *target, long value) -{ - #if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - long result; - do { - result = *target; - } while (DN_Atomic_CompareExchange32(target, value, result) != result); - return result; - #elif defined(DN_COMPILER_GCC) || defined(DN_COMPILER_CLANG) - long result = __sync_lock_test_and_set(target, value); - return result; - #else - #error Unsupported compiler - #endif -} - -template bool DN_CPU_HasAllFeaturesCArray(DN_CPUReport const *report, DN_CPUFeature const (&features)[N]) -{ - bool result = DN_CPU_HasAllFeatures(report, features, N); - return result; -} - -extern struct DN_Core *g_dn_core; diff --git a/dqn_cgen.cpp b/dqn_cgen.cpp deleted file mode 100644 index be69ffd..0000000 --- a/dqn_cgen.cpp +++ /dev/null @@ -1,1238 +0,0 @@ -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$$$$$$$\ $$\ $$\ -// $$ __$$\ $$ __$$\ $$ _____|$$$\ $$ | -// $$ / \__|$$ / \__|$$ | $$$$\ $$ | -// $$ | $$ |$$$$\ $$$$$\ $$ $$\$$ | -// $$ | $$ |\_$$ |$$ __| $$ \$$$$ | -// $$ | $$\ $$ | $$ |$$ | $$ |\$$$ | -// \$$$$$$ |\$$$$$$ |$$$$$$$$\ $$ | \$$ | -// \______/ \______/ \________|\__| \__| -// -// dqn_cgen.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -DN_CGenMapNodeToEnum const DN_CGEN_TABLE_KEY_LIST[] = -{ - {DN_CGenTableKeyType_Name, DN_STR8("name")}, - {DN_CGenTableKeyType_Type, DN_STR8("type")}, -}; - -DN_CGenMapNodeToEnum const DN_CGEN_TABLE_TYPE_LIST[] = -{ - {DN_CGenTableType_Data, DN_STR8("data") }, - {DN_CGenTableType_CodeGenBuiltinTypes, DN_STR8("code_gen_builtin_types")}, - {DN_CGenTableType_CodeGenStruct, DN_STR8("code_gen_struct") }, - {DN_CGenTableType_CodeGenEnum, DN_STR8("code_gen_enum") }, -}; - -DN_CGenMapNodeToEnum const DN_CGEN_TABLE_ROW_TAG_LIST[] = -{ - {DN_CGenTableRowTagType_CommentDivider, DN_STR8("comment_divider")}, - {DN_CGenTableRowTagType_EmptyLine, DN_STR8("empty_line")}, -}; - -DN_CGenMapNodeToEnum const DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST[] = -{ - {DN_CGenTableRowTagCommentDivider_Label, DN_STR8("label")}, -}; - -DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_STRUCT_HEADER_LIST[] = -{ - DN_CGenTableHeaderType_Name, - DN_CGenTableHeaderType_Table, - DN_CGenTableHeaderType_CppType, - DN_CGenTableHeaderType_CppName, - DN_CGenTableHeaderType_CppIsPtr, - DN_CGenTableHeaderType_CppOpEquals, - DN_CGenTableHeaderType_CppArraySize, - DN_CGenTableHeaderType_CppArraySizeField, - DN_CGenTableHeaderType_CppLabel, - DN_CGenTableHeaderType_GenTypeInfo, -}; - -DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_ENUM_HEADER_LIST[] = -{ - DN_CGenTableHeaderType_Name, - DN_CGenTableHeaderType_Table, - DN_CGenTableHeaderType_CppName, - DN_CGenTableHeaderType_CppValue, - DN_CGenTableHeaderType_CppLabel, - DN_CGenTableHeaderType_GenTypeInfo, - DN_CGenTableHeaderType_GenEnumCount, -}; - -DN_CGenTableHeaderType const DN_CGEN_TABLE_CODE_GEN_BUILTIN_TYPES_HEADER_LIST[] = -{ - DN_CGenTableHeaderType_Name, -}; - -static bool DN_CGen_GatherTables_(DN_CGen *cgen, DN_ErrSink *err) -{ - bool result = false; - if (!cgen || !cgen->file_list || !cgen->arena) - return result; - - // NOTE: Gather the tables ///////////////////////////////////////////////////////////////////// - for (MD_EachNode(ref, cgen->file_list->first_child)) { - MD_Node *root = MD_ResolveNodeFromReference(ref); - for (MD_EachNode(node, root->first_child)) { - - MD_Node *table_tag = MD_TagFromString(node, MD_S8Lit("table"), 0); - if (MD_NodeIsNil(table_tag)) - continue; - - DN_CGenTable *table = MD_PushArray(cgen->arena, DN_CGenTable, 1); - table->node = node; - table->name = DN_CGen_MDToDNStr8(table_tag->first_child->first_child->string); - MD_MapInsert(cgen->arena, &cgen->table_map, MD_MapKeyStr(DN_CGen_DNToMDStr8(table->name)), table); - MD_QueuePush(cgen->first_table, cgen->last_table, table); - - for (MD_EachNode(key, table_tag->first_child)) { - DN_CGenMapNodeToEnum key_mapping = DN_CGen_MapNodeToEnumOrExit(key, - DN_CGEN_TABLE_KEY_LIST, - DN_ARRAY_UCOUNT(DN_CGEN_TABLE_KEY_LIST), - "Table specified invalid key"); - switch (DN_CAST(DN_CGenTableKeyType)key_mapping.enum_val) { - case DN_CGenTableKeyType_Nil: DN_INVALID_CODE_PATH; - - case DN_CGenTableKeyType_Name: { - table->name = DN_CGen_MDToDNStr8(key->first_child->string); - } break; - - case DN_CGenTableKeyType_Type: { - MD_Node *table_type_value = key->first_child; - DN_CGenMapNodeToEnum table_type_validator = DN_CGen_MapNodeToEnumOrExit(table_type_value, - DN_CGEN_TABLE_TYPE_LIST, - DN_ARRAY_UCOUNT(DN_CGEN_TABLE_TYPE_LIST), - "Table 'type' specified invalid value"); - table->type = DN_CAST(DN_CGenTableType) table_type_validator.enum_val; - - DN_ASSERT(table->type <= DN_CGenTableType_Count); - cgen->table_counts[table->type]++; - } break; - } - } - } - } - - // NOTE: Parse the tables ////////////////////////////////////////////////////////////////////// - DN_USize const BEGIN_COLUMN_INDEX = 1; /*Reserve 0th slot for nil-entry*/ - for (DN_CGenTable *table = cgen->first_table; table; table = table->next) { - table->column_count = BEGIN_COLUMN_INDEX; - table->headers_node = table->node->first_child; - for (MD_EachNode(column_node, table->headers_node->first_child)) - table->column_count++; - - if (table->column_count == BEGIN_COLUMN_INDEX) - continue; - - MD_Node *row_it = table->headers_node->next; - for (MD_EachNode(row_node, row_it)) - table->row_count++; - - table->rows = MD_PushArray(cgen->arena, DN_CGenTableRow, table->row_count); - table->headers = MD_PushArray(cgen->arena, DN_CGenTableHeader, table->column_count); - for (DN_USize index = 0; index < table->row_count; index++) { - DN_CGenTableRow *row = table->rows + index; - row->columns = MD_PushArray(cgen->arena, DN_CGenTableColumn, table->column_count); - } - - // NOTE: Collect table headers ///////////////////////////////////////////////////////////// - table->headers_map = MD_MapMake(cgen->arena); - DN_USize column_index = BEGIN_COLUMN_INDEX; - for (MD_EachNode(header_column, table->headers_node->first_child)) { - DN_ASSERT(column_index < table->column_count); - - // NOTE: Detect builtin headers and cache the index for that table ///////////////////// - for (DN_USize enum_index = 0; enum_index < DN_CGenTableHeaderType_Count; enum_index++) { - DN_Str8 decl_str8 = DN_CGen_TableHeaderTypeToDeclStr8(DN_CAST(DN_CGenTableHeaderType)enum_index); - if (decl_str8 != DN_Str8_Init(header_column->string.str, header_column->string.size)) - continue; - table->column_indexes[enum_index] = column_index; - break; - } - - MD_MapInsert(cgen->arena, &table->headers_map, MD_MapKeyStr(header_column->string), DN_CAST(void *)column_index); - table->headers[column_index++].name = header_column->string; - } - - // NOTE: Validate table headers //////////////////////////////////////////////////////////// - switch (table->type) { - case DN_CGenTableType_Nil: DN_INVALID_CODE_PATH; - case DN_CGenTableType_Count: DN_INVALID_CODE_PATH; - - case DN_CGenTableType_Data: { - } break; - - case DN_CGenTableType_CodeGenStruct: { - for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_STRUCT_HEADER_LIST) { - if (table->column_indexes[enum_val] == 0) { - DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); - DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Struct code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); - return false; - } - } - } break; - - case DN_CGenTableType_CodeGenEnum: { - for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_ENUM_HEADER_LIST) { - if (table->column_indexes[enum_val] == 0) { - DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); - DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Enum code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); - return false; - } - } - } break; - - case DN_CGenTableType_CodeGenBuiltinTypes: { - for (DN_CGenTableHeaderType enum_val : DN_CGEN_TABLE_CODE_GEN_BUILTIN_TYPES_HEADER_LIST) { - if (table->column_indexes[enum_val] == 0) { - DN_Str8 expected_value = DN_CGen_TableHeaderTypeToDeclStr8(enum_val); - DN_CGen_LogF(MD_MessageKind_Error, table->headers_node, err, "Enum code generation table is missing column '%.*s'", DN_STR_FMT(expected_value)); - return false; - } - } - } break; - } - - // NOTE: Parse each row in table /////////////////////////////////////////////////////////// - DN_USize row_index = 0; - for (MD_EachNode(row_node, row_it)) { - column_index = BEGIN_COLUMN_INDEX; - DN_ASSERT(row_index < table->row_count); - - // NOTE: Parse any tags set on the row ///////////////////////////////////////////////// - DN_CGenTableRow *row = table->rows + row_index++; - for (MD_EachNode(row_tag, row_node->first_tag)) { - DN_CGenMapNodeToEnum row_mapping = DN_CGen_MapNodeToEnumOrExit(row_tag, - DN_CGEN_TABLE_ROW_TAG_LIST, - DN_ARRAY_UCOUNT(DN_CGEN_TABLE_ROW_TAG_LIST), - "Table specified invalid row tag"); - DN_CGenTableRowTag *tag = MD_PushArray(cgen->arena, DN_CGenTableRowTag, 1); - tag->type = DN_CAST(DN_CGenTableRowTagType) row_mapping.enum_val; - MD_QueuePush(row->first_tag, row->last_tag, tag); - - switch (tag->type) { - case DN_CGenTableRowTagType_Nil: DN_INVALID_CODE_PATH; - - case DN_CGenTableRowTagType_CommentDivider: { - for (MD_EachNode(tag_key, row_tag->first_child)) { - DN_CGenMapNodeToEnum tag_mapping = DN_CGen_MapNodeToEnumOrExit(tag_key, - DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST, - DN_ARRAY_UCOUNT(DN_CGEN_TABLE_ROW_TAG_COMMENT_DIVIDER_KEY_LIST), - "Table specified invalid row tag"); - switch (DN_CAST(DN_CGenTableRowTagCommentDivider)tag_mapping.enum_val) { - case DN_CGenTableRowTagCommentDivider_Nil: DN_INVALID_CODE_PATH; - case DN_CGenTableRowTagCommentDivider_Label: { - tag->comment = tag_key->first_child->string; - } break; - } - } - } break; - - case DN_CGenTableRowTagType_EmptyLine: break; - } - } - - for (MD_EachNode(column_node, row_node->first_child)) { - table->headers[column_index].longest_string = DN_MAX(table->headers[column_index].longest_string, DN_CAST(int) column_node->string.size); - row->columns[column_index].string = DN_CGen_MDToDNStr8(column_node->string); - row->columns[column_index].node = column_node; - column_index++; - } - } - } - - // NOTE: Validate codegen ////////////////////////////////////////////////////////////////////// - DN_CGenTableHeaderType const CHECK_COLUMN_LINKS[] = { - DN_CGenTableHeaderType_CppName, - DN_CGenTableHeaderType_CppType, - DN_CGenTableHeaderType_CppValue, - DN_CGenTableHeaderType_CppIsPtr, - DN_CGenTableHeaderType_CppArraySize, - DN_CGenTableHeaderType_CppArraySizeField, - }; - - result = true; - for (DN_CGenTable *table = cgen->first_table; table; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - for (DN_CGenTableHeaderType check_column : CHECK_COLUMN_LINKS) { - DN_CGenTableColumn column = it.cgen_table_row->columns[table->column_indexes[check_column]]; - if (column.string.size == 0) { - // NOTE: The code generation table did not bind a code generation parameter to - // a column in the target table. We skip it. - continue; - } - - // NOTE: Check if the column to bind to exists in the target table. - if (!MD_MapLookup(&it.table->headers_map, MD_MapKeyStr(DN_CGen_DNToMDStr8(column.string)))) { - result = false; - DN_Str8 header_type_str8 = DN_CGen_TableHeaderTypeToDeclStr8(check_column); - DN_CGen_LogF(MD_MessageKind_Error, column.node, err, - "Code generation table binds '%.*s' to '%.*s', but the column '%.*s' does not exist in table '%.*s'\n" - "NOTE: If you want '%.*s' to omit the column '%.*s' you can bind to the empty string `` to skip it, otherwise, please ensure the table '%.*s' has the column '%.*s'" - , - DN_STR_FMT(column.string), - DN_STR_FMT(header_type_str8), - DN_STR_FMT(column.string), - DN_STR_FMT(it.table->name), - DN_STR_FMT(it.table->name), - DN_STR_FMT(header_type_str8), - DN_STR_FMT(it.table->name), - DN_STR_FMT(header_type_str8)); - } - } - } - } - - return result; -} - -DN_API DN_CGen DN_CGen_InitFilesArgV(int argc, char const **argv, DN_ErrSink *err) -{ - DN_CGen result = {}; - result.arena = MD_ArenaAlloc(); - result.file_list = MD_MakeList(result.arena); - result.table_map = MD_MapMake(result.arena); - - bool has_error = false; - for (DN_ISize arg_index = 0; arg_index < argc; arg_index++) { - MD_String8 file_name = MD_S8CString(DN_CAST(char *)argv[arg_index]); - MD_ParseResult parse_result = MD_ParseWholeFile(result.arena, file_name); - for (MD_Message *message = parse_result.errors.first; message != 0; message = message->next) { - has_error = true; - DN_CGen_LogF(message->kind, message->node, err, "%.*s", MD_S8VArg(message->string)); - } - MD_PushNewReference(result.arena, result.file_list, parse_result.node); - } - - if (!has_error) - DN_CGen_GatherTables_(&result, err); - return result; -} - -DN_API DN_Str8 DN_CGen_TableHeaderTypeToDeclStr8(DN_CGenTableHeaderType type) -{ - DN_Str8 result = {}; - switch (type) { - case DN_CGenTableHeaderType_Name: result = DN_STR8("name"); break; - case DN_CGenTableHeaderType_Table: result = DN_STR8("table"); break; - case DN_CGenTableHeaderType_CppType: result = DN_STR8("cpp_type"); break; - case DN_CGenTableHeaderType_CppName: result = DN_STR8("cpp_name"); break; - case DN_CGenTableHeaderType_CppValue: result = DN_STR8("cpp_value"); break; - case DN_CGenTableHeaderType_CppIsPtr: result = DN_STR8("cpp_is_ptr"); break; - case DN_CGenTableHeaderType_CppOpEquals: result = DN_STR8("cpp_op_equals"); break; - case DN_CGenTableHeaderType_CppArraySize: result = DN_STR8("cpp_array_size"); break; - case DN_CGenTableHeaderType_CppArraySizeField: result = DN_STR8("cpp_array_size_field"); break; - case DN_CGenTableHeaderType_CppLabel: result = DN_STR8("cpp_label"); break; - case DN_CGenTableHeaderType_GenTypeInfo: result = DN_STR8("gen_type_info"); break; - case DN_CGenTableHeaderType_GenEnumCount: result = DN_STR8("gen_enum_count"); break; - case DN_CGenTableHeaderType_Count: result = DN_STR8("XX BAD ENUM VALUE XX"); break; - default: result = DN_STR8("XX INVALID ENUM VALUE XX"); break; - } - return result; -} - -DN_API DN_CGenMapNodeToEnum DN_CGen_MapNodeToEnumOrExit(MD_Node const *node, DN_CGenMapNodeToEnum const *valid_keys, DN_USize valid_keys_size, char const *fmt, ...) -{ - DN_CGenMapNodeToEnum result = {}; - for (DN_USize index = 0; index < valid_keys_size; index++) { - DN_CGenMapNodeToEnum const *validator = valid_keys + index; - if (DN_Str8_Init(node->string.str, node->string.size) == validator->node_string) { - result = *validator; - break; - } - } - - if (result.enum_val == 0) { - MD_CodeLoc loc = MD_CodeLocFromNode(DN_CAST(MD_Node *)node); - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - va_list args; - va_start(args, fmt); - DN_Str8 user_msg = DN_Str8_InitFV(tmem.arena, fmt, args); - va_end(args); - - DN_Str8Builder builder = {}; - builder.arena = tmem.arena; - - DN_Str8Builder_AppendF(&builder, "%.*s: '%.*s' is not recognised, the supported values are ", DN_STR_FMT(user_msg), MD_S8VArg(node->string)); - for (DN_USize index = 0; index < valid_keys_size; index++) { - DN_CGenMapNodeToEnum const *validator = valid_keys + index; - DN_Str8Builder_AppendF(&builder, DN_CAST(char *)"%s'%.*s'", index ? ", " : "", DN_STR_FMT(validator->node_string)); - } - - DN_Str8 error_msg = DN_Str8Builder_Build(&builder, tmem.arena); - MD_PrintMessageFmt(stderr, loc, MD_MessageKind_Error, DN_CAST(char *) "%.*s", DN_STR_FMT(error_msg)); - DN_OS_Exit(DN_CAST(uint32_t)-1); - } - return result; -} - -DN_API DN_USize DN_CGen_NodeChildrenCount(MD_Node const *node) -{ - DN_USize result = 0; - for (MD_EachNode(item, node->first_child)) - result++; - return result; -} - -DN_API void DN_CGen_LogF(MD_MessageKind kind, MD_Node *node, DN_ErrSink *err, char const *fmt, ...) -{ - if (!err) - return; - - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - - MD_String8 kind_string = MD_StringFromMessageKind(kind); - MD_CodeLoc loc = MD_CodeLocFromNode(node); - DN_Str8Builder_AppendF(&builder, "" MD_FmtCodeLoc " %.*s: ", MD_CodeLocVArg(loc), MD_S8VArg(kind_string)); - - va_list args; - va_start(args, fmt); - DN_Str8Builder_AppendFV(&builder, fmt, args); - va_end(args); - - DN_Str8 msg = DN_Str8Builder_Build(&builder, tmem.arena); - DN_ErrSink_AppendF(err, DN_CAST(uint32_t)-1, "%.*s", DN_STR_FMT(msg)); -} - -DN_API bool DN_CGen_TableHasHeaders(DN_CGenTable const *table, DN_Str8 const *headers, DN_USize header_count, DN_ErrSink *err) -{ - bool result = true; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8Builder builder = {}; - builder.arena = tmem.arena; - - for (DN_USize index = 0; index < header_count; index++) { - DN_Str8 header = headers[index]; - MD_String8 header_md = {DN_CAST(MD_u8 *) header.data, header.size}; - MD_MapSlot *slot = MD_MapLookup(DN_CAST(MD_Map *)&table->headers_map, MD_MapKeyStr(header_md)); - if (!slot) { - result = false; - DN_Str8Builder_AppendF(&builder, "%s%.*s", builder.count ? ", " : "", DN_STR_FMT(header)); - } - } - - if (!result) { - DN_Str8 missing_headers = DN_Str8Builder_Build(&builder, tmem.arena); - DN_CGen_LogF(MD_MessageKind_Error, - table->headers_node, - err, - "Table '%.*s' is missing the header(s): %.*s", - DN_STR_FMT(table->name), - DN_STR_FMT(missing_headers)); - } - - return result; -} - -DN_API DN_CGenLookupColumnAtHeader DN_CGen_LookupColumnAtHeader(DN_CGenTable *table, DN_Str8 header, DN_CGenTableRow const *row) -{ - DN_CGenLookupColumnAtHeader result = {}; - if (!table || !row) - return result; - - MD_String8 header_md = {DN_CAST(MD_u8 *) header.data, header.size}; - MD_MapSlot *slot = MD_MapLookup(&table->headers_map, MD_MapKeyStr(header_md)); - if (!slot) - return result; - - DN_USize column_index = DN_CAST(DN_USize) slot->val; - DN_ASSERT(column_index < table->column_count); - { - DN_USize begin = DN_CAST(uintptr_t)(table->rows); - DN_USize end = DN_CAST(uintptr_t)(table->rows + table->row_count); - DN_USize ptr = DN_CAST(uintptr_t)row; - DN_ASSERTF(ptr >= begin && ptr <= end, "The row to lookup does not belong to the table passed in"); - } - - result.index = column_index; - result.column = row->columns[column_index]; - result.header = table->headers[column_index]; - return result; -} - -DN_API bool DN_CGen_LookupNextTableInCodeGenTable(DN_CGen *cgen, DN_CGenTable *cgen_table, DN_CGenLookupTableIterator *it) -{ - if (!cgen_table) - return false; - - if (it->row_index >= cgen_table->row_count) - return false; - - if (cgen_table->type != DN_CGenTableType_CodeGenEnum && cgen_table->type != DN_CGenTableType_CodeGenStruct && cgen_table->type != DN_CGenTableType_CodeGenBuiltinTypes) - return false; - - // NOTE: Lookup the table in this row that we will code generate from. Not - // applicable when we are doing builtin types as the types are just put - // in-line into the code generation table itself. - it->cgen_table = cgen_table; - it->cgen_table_row = cgen_table->rows + it->row_index++; - if (cgen_table->type != DN_CGenTableType_CodeGenBuiltinTypes) { - DN_CGenTableColumn cgen_table_column = it->cgen_table_row->columns[cgen_table->column_indexes[DN_CGenTableHeaderType_Table]]; - MD_String8 key = {DN_CAST(MD_u8 *)cgen_table_column.string.data, cgen_table_column.string.size}; - MD_MapSlot *table_slot = MD_MapLookup(&cgen->table_map, MD_MapKeyStr(key)); - if (!table_slot) { - MD_CodeLoc loc = MD_CodeLocFromNode(cgen_table_column.node); - MD_PrintMessageFmt(stderr, - loc, - MD_MessageKind_Warning, - DN_CAST(char *) "Code generation table references non-existent table '%.*s'", - DN_STR_FMT(cgen_table_column.string)); - return false; - } - it->table = DN_CAST(DN_CGenTable *) table_slot->val; - } - - for (DN_USize type = 0; type < DN_CGenTableHeaderType_Count; type++) - it->cgen_table_column[type] = it->cgen_table_row->columns[cgen_table->column_indexes[type]]; - return true; -} - -DN_API bool DN_CGen_WillCodeGenTypeName(DN_CGen const *cgen, DN_Str8 name) -{ - if (!DN_Str8_HasData(name)) - return false; - - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenStruct && table->type != DN_CGenTableType_CodeGenEnum) - continue; - - for (DN_USize row_index = 0; row_index < table->row_count; row_index++) { - DN_CGenTableRow const *row = table->rows + row_index; - DN_CGenTableColumn const *column = row->columns + table->column_indexes[DN_CGenTableHeaderType_Name]; - if (column->string == name) - return true; - } - } - - return false; -} - -static void DN_CGen_EmitRowWhitespace_(DN_CGenTableRow const *row, DN_CppFile *cpp) -{ - for (DN_CGenTableRowTag *tag = row->first_tag; tag; tag = tag->next) { - switch (tag->type) { - case DN_CGenTableRowTagType_Nil: DN_INVALID_CODE_PATH; - - case DN_CGenTableRowTagType_CommentDivider: { - if (tag->comment.size <= 0) - break; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 prefix = DN_Str8_InitF(tmem.arena, "// NOTE: %.*s ", MD_S8VArg(tag->comment)); - int line_padding = DN_MAX(100 - (DN_CAST(int) prefix.size + (DN_CppSpacePerIndent(cpp) * cpp->indent)), 0); - DN_CppPrint(cpp, "%.*s", DN_STR_FMT(prefix)); - for (int index = 0; index < line_padding; index++) - DN_CppAppend(cpp, "/"); - DN_CppAppend(cpp, "\n"); - } break; - - case DN_CGenTableRowTagType_EmptyLine: { - DN_CppAppend(cpp, "\n"); - } break; - } - } -} - -DN_Str8 DN_CGen_StripQualifiersOnCppType_(DN_Arena *arena, DN_Str8 type) -{ - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8 result = DN_Str8_TrimWhitespaceAround(type); - result = DN_Str8_Replace(result, /*find*/ DN_STR8("*"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); - result = DN_Str8_Replace(result, /*find*/ DN_STR8("constexpr"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); - result = DN_Str8_Replace(result, /*find*/ DN_STR8("const"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); - result = DN_Str8_Replace(result, /*find*/ DN_STR8("static"), /*replace*/ DN_STR8(""), /*start_index*/ 0, tmem.arena, DN_Str8EqCase_Sensitive); - result = DN_Str8_Replace(result, /*find*/ DN_STR8(" "), /*replace*/ DN_STR8(""), /*start_index*/ 0, arena, DN_Str8EqCase_Sensitive); - result = DN_Str8_TrimWhitespaceAround(result); - return result; -} - -DN_API void DN_CGen_EmitCodeForTables(DN_CGen *cgen, DN_CGenEmit emit, DN_CppFile *cpp, DN_Str8 emit_prefix) -{ - if (emit & DN_CGenEmit_Prototypes) { - // NOTE: Generate type info enums ////////////////////////////////////////////////////////// - DN_CppEnumBlock(cpp, "%.*sType", DN_STR_FMT(emit_prefix)) { - DN_CppLine(cpp, "%.*sType_Nil,", DN_STR_FMT(emit_prefix)); - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) - DN_CppLine(cpp, "%.*sType_%.*s,", DN_STR_FMT(emit_prefix), DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string)); - } - DN_CppLine(cpp, "%.*sType_Count,", DN_STR_FMT(emit_prefix)); - } - DN_CppNewLine(cpp); - - // NOTE: Generate structs + enums ////////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - switch (table->type) { - case DN_CGenTableType_Nil: DN_INVALID_CODE_PATH; - case DN_CGenTableType_Count: DN_INVALID_CODE_PATH; - case DN_CGenTableType_CodeGenBuiltinTypes: continue; - case DN_CGenTableType_Data: continue; - - case DN_CGenTableType_CodeGenStruct: { - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it); ) { - // TODO(doyle): Verify the codegen table has the headers from the table it references - int longest_type_name = 0; - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); - - DN_USize length = cpp_type.column.string.size; - DN_Str8 find_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); - if (DN_CGen_WillCodeGenTypeName(cgen, find_name)) - length += emit_prefix.size; - - longest_type_name = DN_MAX(longest_type_name, DN_CAST(int)length); - } - - DN_CppStructBlock(cpp, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string)) { - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); - DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); - if (cpp_name.column.string.size <= 0 || cpp_type.column.string.size <= 0) - continue; - - // NOTE: Generate cpp array size /////////////////////////////////// - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 array_size = {}; - if (cpp_array_size.column.string.size) - array_size = DN_Str8_InitF(tmem.arena, "[%.*s]", DN_STR_FMT(cpp_array_size.column.string)); - - // NOTE: Check if we're referencing a code generated type. If we - // are, append the `emit_prefix` - DN_Str8 emit_cpp_type = cpp_type.column.string; - { - DN_Str8 find_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, emit_cpp_type); - if (DN_CGen_WillCodeGenTypeName(cgen, find_name)) - emit_cpp_type = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(cpp_type.column.string)); - } - - int name_to_type_padding = 1 + longest_type_name - DN_CAST(int) emit_cpp_type.size; - - // NOTE: Emit decl ///////////////////////////////////////////////// - DN_CGen_EmitRowWhitespace_(row, cpp); - DN_CppLine(cpp, - "%.*s%*s%.*s%.*s;", - DN_STR_FMT(emit_cpp_type), - name_to_type_padding, - "", - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(array_size)); - } - } - DN_CppNewLine(cpp); - DN_CppNewLine(cpp); - } - - } break; - - case DN_CGenTableType_CodeGenEnum: { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it); ) { - DN_CppEnumBlock(cpp, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string)) { - DN_USize enum_count = 0; - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - DN_CGenLookupColumnAtHeader cpp_value = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppValue].string, row); - - if (cpp_name.column.string.size <= 0) - continue; - - DN_CGen_EmitRowWhitespace_(row, cpp); - if (cpp_value.column.string.size) { - DN_CppLine(cpp, - "%.*s%.*s_%.*s = %.*s,", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_value.column.string)); - } else { - DN_CppLine(cpp, - "%.*s%.*s_%.*s = %zu,", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), - DN_STR_FMT(cpp_name.column.string), - row_index); - } - enum_count++; - } - - DN_CGenTableColumn gen_enum_count_column = it.cgen_table_column[DN_CGenTableHeaderType_GenEnumCount]; - if (gen_enum_count_column.string.size) - DN_CppLine(cpp, - "%.*s%.*s_%.*s = %zu,", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(it.cgen_table_column[DN_CGenTableHeaderType_Name].string), - DN_STR_FMT(gen_enum_count_column.string), - enum_count); - } - DN_CppNewLine(cpp); - } - } break; - } - } - - // NOTE: Generate enums for struct fields ////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - switch (table->type) { - case DN_CGenTableType_Nil: DN_INVALID_CODE_PATH; - case DN_CGenTableType_Count: DN_INVALID_CODE_PATH; - case DN_CGenTableType_Data: continue; - case DN_CGenTableType_CodeGenBuiltinTypes: continue; - case DN_CGenTableType_CodeGenEnum: continue; - - case DN_CGenTableType_CodeGenStruct: { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it); ) { - DN_Str8 struct_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppEnumBlock(cpp, "%.*s%.*sTypeField", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name)) { - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - if (cpp_name.column.string.size <= 0) - continue; - DN_CGen_EmitRowWhitespace_(row, cpp); - DN_CppLine(cpp, "%.*s%.*sTypeField_%.*s,", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name), DN_STR_FMT(cpp_name.column.string)); - } - DN_CppLine(cpp, "%.*s%.*sTypeField_Count,", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_name)); - } - DN_CppNewLine(cpp); - } - } break; - } - } - - // NOTE: Str8 to enum conversion //////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenEnum) - continue; - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppStructBlock(cpp, "%.*s%.*sStr8ToEnumResult", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppLine(cpp, "bool success;"); - DN_CppLine(cpp, "%.*s%.*s value;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - } - DN_CppNewLine(cpp); - } - } - - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenEnum) - continue; - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppLine(cpp, - "%.*s%.*sStr8ToEnumResult %.*s%.*s_NameStr8ToEnum(DN_Str8 string);", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name), - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name)); - DN_CppLine(cpp, - "%.*s%.*sStr8ToEnumResult %.*s%.*s_LabelStr8ToEnum(DN_Str8 string);", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name), - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name)); - } - } - - // NOTE: Operator == and != //////////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenStruct) - continue; - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_Str8 cpp_op_equals = it.cgen_table_column[DN_CGenTableHeaderType_CppOpEquals].string; - if (cpp_op_equals != DN_STR8("true")) - continue; - - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppLine(cpp, - "bool operator==(%.*s%.*s const &lhs, %.*s%.*s const &rhs);", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name), - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name)); - DN_CppLine(cpp, - "bool operator!=(%.*s%.*s const &lhs, %.*s%.*s const &rhs);", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name), - DN_STR_FMT(emit_prefix), - DN_STR_FMT(type_name)); - } - } - } - - if (emit & DN_CGenEmit_Implementation) { - // NOTE: Generate type info //////////////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - - if (table->type == DN_CGenTableType_CodeGenBuiltinTypes) - continue; - - DN_Str8 struct_or_enum_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppBlock(cpp, ";\n\n", "DN_TypeField const g_%.*s%.*s_type_fields[] =", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_or_enum_name)) { - if (table->type == DN_CGenTableType_CodeGenStruct) { - - // NOTE: Construct the cpp type string first. We will prepend `emit_prefix` - // for types that are declared in the same mdesk file. We will also - // calculate the longest type name that we will generate for whitespace - // padding purposes. - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_USize longest_cpp_type_name = 0; - auto cpp_type_list = DN_SArray_Init(tmem.arena, it.table->row_count, DN_ZeroMem_Yes); - - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); - DN_Str8 cpp_type_name = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); - if (DN_CGen_WillCodeGenTypeName(cgen, cpp_type_name)) - cpp_type_name = DN_Str8_InitF_TLS("%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(cpp_type_name)); - - longest_cpp_type_name = DN_MAX(longest_cpp_type_name, cpp_type_name.size); - DN_SArray_Add(&cpp_type_list, cpp_type_name); - } - - // NOTE: Iterate each row and emit the C++ declarations //////////////////// - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - DN_CGenLookupColumnAtHeader cpp_type = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppType].string, row); - DN_CGenLookupColumnAtHeader cpp_is_ptr = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppIsPtr].string, row); - DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); - DN_CGenLookupColumnAtHeader cpp_array_size_field = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySizeField].string, row); - DN_CGenLookupColumnAtHeader cpp_label = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppLabel].string, row); - - bool cpp_is_ptr_b32 = cpp_is_ptr.column.string == DN_STR8("true"); - DN_Str8 cpp_array_size_str8 = DN_Str8_HasData(cpp_array_size.column.string) ? cpp_array_size.column.string : DN_STR8("0"); - - DN_CGenTableColumn struct_name = it.cgen_table_row->columns[table->column_indexes[DN_CGenTableHeaderType_Name]]; - DN_Str8 cpp_array_size_field_str8 = DN_STR8("NULL"); - if (cpp_array_size_field.column.string.size) { - - // TODO(doyle): Check that array_size_field references a valid field in the table - // NOTE: We use a raw index for the reference because the struct might - // not have type info being generated so we can't rely on the enum. - DN_USize index_the_field_references = 0; - for (DN_USize sub_row_index = 0; sub_row_index < it.table->row_count; sub_row_index++) { - DN_CGenTableRow const *sub_row = it.table->rows + sub_row_index; - DN_CGenTableColumn sub_cpp_name = sub_row->columns[cpp_name.index]; - if (sub_cpp_name.string == cpp_array_size_field.column.string) - index_the_field_references = sub_row_index; - } - cpp_array_size_field_str8 = - DN_Str8_InitF_TLS("&g_%.*s%.*s_type_fields[%zu]", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(struct_name.string), - index_the_field_references); - } - - DN_Str8 cpp_type_name = cpp_type_list.data[row_index]; - DN_Str8 orig_cpp_type = DN_CGen_StripQualifiersOnCppType_(tmem.arena, cpp_type.column.string); - - DN_USize cpp_name_padding = 1 + it.table->headers[cpp_name.index].longest_string - cpp_name.column.string.size; - DN_USize cpp_type_padding = 1 + longest_cpp_type_name - cpp_type_name.size; - - DN_Str8 cpp_type_enum = DN_Str8_InitF_TLS("%.*sType_%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(orig_cpp_type)); - DN_USize cpp_type_enum_padding = cpp_type_padding + (orig_cpp_type.size - cpp_type_name.size); - - DN_Str8 cpp_label_str8 = cpp_name.column.string; - DN_USize cpp_label_str8_padding = cpp_name_padding; - if (cpp_label.column.string.size) { - cpp_label_str8 = cpp_label.column.string; - cpp_label_str8_padding = 1 + it.table->headers[cpp_label.index].longest_string - cpp_label.column.string.size; - } - - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - - // NOTE: row - DN_Str8Builder_AppendF(&builder, "{%2d, ", row_index); - - // NOTE: name - DN_Str8Builder_AppendF(&builder, - "DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(cpp_name.column.string), - cpp_name_padding, ""); - - // NOTE: label - DN_Str8Builder_AppendF(&builder, - "DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(cpp_label_str8), - cpp_label_str8_padding, ""); - - // NOTE: value - DN_Str8Builder_AppendF(&builder, - "/*value*/ 0, "); - - - // NOTE: offsetof(a, b) - DN_Str8Builder_AppendF(&builder, - "offsetof(%.*s%.*s, %.*s),%*s", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(struct_or_enum_name), - DN_STR_FMT(cpp_name.column.string), - cpp_name_padding, ""); - - // NOTE: sizeof(a->b) - DN_Str8Builder_AppendF(&builder, - "sizeof(((%.*s%.*s*)0)->%.*s),%*s", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(struct_or_enum_name), - DN_STR_FMT(cpp_name.column.string), - cpp_name_padding, ""); - - // NOTE: alignof(a) - if (cpp_type_name == DN_STR8("void")) { - DN_Str8 proxy_type = DN_STR8("char"); - DN_USize proxy_type_padding = 1 + longest_cpp_type_name - proxy_type.size; - DN_Str8Builder_AppendF(&builder, "alignof(%.*s),%*s", DN_STR_FMT(proxy_type), proxy_type_padding, ""); - } else { - DN_Str8Builder_AppendF(&builder, - "alignof(%.*s),%*s", - DN_STR_FMT(cpp_type_name), - cpp_type_padding, ""); - } - - // NOTE: Type string - DN_Str8Builder_AppendF(&builder, - "DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(cpp_type_name), - cpp_type_padding, ""); - - // NOTE: Type as enum - DN_Str8Builder_AppendF(&builder, - "%.*s,%*s", - DN_STR_FMT(cpp_type_enum), - cpp_type_enum_padding, ""); - - DN_Str8Builder_AppendF(&builder, - "/*is_pointer*/ %s,%s /*array_size*/ %.*s, /*array_size_field*/ %.*s},", - cpp_is_ptr_b32 ? "true" : "false", - cpp_is_ptr_b32 ? " " : "", - DN_STR_FMT(cpp_array_size_str8), - DN_STR_FMT(cpp_array_size_field_str8)); - - DN_Str8 line = DN_Str8Builder_Build(&builder, tmem.arena); - DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); - } - } else { - DN_ASSERT(table->type == DN_CGenTableType_CodeGenEnum); - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - DN_CGenLookupColumnAtHeader cpp_value = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppValue].string, row); - DN_CGenLookupColumnAtHeader cpp_label = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppLabel].string, row); - if (cpp_name.column.string.size <= 0) - continue; - - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_USize cpp_name_padding = 1 + it.table->headers[cpp_name.index].longest_string - cpp_name.column.string.size; - DN_Str8 cpp_value_str8 = DN_Str8_HasData(cpp_value.column.string) ? cpp_value.column.string : DN_Str8_InitF_TLS("%zu", row_index); - DN_Str8 cpp_type_enum = DN_Str8_InitF_TLS("%.*sType_%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(struct_or_enum_name)); - - DN_Str8 cpp_label_str8 = cpp_name.column.string; - DN_USize cpp_label_str8_padding = cpp_name_padding; - if (cpp_label.column.string.size) { - cpp_label_str8 = cpp_label.column.string; - cpp_label_str8_padding = 1 + it.table->headers[cpp_label.index].longest_string - cpp_label.column.string.size; - } - - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - // NOTE: row - DN_Str8Builder_AppendF(&builder, "{%2d, ", row_index); - - // NOTE: name - DN_Str8Builder_AppendF(&builder, - "DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(cpp_name.column.string), - cpp_name_padding, ""); - - // NOTE: label - DN_Str8Builder_AppendF(&builder, - "DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(cpp_label_str8), - cpp_label_str8_padding, ""); - - // NOTE: value - DN_Str8Builder_AppendF(&builder, "/*value*/ %.*s, ", DN_STR_FMT(cpp_value_str8)); - - // NOTE: offsetof(a, b) - DN_Str8Builder_AppendF(&builder, "/*offsetof*/ 0, "); - - // NOTE: sizeof(a->b) - DN_Str8Builder_AppendF(&builder, - "sizeof(%.*s%.*s), ", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(struct_or_enum_name)); - - // NOTE: alignof(a->b) - DN_Str8Builder_AppendF(&builder, - "alignof(%.*s%.*s), ", - DN_STR_FMT(emit_prefix), - DN_STR_FMT(struct_or_enum_name)); - - // TODO: Type string - DN_Str8Builder_AppendF(&builder, "DN_STR8(\"\"), "); - - // NOTE: Type as enum - DN_Str8Builder_AppendF(&builder, "%.*s, ", DN_STR_FMT(cpp_type_enum)); - - DN_Str8Builder_AppendF(&builder, "/*is_pointer*/ false, "); - DN_Str8Builder_AppendF(&builder, "/*array_size*/ 0, "); - DN_Str8Builder_AppendF(&builder, "/*array_size_field*/ NULL},"); - - DN_Str8 line = DN_Str8Builder_Build_TLS(&builder); - DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); - } - } - } - } - } - - int longest_name_across_all_tables = 0; - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) - type_name = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - - longest_name_across_all_tables = DN_MAX(longest_name_across_all_tables, DN_CAST(int)type_name.size); - } - } - - DN_CppBlock(cpp, ";\n\n", "DN_TypeInfo const g_%.*stypes[] =", DN_STR_FMT(emit_prefix)) { - DN_CppLine(cpp, "{DN_STR8(\"\"),%*sDN_TypeKind_Nil, 0, /*fields*/ NULL, /*count*/ 0},", 1 + longest_name_across_all_tables, ""); - - DN_USize longest_type_name = 0; - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) - type_name = DN_Str8_InitF(tmem.arena, "%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - longest_type_name = DN_MAX(longest_type_name, type_name.size); - } - } - - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - if (DN_CGen_WillCodeGenTypeName(cgen, type_name)) - type_name = DN_Str8_InitF_TLS("%.*s%.*s", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - - int name_padding = 1 + longest_name_across_all_tables - DN_CAST(int) type_name.size; - DN_Str8 type_info_kind = {}; - char const *type_info_kind_padding = ""; - if (table->type == DN_CGenTableType_CodeGenEnum) { - type_info_kind = DN_STR8("DN_TypeKind_Enum"); - type_info_kind_padding = " "; - } else if (table->type == DN_CGenTableType_CodeGenStruct) { - type_info_kind = DN_STR8("DN_TypeKind_Struct"); - } else { - DN_ASSERT(table->type == DN_CGenTableType_CodeGenBuiltinTypes); - type_info_kind = DN_STR8("DN_TypeKind_Basic"); - type_info_kind_padding = " "; - } - - DN_Str8 fields_count = {}; - if (table->type == DN_CGenTableType_CodeGenBuiltinTypes) { - fields_count = DN_STR8("0"); - } else { - DN_ASSERT(table->type == DN_CGenTableType_CodeGenStruct || table->type == DN_CGenTableType_CodeGenEnum); - int fields_count_int = 0; - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - fields_count_int += DN_Str8_HasData(cpp_name.column.string); - } - fields_count = DN_Str8_InitF_TLS("%d", fields_count_int); - } - - DN_Str8 fields = DN_STR8("NULL"); - int fields_padding = 1; - if (table->type != DN_CGenTableType_CodeGenBuiltinTypes) { - fields_padding = name_padding; - fields = DN_Str8_InitF(tmem.arena, "g_%.*s_type_fields", DN_STR_FMT(type_name)); - } - - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - - // NOTE: name - DN_Str8Builder_AppendF(&builder, - "{DN_STR8(\"%.*s\"),%*s", - DN_STR_FMT(type_name), - name_padding, - ""); - - // NOTE: DN_TypeKind_{Nil|Basic|Enum|Struct} - DN_Str8Builder_AppendF(&builder, - "%.*s,%s", - DN_STR_FMT(type_info_kind), - type_info_kind_padding); - - // NOTE: sizeof(T) - if (type_name == DN_STR8("void")) { - DN_Str8Builder_AppendF(&builder, "0,%*s", name_padding, ""); - } else { - DN_Str8Builder_AppendF(&builder, - "sizeof(%.*s),%*s", - DN_STR_FMT(type_name), - name_padding, - ""); - } - - // NOTE: Pointer to DN_TypeField[] - DN_Str8Builder_AppendF(&builder, - "/*fields*/ %.*s,%*s", - DN_STR_FMT(fields), - fields_padding, - ""); - - // NOTE: DN_TypeField length - DN_Str8Builder_AppendF(&builder, - "/*count*/ %.*s},", - DN_STR_FMT(fields_count)); - - DN_Str8 line = DN_Str8Builder_Build_TLS(&builder); - DN_CppLine(cpp, "%.*s", DN_STR_FMT(line)); - } - } - } - - // NOTE: Str8 to enum conversion //////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenEnum) - continue; - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - - DN_CppFuncBlock(cpp, "%.*s%.*sStr8ToEnumResult %.*s%.*s_NameStr8ToEnum(DN_Str8 string)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppLine(cpp, "%.*s%.*sStr8ToEnumResult result = {};", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - DN_CppForBlock(cpp, "DN_USize index = 0; !result.success && index < DN_ARRAY_UCOUNT(g_%.*s%.*s_type_fields); index++", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppIfChain(cpp) { - DN_CppLine(cpp, "DN_TypeField field = g_%.*s%.*s_type_fields[index];", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - DN_CppIfOrElseIfBlock(cpp, "DN_Str8_EqInsensitive(string, field.name)") { - DN_CppLine(cpp, "result.success = true;"); - DN_CppLine(cpp, "result.value = (%.*s%.*s)index;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - } - } - } - DN_CppLine(cpp, "return result;"); - } - DN_CppNewLine(cpp); - - DN_CppFuncBlock(cpp, "%.*s%.*sStr8ToEnumResult %.*s%.*s_LabelStr8ToEnum(DN_Str8 string)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppLine(cpp, "%.*s%.*sStr8ToEnumResult result = {};", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - DN_CppForBlock(cpp, "DN_USize index = 0; !result.success && index < DN_ARRAY_UCOUNT(g_%.*s%.*s_type_fields); index++", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppIfChain(cpp) { - DN_CppLine(cpp, "DN_TypeField field = g_%.*s%.*s_type_fields[index];", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - DN_CppIfOrElseIfBlock(cpp, "DN_Str8_EqInsensitive(string, field.label)") { - DN_CppLine(cpp, "result.success = true;"); - DN_CppLine(cpp, "result.value = (%.*s%.*s)index;", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)); - } - } - } - DN_CppLine(cpp, "return result;"); - } - DN_CppNewLine(cpp); - } - } - - // NOTE: Operator == and != //////////////////////////////////////////////////////////////// - for (DN_CGenTable *table = cgen->first_table; table != 0; table = table->next) { - if (table->type != DN_CGenTableType_CodeGenStruct) - continue; - - for (DN_CGenLookupTableIterator it = {}; DN_CGen_LookupNextTableInCodeGenTable(cgen, table, &it);) { - DN_Str8 cpp_op_equals = it.cgen_table_column[DN_CGenTableHeaderType_CppOpEquals].string; - if (cpp_op_equals != DN_STR8("true")) - continue; - - DN_Str8 type_name = it.cgen_table_column[DN_CGenTableHeaderType_Name].string; - DN_CppFuncBlock(cpp, "bool operator==(%.*s%.*s const &lhs, %.*s%.*s const &rhs)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - for (DN_USize row_index = 0; row_index < it.table->row_count; row_index++) { - DN_CGenTableRow const *row = it.table->rows + row_index; - DN_CGenLookupColumnAtHeader cpp_name = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppName].string, row); - DN_CGenLookupColumnAtHeader cpp_is_ptr = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppIsPtr].string, row); - DN_CGenLookupColumnAtHeader cpp_array_size = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySize].string, row); - DN_CGenLookupColumnAtHeader cpp_array_size_field = DN_CGen_LookupColumnAtHeader(it.table, it.cgen_table_column[DN_CGenTableHeaderType_CppArraySizeField].string, row); - - // TODO(doyle): Check if we're an integral type or not to double check if we - // can use memcmp or operator== - if (DN_Str8_HasData(cpp_array_size_field.column.string)) { - DN_CppIfChain(cpp) { - DN_CppIfOrElseIfBlock(cpp, - "lhs.%.*s != rhs.%.*s", - DN_STR_FMT(cpp_array_size_field.column.string), - DN_STR_FMT(cpp_array_size_field.column.string)) { - DN_CppLine(cpp, "return false;"); - } - } - DN_CppIfChain(cpp) { - DN_CppIfOrElseIfBlock(cpp, - "DN_MEMCMP(lhs.%.*s, rhs.%.*s, lhs.%.*s) != 0", - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_array_size_field.column.string)) { - DN_CppLine(cpp, "return false;"); - } - } - } else if (DN_Str8_HasData(cpp_array_size.column.string)) { - DN_CppIfChain(cpp) { - DN_CppIfOrElseIfBlock(cpp, - "DN_MEMCMP(lhs.%.*s, rhs.%.*s, %.*s) != 0", - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_array_size.column.string)) { - DN_CppLine(cpp, "return false;"); - } - } - } else if (cpp_is_ptr.column.string == DN_STR8("true")) { - DN_CppIfChain(cpp) { - DN_CppIfOrElseIfBlock(cpp, - "*lhs.%.*s != *rhs.%.*s", - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_name.column.string)) { - DN_CppLine(cpp, "return false;"); - } - } - } else { - DN_CppIfChain(cpp) { - DN_CppIfOrElseIfBlock(cpp, - "lhs.%.*s != rhs.%.*s", - DN_STR_FMT(cpp_name.column.string), - DN_STR_FMT(cpp_name.column.string)) { - DN_CppLine(cpp, "return false;"); - } - } - } - } - DN_CppLine(cpp, "return true;"); - } - DN_CppNewLine(cpp); - - DN_CppFuncBlock(cpp, "bool operator!=(%.*s%.*s const &lhs, %.*s%.*s const &rhs)", DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name), DN_STR_FMT(emit_prefix), DN_STR_FMT(type_name)) { - DN_CppLine(cpp, "bool result = !(lhs == rhs);"); - DN_CppLine(cpp, "return result;"); - } - DN_CppNewLine(cpp); - } - } - } -} diff --git a/dqn_cgen.h b/dqn_cgen.h deleted file mode 100644 index e2d4044..0000000 --- a/dqn_cgen.h +++ /dev/null @@ -1,174 +0,0 @@ -#if !defined(DN_CGEN_H) -#define DN_CGEN_H - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$$$$$$$\ $$\ $$\ -// $$ __$$\ $$ __$$\ $$ _____|$$$\ $$ | -// $$ / \__|$$ / \__|$$ | $$$$\ $$ | -// $$ | $$ |$$$$\ $$$$$\ $$ $$\$$ | -// $$ | $$ |\_$$ |$$ __| $$ \$$$$ | -// $$ | $$\ $$ | $$ |$$ | $$ |\$$$ | -// \$$$$$$ |\$$$$$$ |$$$$$$$$\ $$ | \$$ | -// \______/ \______/ \________|\__| \__| -// -// dqn_cgen.h -- C/C++ code generation from table data in Metadesk files -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$CGEN] DN_CGen ////////////////////////////////////////////////////////////////////////// -#if !defined(MD_H) -#error Metadesk 'md.h' must be included before 'dn_cgen.h' -#endif - -#if !defined(DN_H) -#error 'dqn.h' must be included before 'dn_cgen.h' -#endif - -#if !defined(DN_CPP_FILE_H) -#error 'dn_cpp_file.h' must be included before 'dn_cgen.h' -#endif - -enum DN_CGenTableKeyType -{ - DN_CGenTableKeyType_Nil, - DN_CGenTableKeyType_Name, - DN_CGenTableKeyType_Type, -}; - -enum DN_CGenTableType -{ - DN_CGenTableType_Nil, - DN_CGenTableType_Data, - DN_CGenTableType_CodeGenBuiltinTypes, - DN_CGenTableType_CodeGenStruct, - DN_CGenTableType_CodeGenEnum, - DN_CGenTableType_Count, -}; - -enum DN_CGenTableRowTagType -{ - DN_CGenTableRowTagType_Nil, - DN_CGenTableRowTagType_CommentDivider, - DN_CGenTableRowTagType_EmptyLine, -}; - -enum DN_CGenTableRowTagCommentDivider -{ - DN_CGenTableRowTagCommentDivider_Nil, - DN_CGenTableRowTagCommentDivider_Label, -}; - -enum DN_CGenTableHeaderType -{ - DN_CGenTableHeaderType_Name, - DN_CGenTableHeaderType_Table, - DN_CGenTableHeaderType_CppType, - DN_CGenTableHeaderType_CppName, - DN_CGenTableHeaderType_CppValue, - DN_CGenTableHeaderType_CppIsPtr, - DN_CGenTableHeaderType_CppOpEquals, - DN_CGenTableHeaderType_CppArraySize, - DN_CGenTableHeaderType_CppArraySizeField, - DN_CGenTableHeaderType_CppLabel, - DN_CGenTableHeaderType_GenTypeInfo, - DN_CGenTableHeaderType_GenEnumCount, - DN_CGenTableHeaderType_Count, -}; - -struct DN_CGenTableHeader -{ - MD_String8 name; - int longest_string; -}; - -struct DN_CGenTableRowTag -{ - DN_CGenTableRowTagType type; - MD_String8 comment; - DN_CGenTableRowTag *next; -}; - - -struct DN_CGenTableColumn -{ - MD_Node *node; - DN_Str8 string; -}; - -struct DN_CGenTableRow -{ - DN_CGenTableRowTag *first_tag; - DN_CGenTableRowTag *last_tag; - DN_CGenTableColumn *columns; -}; - -struct DN_CGenTable -{ - DN_CGenTableType type; - DN_Str8 name; - MD_Map headers_map; - DN_CGenTableHeader *headers; - DN_CGenTableRow *rows; - size_t column_count; - size_t row_count; - - MD_Node *node; - MD_Node *headers_node; - DN_USize column_indexes[DN_CGenTableHeaderType_Count]; - DN_CGenTable *next; -}; - -struct DN_CGen -{ - MD_Arena *arena; - MD_Node *file_list; - MD_Map table_map; - DN_CGenTable *first_table; - DN_CGenTable *last_table; - DN_USize table_counts[DN_CGenTableType_Count]; -}; - -struct DN_CGenMapNodeToEnum -{ - uint32_t enum_val; - DN_Str8 node_string; -}; - -struct DN_CGenLookupTableIterator -{ - DN_CGenTable *cgen_table; - DN_CGenTableRow *cgen_table_row; - DN_CGenTableColumn cgen_table_column[DN_CGenTableHeaderType_Count]; - DN_CGenTable *table; - DN_USize row_index; -}; - -struct DN_CGenLookupColumnAtHeader -{ - DN_USize index; - DN_CGenTableHeader header; - DN_CGenTableColumn column; -}; - -enum DN_CGenEmit -{ - DN_CGenEmit_Prototypes = 1 << 0, - DN_CGenEmit_Implementation = 1 << 1, -}; - -// NOTE: [$CGEN] DN_CGen ////////////////////////////////////////////////////////////////////////// -#define DN_CGen_MDToDNStr8(str8) DN_Str8_Init((str8).str, (str8).size) -#define DN_CGen_DNToMDStr8(str8) {DN_CAST(MD_u8 *)(str8).data, (str8).size} -DN_API DN_CGen DN_CGen_InitFilesArgV (int argc, char const **argv, DN_ErrSink *err); -DN_API DN_Str8 DN_CGen_TableHeaderTypeToDeclStr8 (DN_CGenTableHeaderType type); -DN_API DN_CGenMapNodeToEnum DN_CGen_MapNodeToEnumOrExit (MD_Node const *node, DN_CGenMapNodeToEnum const *valid_keys, DN_USize valid_keys_size, char const *fmt, ...); -DN_API DN_USize DN_CGen_NodeChildrenCount (MD_Node const *node); -DN_API void DN_CGen_LogF (MD_MessageKind kind, MD_Node *node, DN_ErrSink *err, char const *fmt, ...); -DN_API bool DN_CGen_TableHasHeaders (DN_CGenTable const *table, DN_Str8 const *headers, DN_USize header_count, DN_ErrSink *err); -DN_API DN_CGenLookupColumnAtHeader DN_CGen_LookupColumnAtHeader (DN_CGenTable *table, DN_Str8 header, DN_CGenTableRow const *row); -DN_API bool DN_CGen_LookupNextTableInCodeGenTable(DN_CGen *cgen, DN_CGenTable *cgen_table, DN_CGenLookupTableIterator *it); -DN_API void DN_CGen_EmitCodeForTables (DN_CGen *cgen, DN_CGenEmit emit, DN_CppFile *cpp, DN_Str8 emit_prefix); -#endif // DN_CGEN_H diff --git a/dqn_containers.cpp b/dqn_containers.cpp deleted file mode 100644 index 7190eae..0000000 --- a/dqn_containers.cpp +++ /dev/null @@ -1,1406 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ -// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| -// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ -// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ -// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | -// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | -// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ -// -// dqn_containers.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$CARR] DN_CArray //////////////////////////////////////////////////////////////////////// -template DN_ArrayEraseResult DN_CArray_EraseRange(T* data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = {}; - if (!data || !size || *size == 0 || count == 0) - return result; - - DN_ASSERTF(count != -1, "There's a bug with negative element erases, see the DN_VArray section in dn_docs.cpp"); - - // NOTE: Caculate the end index of the erase range - DN_ISize abs_count = DN_ABS(count); - DN_USize end_index = 0; - if (count < 0) { - end_index = begin_index - (abs_count - 1); - if (end_index > begin_index) - end_index = 0; - } else { - end_index = begin_index + (abs_count - 1); - if (end_index < begin_index) - end_index = (*size) - 1; - } - - // NOTE: Ensure begin_index < one_past_end_index - if (end_index < begin_index) { - DN_USize tmp = begin_index; - begin_index = end_index; - end_index = tmp; - } - - // NOTE: Ensure indexes are within valid bounds - begin_index = DN_MIN(begin_index, *size); - end_index = DN_MIN(end_index, *size - 1); - - // NOTE: Erase the items in the range [begin_index, one_past_end_index) - DN_USize one_past_end_index = end_index + 1; - DN_USize erase_count = one_past_end_index - begin_index; - if (erase_count) { - T *end = data + *size; - T *dest = data + begin_index; - if (erase == DN_ArrayErase_Stable) { - T *src = dest + erase_count; - DN_MEMMOVE(dest, src, (end - src) * sizeof(T)); - } else { - T *src = end - erase_count; - DN_MEMCPY(dest, src, (end - src) * sizeof(T)); - } - *size -= erase_count; - } - - result.items_erased = erase_count; - result.it_index = begin_index; - return result; -} - -template T *DN_CArray_MakeArray(T* data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem) -{ - if (!data || !size || count == 0) - return nullptr; - - if (!DN_CHECKF((*size + count) <= max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, *size, max)) - return nullptr; - - // TODO: Use placement new? Why doesn't this work? - T *result = data + *size; - *size += count; - if (zero_mem == DN_ZeroMem_Yes) - DN_MEMSET(result, 0, sizeof(*result) * count); - return result; -} - -template T *DN_CArray_InsertArray(T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count) -{ - T *result = nullptr; - if (!data || !size || !items || count <= 0 || ((*size + count) > max)) - return result; - - DN_USize clamped_index = DN_MIN(index, *size); - if (clamped_index != *size) { - char const *src = DN_CAST(char *)(data + clamped_index); - char const *dest = DN_CAST(char *)(data + (clamped_index + count)); - char const *end = DN_CAST(char *)(data + (*size)); - DN_USize bytes_to_move = end - src; - DN_MEMMOVE(DN_CAST(void *) dest, src, bytes_to_move); - } - - result = data + clamped_index; - DN_MEMCPY(result, items, sizeof(T) * count); - *size += count; - return result; -} - -template T DN_CArray_PopFront(T* data, DN_USize *size, DN_USize count) -{ - T result = {}; - if (!data || !size || *size <= 0) - return result; - - result = data[0]; - DN_USize pop_count = DN_MIN(count, *size); - DN_MEMMOVE(data, data + pop_count, (*size - pop_count) * sizeof(T)); - *size -= pop_count; - return result; -} - -template T DN_CArray_PopBack(T* data, DN_USize *size, DN_USize count) -{ - T result = {}; - if (!data || !size || *size <= 0) - return result; - - DN_USize pop_count = DN_MIN(count, *size); - result = data[(*size - 1)]; - *size -= pop_count; - return result; -} - -template DN_ArrayFindResult DN_CArray_Find(T *data, DN_USize size, T const &value) -{ - DN_ArrayFindResult result = {}; - if (!data || size <= 0) - return result; - - for (DN_USize index = 0; !result.data && index < size; index++) { - T *item = data + index; - if (*item == value) { - result.data = item; - result.index = index; - } - } - - return result; -} - -#if !defined(DN_NO_VARRAY) -// NOTE: [$VARR] DN_VArray //////////////////////////////////////////////////////////////////////// -template DN_VArray DN_VArray_InitByteSize(DN_USize byte_size) -{ - DN_VArray result = {}; - result.data = DN_CAST(T *)DN_OS_MemReserve(byte_size, DN_OSMemCommit_No, DN_OSMemPage_ReadWrite); - if (result.data) - result.max = byte_size / sizeof(T); - return result; -} - -template DN_VArray DN_VArray_Init(DN_USize max) -{ - DN_VArray result = DN_VArray_InitByteSize(max * sizeof(T)); - DN_ASSERT(result.max >= max); - return result; -} - -template DN_VArray DN_VArray_InitSlice(DN_Slice slice, DN_USize max) -{ - DN_USize real_max = DN_MAX(slice.size, max); - DN_VArray result = DN_VArray_Init(real_max); - if (DN_VArray_IsValid(&result)) - DN_VArray_AddArray(&result, slice.data, slice.size); - return result; -} - -template DN_VArray DN_VArray_InitCArray(T const (&items)[N], DN_USize max) -{ - DN_USize real_max = DN_MAX(N, max); - DN_VArray result = DN_VArray_InitSlice(DN_Slice_Init(items, N), real_max); - return result; -} - -template void DN_VArray_Deinit(DN_VArray *array) -{ - DN_OS_MemRelease(array->data, array->max * sizeof(T)); - *array = {}; -} - -template bool DN_VArray_IsValid(DN_VArray const *array) -{ - bool result = array->data && array->size <= array->max; - return result; -} - -template DN_Slice DN_VArray_Slice(DN_VArray const *array) -{ - DN_Slice result = {}; - if (array) - result = DN_Slice_Init(array->data, array->size); - return result; -} - -template T *DN_VArray_AddArray(DN_VArray *array, T const *items, DN_USize count) -{ - T *result = DN_VArray_MakeArray(array, count, DN_ZeroMem_No); - if (result) - DN_MEMCPY(result, items, count * sizeof(T)); - return result; -} - -template T *DN_VArray_AddCArray(DN_VArray *array, T const (&items)[N]) -{ - T *result = DN_VArray_AddArray(array, items, N); - return result; -} - -template T *DN_VArray_Add(DN_VArray *array, T const &item) -{ - T *result = DN_VArray_AddArray(array, &item, 1); - return result; -} - -template T *DN_VArray_MakeArray(DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem) -{ - if (!DN_VArray_IsValid(array)) - return nullptr; - - if (!DN_CHECKF((array->size + count) < array->max, "Array is out of space (user requested +%zu items, array has %zu/%zu items)", count, array->size, array->max)) - return nullptr; - - if (!DN_VArray_Reserve(array, count)) - return nullptr; - - // TODO: Use placement new - T *result = array->data + array->size; - array->size += count; - if (zero_mem == DN_ZeroMem_Yes) - DN_MEMSET(result, 0, count * sizeof(T)); - return result; -} - -template T *DN_VArray_Make(DN_VArray *array, DN_ZeroMem zero_mem) -{ - T *result = DN_VArray_MakeArray(array, 1, zero_mem); - return result; -} - -template T *DN_VArray_InsertArray(DN_VArray *array, DN_USize index, T const *items, DN_USize count) -{ - T *result = nullptr; - if (!DN_VArray_IsValid(array)) - return result; - if (DN_VArray_Reserve(array, array->size + count)) - result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); - return result; -} - -template T *DN_VArray_InsertCArray(DN_VArray *array, DN_USize index, T const (&items)[N]) -{ - T *result = DN_VArray_InsertArray(array, index, items, N); - return result; -} - -template T *DN_VArray_Insert(DN_VArray *array, DN_USize index, T const &item) -{ - T *result = DN_VArray_InsertArray(array, index, &item, 1); - return result; -} - -template T *DN_VArray_PopFront(DN_VArray *array, DN_USize count) -{ - T *result = DN_CArray_PopFront(array->data, &array->size, count); - return result; -} - -template T *DN_VArray_PopBack(DN_VArray *array, DN_USize count) -{ - T *result = DN_CArray_PopBack(array->data, &array->size, count); - return result; -} - -template DN_ArrayEraseResult DN_VArray_EraseRange(DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = {}; - if (!DN_VArray_IsValid(array)) - return result; - result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); - return result; -} - -template void DN_VArray_Clear(DN_VArray *array, DN_ZeroMem zero_mem) -{ - if (array) { - if (zero_mem == DN_ZeroMem_Yes) - DN_MEMSET(array->data, 0, array->size * sizeof(T)); - array->size = 0; - } -} - -template bool DN_VArray_Reserve(DN_VArray *array, DN_USize count) -{ - if (!DN_VArray_IsValid(array) || count == 0) - return false; - - DN_USize real_commit = (array->size + count) * sizeof(T); - DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_core->os_page_size); - if (array->commit >= aligned_commit) - return true; - bool result = DN_OS_MemCommit(array->data, aligned_commit, DN_OSMemPage_ReadWrite); - array->commit = aligned_commit; - return result; -} -#endif // !defined(DN_NO_VARRAY) - -#if !defined(DN_NO_SARRAY) -// NOTE: [$SARR] DN_SArray //////////////////////////////////////////////////////////////////////// -template DN_SArray DN_SArray_Init(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) -{ - DN_SArray result = {}; - if (!arena || !size) - return result; - result.data = DN_Arena_NewArray(arena, T, size, zero_mem); - if (result.data) - result.max = size; - return result; -} - -template DN_SArray DN_SArray_InitSlice(DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem) -{ - DN_USize max = DN_MAX(slice.size, size); - DN_SArray result = DN_SArray_Init(arena, max, DN_ZeroMem_No); - if (DN_SArray_IsValid(&result)) { - DN_SArray_AddArray(&result, slice.data, slice.size); - if (zero_mem == DN_ZeroMem_Yes) - DN_MEMSET(result.data + result.size, 0, (result.max - result.size) * sizeof(T)); - } - return result; -} - -template DN_SArray DN_SArray_InitCArray(DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem zero_mem) -{ - DN_SArray result = DN_SArray_InitSlice(arena, DN_Slice_Init(DN_CAST(T *)array, N), size, zero_mem); - return result; -} - -template DN_SArray DN_SArray_InitBuffer(T *buffer, DN_USize size) -{ - DN_SArray result = {}; - result.data = buffer; - result.max = size; - return result; -} - -template bool DN_SArray_IsValid(DN_SArray const *array) -{ - bool result = array && array->data && array->size <= array->max; - return result; -} - -template DN_Slice DN_SArray_Slice(DN_SArray const *array) -{ - DN_Slice result = {}; - if (array) - result = DN_Slice_Init(DN_CAST(T *)array->data, array->size); - return result; -} - -template T *DN_SArray_MakeArray(DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem) -{ - if (!DN_SArray_IsValid(array)) - return nullptr; - T *result = DN_CArray_MakeArray(array->data, &array->size, array->max, count, zero_mem); - return result; -} - -template T *DN_SArray_Make(DN_SArray *array, DN_ZeroMem zero_mem) -{ - T *result = DN_SArray_MakeArray(array, 1, zero_mem); - return result; -} - -template T *DN_SArray_AddArray(DN_SArray *array, T const *items, DN_USize count) -{ - T *result = DN_SArray_MakeArray(array, count, DN_ZeroMem_No); - if (result) - DN_MEMCPY(result, items, count * sizeof(T)); - return result; -} - -template T *DN_SArray_AddCArray(DN_SArray *array, T const (&items)[N]) -{ - T *result = DN_SArray_AddArray(array, items, N); - return result; -} - -template T *DN_SArray_Add(DN_SArray *array, T const &item) -{ - T *result = DN_SArray_AddArray(array, &item, 1); - return result; -} - -template T *DN_SArray_InsertArray(DN_SArray *array, DN_USize index, T const *items, DN_USize count) -{ - T *result = nullptr; - if (!DN_SArray_IsValid(array)) - return result; - result = DN_CArray_InsertArray(array->data, &array->size, array->max, index, items, count); - return result; -} - -template T *DN_SArray_InsertCArray(DN_SArray *array, DN_USize index, T const (&items)[N]) -{ - T *result = DN_SArray_InsertArray(array, index, items, N); - return result; -} - -template T *DN_SArray_Insert(DN_SArray *array, DN_USize index, T const &item) -{ - T *result = DN_SArray_InsertArray(array, index, &item, 1); - return result; -} - -template T DN_SArray_PopFront(DN_SArray *array, DN_USize count) -{ - T result = DN_CArray_PopFront(array->data, &array->size, count); - return result; -} - -template T DN_SArray_PopBack(DN_SArray *array, DN_USize count) -{ - T result = DN_CArray_PopBack(array->data, &array->size, count); - return result; -} - -template DN_ArrayEraseResult DN_SArray_EraseRange(DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = {}; - if (!DN_SArray_IsValid(array) || array->size == 0 || count == 0) - return result; - result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); - return result; -} - -template void DN_SArray_Clear(DN_SArray *array) -{ - if (array) - array->size = 0; -} -#endif // !defined(DN_NO_SARRAY) - -#if !defined(DN_NO_FARRAY) -// NOTE: [$FARR] DN_FArray //////////////////////////////////////////////////////////////////////// -template DN_FArray DN_FArray_Init(T const *array, DN_USize count) -{ - DN_FArray result = {}; - bool added = DN_FArray_AddArray(&result, array, count); - DN_ASSERT(added); - return result; -} - -template DN_FArray DN_FArray_InitSlice(DN_Slice slice) -{ - DN_FArray result = DN_FArray_Init(slice.data, slice.size); - return result; -} - -template DN_FArray DN_FArray_InitCArray(T const (&items)[K]) -{ - DN_FArray result = DN_FArray_Init(items, K); - return result; -} - -template bool DN_FArray_IsValid(DN_FArray const *array) -{ - bool result = array && array->size <= DN_ARRAY_UCOUNT(array->data); - return result; -} - -template DN_Slice DN_FArray_Slice(DN_FArray const *array) -{ - DN_Slice result = {}; - if (array) - result = DN_Slice_Init(DN_CAST(T *)array->data, array->size); - return result; -} - -template T *DN_FArray_AddArray(DN_FArray *array, T const *items, DN_USize count) -{ - T *result = DN_FArray_MakeArray(array, count, DN_ZeroMem_No); - if (result) - DN_MEMCPY(result, items, count * sizeof(T)); - return result; -} - -template T *DN_FArray_AddCArray(DN_FArray *array, T const (&items)[K]) -{ - T *result = DN_FArray_MakeArray(array, K, DN_ZeroMem_No); - if (result) - DN_MEMCPY(result, items, K * sizeof(T)); - return result; -} - -template T *DN_FArray_Add(DN_FArray *array, T const &item) -{ - T *result = DN_FArray_AddArray(array, &item, 1); - return result; -} - -template T *DN_FArray_MakeArray(DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem) -{ - if (!DN_FArray_IsValid(array)) - return nullptr; - T *result = DN_CArray_MakeArray(array->data, &array->size, N, count, zero_mem); - return result; -} - -template T *DN_FArray_Make(DN_FArray *array, DN_ZeroMem zero_mem) -{ - T *result = DN_FArray_MakeArray(array, 1, zero_mem); - return result; -} - -template T *DN_FArray_InsertArray(DN_FArray *array, DN_USize index, T const *items, DN_USize count) -{ - T *result = nullptr; - if (!DN_FArray_IsValid(array)) - return result; - result = DN_CArray_InsertArray(array->data, &array->size, N, index, items, count); - return result; -} - -template T *DN_FArray_InsertCArray(DN_FArray *array, DN_USize index, T const (&items)[K]) -{ - T *result = DN_FArray_InsertArray(array, index, items, K); - return result; -} - -template T *DN_FArray_Insert(DN_FArray *array, DN_USize index, T const &item) -{ - T *result = DN_FArray_InsertArray(array, index, &item, 1); - return result; -} - -template T DN_FArray_PopFront(DN_FArray *array, DN_USize count) -{ - T result = DN_CArray_PopFront(array->data, &array->size, count); - return result; -} - -template T DN_FArray_PopBack(DN_FArray *array, DN_USize count) -{ - T result = DN_CArray_PopBack(array->data, &array->size, count); - return result; -} - -template DN_ArrayFindResult DN_FArray_Find(DN_FArray *array, T const &find) -{ - DN_ArrayFindResult result = DN_CArray_Find(array->data, array->size, find); - return result; -} - -template DN_ArrayEraseResult DN_FArray_EraseRange(DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase) -{ - DN_ArrayEraseResult result = {}; - if (!DN_FArray_IsValid(array) || array->size == 0 || count == 0) - return result; - result = DN_CArray_EraseRange(array->data, &array->size, begin_index, count, erase); - return result; -} - -template void DN_FArray_Clear(DN_FArray *array) -{ - if (array) - array->size = 0; -} -#endif // !defined(DN_NO_FARRAY) - -#if !defined(DN_NO_SLICE) -template DN_Slice DN_Slice_Init(T* const data, DN_USize size) -{ - DN_Slice result = {}; - if (data) { - result.data = data; - result.size = size; - } - return result; -} - -template DN_Slice DN_Slice_InitCArray(DN_Arena *arena, T const (&array)[N]) -{ - DN_Slice result = DN_Slice_Alloc(arena, N, DN_ZeroMem_No); - if (result.data) - DN_MEMCPY(result.data, array, sizeof(T) * N); - return result; -} - -template DN_Slice DN_Slice_CopyPtr(DN_Arena *arena, T *const data, DN_USize size) -{ - T *copy = DN_Arena_NewArrayCopy(arena, T, data, size); - DN_Slice result = DN_Slice_Init(copy, copy ? size : 0); - return result; -} - -template DN_Slice DN_Slice_Copy(DN_Arena *arena, DN_Slice slice) -{ - DN_Slice result = DN_Slice_CopyPtr(arena, slice.data, slice.size); - return result; -} - -template DN_Slice DN_Slice_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) -{ - DN_Slice result = {}; - if (!arena || size == 0) - return result; - result.data = DN_Arena_NewArray(arena, T, size, zero_mem); - if (result.data) - result.size = size; - return result; -} - -#endif // !defined(DN_NO_SLICE) - -#if !defined(DN_NO_DSMAP) -// NOTE: [$DMAP] DN_DSMap ///////////////////////////////////////////////////////////////////////// -DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; -DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; - -template DN_DSMap DN_DSMap_Init(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) -{ - DN_DSMap result = {}; - if (!DN_CHECKF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size)) - return result; - if (size <= 0) - return result; - if (!DN_CHECK(arena)) - return result; - result.arena = arena; - result.pool = DN_Pool_Init(arena, DN_POOL_DEFAULT_ALIGN); - result.hash_to_slot = DN_Arena_NewArray(result.arena, DN_U32, size, DN_ZeroMem_Yes); - result.slots = DN_Arena_NewArray(result.arena, DN_DSMapSlot, size, DN_ZeroMem_Yes); - result.occupied = 1; // For sentinel - result.size = size; - result.initial_size = size; - result.flags = flags; - DN_ASSERTF(result.hash_to_slot && result.slots, "We pre-allocated a block of memory sufficient in size for the 2 arrays. Maybe the pointers needed extra space because of natural alignment?"); - return result; -} - -template -void DN_DSMap_Deinit(DN_DSMap *map, DN_ZeroMem zero_mem) -{ - if (!map) - return; - // TODO(doyle): Use zero_mem - (void)zero_mem; - DN_Arena_Deinit(map->arena); - *map = {}; -} - -template -bool DN_DSMap_IsValid(DN_DSMap const *map) -{ - bool result = map && - map->arena && - map->hash_to_slot && // Hash to slot mapping array must be allocated - map->slots && // Slots array must be allocated - (map->size & (map->size - 1)) == 0 && // Must be power of two size - map->occupied >= 1; // DN_DS_MAP_SENTINEL_SLOT takes up one slot - return result; -} - -template -DN_U32 DN_DSMap_Hash(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_U32 result = 0; - if (!map) - return result; - - if (key.type == DN_DSMapKeyType_U64NoHash) { - result = DN_CAST(DN_U32)key.u64; - return result; - } - - if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { - result = key.hash; - return result; - } - - DN_U32 seed = map->hash_seed ? map->hash_seed : DN_DS_MAP_DEFAULT_HASH_SEED; - if (map->hash_function) { - map->hash_function(key, seed); - } else { - // NOTE: Courtesy of Demetri Spanos (which this hash table was inspired - // from), the following is a hashing function snippet provided for - // reliable, quick and simple quality hashing functions for hash table - // use. - // Source: https://github.com/demetri/scribbles/blob/c475464756c104c91bab83ed4e14badefef12ab5/hashing/ub_aware_hash_functions.c - - char const *key_ptr = nullptr; - DN_U32 len = 0; - DN_U32 h = seed; - switch (key.type) { - case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ - case DN_DSMapKeyType_U64NoHash: DN_INVALID_CODE_PATH; /*FALLTHRU*/ - case DN_DSMapKeyType_Invalid: break; - - case DN_DSMapKeyType_Buffer: - key_ptr = DN_CAST(char const *)key.buffer_data; - len = key.buffer_size; - break; - - case DN_DSMapKeyType_U64: - key_ptr = DN_CAST(char const *)&key.u64; - len = sizeof(key.u64); - break; - } - - // Murmur3 32-bit without UB unaligned accesses - // DN_U32 mur3_32_no_UB(const void *key, int len, DN_U32 h) - - // main body, work on 32-bit blocks at a time - for (DN_U32 i = 0; i < len / 4; i++) { - DN_U32 k; - memcpy(&k, &key_ptr[i * 4], sizeof(k)); - - k *= 0xcc9e2d51; - k = ((k << 15) | (k >> 17)) * 0x1b873593; - h = (((h ^ k) << 13) | ((h ^ k) >> 19)) * 5 + 0xe6546b64; - } - - // load/mix up to 3 remaining tail bytes into a tail block - DN_U32 t = 0; - uint8_t *tail = ((uint8_t *)key_ptr) + 4 * (len / 4); - switch (len & 3) { - case 3: t ^= tail[2] << 16; - case 2: t ^= tail[1] << 8; - case 1: { - t ^= tail[0] << 0; - h ^= ((0xcc9e2d51 * t << 15) | (0xcc9e2d51 * t >> 17)) * 0x1b873593; - } - } - - // finalization mix, including key length - h = ((h ^ len) ^ ((h ^ len) >> 16)) * 0x85ebca6b; - h = (h ^ (h >> 13)) * 0xc2b2ae35; - result = h ^ (h >> 16); - } - return result; -} - -template -DN_U32 DN_DSMap_HashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_ASSERT(key.type != DN_DSMapKeyType_Invalid); - DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; - if (!DN_DSMap_IsValid(map)) - return result; - - result = key.hash & (map->size - 1); - for (;;) { - if (result == DN_DS_MAP_SENTINEL_SLOT) // Sentinel is reserved - result++; - - if (map->hash_to_slot[result] == DN_DS_MAP_SENTINEL_SLOT) // Slot is vacant, can use - return result; - - DN_DSMapSlot *slot = map->slots + map->hash_to_slot[result]; - if (slot->key.type == DN_DSMapKeyType_Invalid || (slot->key.hash == key.hash && slot->key == key)) - return result; - - result = (result + 1) & (map->size - 1); - } -} - -template -DN_DSMapResult DN_DSMap_Find(DN_DSMap const *map, DN_DSMapKey key) -{ - DN_DSMapResult result = {}; - if (DN_DSMap_IsValid(map)) { - DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); - if (index != DN_DS_MAP_SENTINEL_SLOT && map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { - result.slot = map->slots; // NOTE: Set to sentinel value - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.found = true; - } - result.value = &result.slot->value; - } - return result; -} - -template -DN_DSMapResult DN_DSMap_Make(DN_DSMap *map, DN_DSMapKey key) -{ - DN_DSMapResult result = {}; - if (!DN_DSMap_IsValid(map)) - return result; - - DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); - if (map->hash_to_slot[index] == DN_DS_MAP_SENTINEL_SLOT) { - // NOTE: Create the slot - if (index != DN_DS_MAP_SENTINEL_SLOT) - map->hash_to_slot[index] = map->occupied++; - - // NOTE: Check if resize is required - bool map_is_75pct_full = (map->occupied * 4) > (map->size * 3); - if (map_is_75pct_full) { - if (!DN_DSMap_Resize(map, map->size * 2)) - return result; - result = DN_DSMap_Make(map, key); - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.slot->key = key; // NOTE: Assign key to new slot - if ((key.type == DN_DSMapKeyType_Buffer || - key.type == DN_DSMapKeyType_BufferAsU64NoHash) && - !key.no_copy_buffer) { - result.slot->key.buffer_data = DN_Pool_NewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); - } - } - } else { - result.slot = map->slots + map->hash_to_slot[index]; - result.found = true; - } - - result.value = &result.slot->value; - DN_ASSERT(result.slot->key.type != DN_DSMapKeyType_Invalid); - return result; -} - -template -DN_DSMapResult DN_DSMap_Set(DN_DSMap *map, DN_DSMapKey key, T const &value) -{ - DN_DSMapResult result = {}; - if (!DN_DSMap_IsValid(map)) - return result; - - result = DN_DSMap_Make(map, key); - result.slot->value = value; - return result; -} - -template -DN_DSMapResult DN_DSMap_FindKeyU64(DN_DSMap const *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); - DN_DSMapResult result = DN_DSMap_Find(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMap_MakeKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); - DN_DSMapResult result = DN_DSMap_Make(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMap_SetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); - DN_DSMapResult result = DN_DSMap_Set(map, map_key, value); - return result; -} - -template -DN_DSMapResult DN_DSMap_FindKeyStr8(DN_DSMap const *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); - DN_DSMapResult result = DN_DSMap_Find(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMap_MakeKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); - DN_DSMapResult result = DN_DSMap_Make(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMap_SetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); - DN_DSMapResult result = DN_DSMap_Set(map, map_key); - return result; -} - -template -bool DN_DSMap_Resize(DN_DSMap *map, DN_U32 size) -{ - if (!DN_DSMap_IsValid(map) || size < map->occupied || size < map->initial_size) - return false; - - DN_Arena *prev_arena = map->arena; - DN_Arena new_arena = {}; - new_arena.flags = prev_arena->flags; - new_arena.label = prev_arena->label; - new_arena.prev = prev_arena->prev; - new_arena.next = prev_arena->next; - - DN_DSMap new_map = DN_DSMap_Init(&new_arena, size, map->flags); - if (!DN_DSMap_IsValid(&new_map)) - return false; - - new_map.initial_size = map->initial_size; - for (DN_U32 old_index = 1 /*Sentinel*/; old_index < map->occupied; old_index++) { - DN_DSMapSlot *old_slot = map->slots + old_index; - DN_DSMapKey old_key = old_slot->key; - if (old_key.type == DN_DSMapKeyType_Invalid) - continue; - DN_DSMap_Set(&new_map, old_key, old_slot->value); - } - - if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) - DN_DSMap_Deinit(map, DN_ZeroMem_No); - *map = new_map; // Update the map inplace - map->arena = prev_arena; // Restore the previous arena pointer, it's been de-init-ed - *map->arena = new_arena; // Re-init the old arena with the new data - map->pool.arena = map->arena; - return true; -} - -template -bool DN_DSMap_Erase(DN_DSMap *map, DN_DSMapKey key) -{ - if (!DN_DSMap_IsValid(map)) - return false; - - DN_U32 index = DN_DSMap_HashToSlotIndex(map, key); - if (index == 0) - return true; - - DN_U32 slot_index = map->hash_to_slot[index]; - if (slot_index == DN_DS_MAP_SENTINEL_SLOT) - return false; - - // NOTE: Mark the slot as unoccupied - map->hash_to_slot[index] = DN_DS_MAP_SENTINEL_SLOT; - - DN_DSMapSlot *slot = map->slots + slot_index; - if (!slot->key.no_copy_buffer) - DN_Pool_Dealloc(&map->pool, DN_CAST(void *)slot->key.buffer_data); - *slot = {}; // TODO: Optional? - - if (map->occupied > 1 /*Sentinel*/) { - // NOTE: Repair the hash chain, e.g. rehash all the items after the removed - // element and reposition them if necessary. - for (DN_U32 probe_index = index;;) { - probe_index = (probe_index + 1) & (map->size - 1); - if (map->hash_to_slot[probe_index] == DN_DS_MAP_SENTINEL_SLOT) - break; - - DN_DSMapSlot *probe = map->slots + map->hash_to_slot[probe_index]; - DN_U32 new_index = probe->key.hash & (map->size - 1); - if (index <= probe_index) { - if (index < new_index && new_index <= probe_index) - continue; - } else { - if (index < new_index || new_index <= probe_index) - continue; - } - - map->hash_to_slot[index] = map->hash_to_slot[probe_index]; - map->hash_to_slot[probe_index] = DN_DS_MAP_SENTINEL_SLOT; - index = probe_index; - } - - // NOTE: We have erased a slot from the hash table, this leaves a gap - // in our contiguous array. After repairing the chain, the hash mapping - // is correct. - // We will now fill in the vacant spot that we erased using the last - // element in the slot list. - if (map->occupied >= 3 /*Ignoring sentinel, at least 2 other elements to unstable erase*/) { - DN_U32 last_index = map->occupied - 1; - if (last_index != slot_index) { - // NOTE: Copy in last slot to the erase slot - DN_DSMapSlot *last_slot = map->slots + last_index; - map->slots[slot_index] = *last_slot; - - // NOTE: Update the hash-to-slot mapping for the value that was copied in - DN_U32 hash_to_slot_index = DN_DSMap_HashToSlotIndex(map, last_slot->key); - map->hash_to_slot[hash_to_slot_index] = slot_index; - *last_slot = {}; // TODO: Optional? - } - } - } - - map->occupied--; - bool map_is_below_25pct_full = (map->occupied * 4) < (map->size * 1); - if (map_is_below_25pct_full && (map->size / 2) >= map->initial_size) - DN_DSMap_Resize(map, map->size / 2); - - return true; -} - -template bool DN_DSMap_EraseKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyU64(map, key); - bool result = DN_DSMap_Erase(map, map_key); - return result; -} - -template bool DN_DSMap_EraseKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMap_KeyStr8(map, key); - bool result = DN_DSMap_Erase(map, map_key); - return result; -} - -template -DN_DSMapKey DN_DSMap_KeyBuffer(DN_DSMap const *map, void const *data, DN_USize size) -{ - DN_ASSERT(size > 0 && size <= UINT32_MAX); - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_Buffer; - result.buffer_data = data; - result.buffer_size = DN_CAST(DN_U32) size; - result.hash = DN_DSMap_Hash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_U32 size) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_BufferAsU64NoHash; - result.buffer_data = data; - result.buffer_size = DN_CAST(DN_U32) size; - DN_ASSERT(size >= sizeof(result.hash)); - DN_MEMCPY(&result.hash, data, sizeof(result.hash)); - return result; -} - -template DN_DSMapKey DN_DSMap_KeyU64(DN_DSMap const *map, DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64; - result.u64 = u64; - result.hash = DN_DSMap_Hash(map, result); - return result; -} - -template DN_DSMapKey DN_DSMap_KeyStr8(DN_DSMap const *map, DN_Str8 string) -{ - DN_DSMapKey result = DN_DSMap_KeyBuffer(map, string.data, string.size); - return result; -} -#endif // !defined(DN_NO_DSMAP) - -#if !defined(DN_NO_LIST) -// NOTE: [$LIST] DN_List ////////////////////////////////////////////////////////////////////////// -template DN_List DN_List_Init(DN_USize chunk_size) -{ - DN_List result = {}; - result.chunk_size = chunk_size; - return result; -} - -template DN_List DN_List_InitCArray(DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]) -{ - DN_List result = DN_List_Init(arena, chunk_size); - DN_FOR_UINDEX (index, N) - DN_List_Add(&result, array[index]); - return result; -} - -template DN_List DN_List_InitSliceCopy(DN_Arena *arena, DN_USize chunk_size, DN_Slice slice) -{ - DN_List result = DN_List_Init(arena, chunk_size); - DN_FOR_UINDEX (index, slice.size) - DN_List_Add(&result, slice.data[index]); - return result; -} - -template DN_API bool DN_List_AttachTail_(DN_List *list, DN_ListChunk *tail) -{ - if (!tail) - return false; - - if (list->tail) { - list->tail->next = tail; - tail->prev = list->tail; - } - - list->tail = tail; - - if (!list->head) - list->head = list->tail; - return true; -} - -template DN_API DN_ListChunk *DN_List_AllocArena_(DN_List *list, DN_Arena *arena, DN_USize count) -{ - auto *result = DN_Arena_New(arena, DN_ListChunk, DN_ZeroMem_Yes); - DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(arena); - if (!result) - return nullptr; - - DN_USize items = DN_MAX(list->chunk_size, count); - result->data = DN_Arena_NewArray(arena, T, items, DN_ZeroMem_Yes); - result->size = items; - if (!result->data) { - DN_Arena_TempMemEnd(tmem); - result = nullptr; - } - - DN_List_AttachTail_(list, result); - return result; -} - -template DN_API DN_ListChunk *DN_List_AllocPool_(DN_List *list, DN_Pool *pool, DN_USize count) -{ - auto *result = DN_Pool_New(pool, DN_ListChunk); - if (!result) - return nullptr; - - DN_USize items = DN_MAX(list->chunk_size, count); - result->data = DN_Pool_NewArray(pool, T, items); - result->size = items; - if (!result->data) { - DN_Pool_Dealloc(result); - result = nullptr; - } - - DN_List_AttachTail_(list, result); - return result; -} - -template DN_API T *DN_List_MakeArena(DN_List *list, DN_Arena *arena, DN_USize count) -{ - if (list->chunk_size == 0) - list->chunk_size = 128; - - if (!list->tail || (list->tail->count + count) > list->tail->size) { - if (!DN_List_AllocArena_(list, arena, count)) - return nullptr; - } - - T *result = list->tail->data + list->tail->count; - list->tail->count += count; - list->count += count; - return result; -} - -template DN_API T *DN_List_MakePool(DN_List *list, DN_Pool *pool, DN_USize count) -{ - if (list->chunk_size == 0) - list->chunk_size = 128; - - if (!list->tail || (list->tail->count + count) > list->tail->size) { - if (!DN_List_AllocPool_(list, pool, count)) - return nullptr; - } - - T *result = list->tail->data + list->tail->count; - list->tail->count += count; - list->count += count; - return result; -} - -template DN_API T *DN_List_AddArena(DN_List *list, DN_Arena *arena, T const &value) -{ - T *result = DN_List_MakeArena(list, arena, 1); - *result = value; - return result; -} - -template DN_API T *DN_List_AddPool(DN_List *list, DN_Pool *pool, T const &value) -{ - T *result = DN_List_MakePool(list, pool, 1); - *result = value; - return result; -} - -template DN_API bool DN_List_AddCArray(DN_List *list, T const (&array)[N]) -{ - if (!list) - return false; - for (T const &item : array) { - if (!DN_List_Add(list, item)) - return false; - } - return true; -} - -template DN_API void DN_List_AddListArena(DN_List *list, DN_Arena *arena, DN_List other) -{ - if (!list) - return; - // TODO(doyle): Copy chunk by chunk - for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/); ) - DN_List_AddArena(list, arena, *it.data); -} - -template DN_API void DN_List_AddListPool(DN_List *list, DN_Pool *pool, DN_List other) -{ - if (!list) - return; - // TODO(doyle): Copy chunk by chunk - for (DN_ListIterator it = {}; DN_List_Iterate(&other, &it, 0 /*start_index*/); ) - DN_List_AddPool(list, pool, *it.data); -} - -template void DN_List_Clear(DN_List *list) -{ - if (!list) - return; - list->head = list->tail = nullptr; - list->count = 0; -} - -template DN_API bool DN_List_Iterate(DN_List *list, DN_ListIterator *it, DN_USize start_index) -{ - bool result = false; - if (!list || !it || list->chunk_size <= 0) - return result; - - if (it->init) { - it->index++; - } else { - *it = {}; - if (start_index == 0) { - it->chunk = list->head; - } else { - DN_List_At(list, start_index, &it->chunk); - if (list->chunk_size > 0) - it->chunk_data_index = start_index % list->chunk_size; - } - - it->init = true; - } - - if (it->chunk) { - if (it->chunk_data_index >= it->chunk->count) { - it->chunk = it->chunk->next; - it->chunk_data_index = 0; - } - - if (it->chunk) { - it->data = it->chunk->data + it->chunk_data_index++; - result = true; - } - } - - if (!it->chunk) - DN_ASSERT(result == false); - return result; -} - -template DN_API T *DN_List_At(DN_List *list, DN_USize index, DN_ListChunk **at_chunk) -{ - if (!list || index >= list->count || !list->head) - return nullptr; - - // NOTE: Scan forwards to the chunk we need. We don't have random access to chunks - DN_ListChunk **chunk = &list->head; - DN_USize running_index = index; - for (; (*chunk) && running_index >= (*chunk)->size; chunk = &((*chunk)->next)) - running_index -= (*chunk)->size; - - T *result = nullptr; - if (*chunk) - result = (*chunk)->data + running_index; - - if (result && at_chunk) - *at_chunk = *chunk; - - return result; -} - -template DN_Slice DN_List_ToSliceCopy(DN_List const *list, DN_Arena *arena) -{ - // TODO(doyle): Chunk memcopies is much faster - DN_Slice result = DN_Slice_Alloc(arena, list->count, DN_ZeroMem_No); - if (result.size) { - DN_USize slice_index = 0; - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6011) // Dereferencing NULL pointer 'x' - for (DN_ListIterator it = {}; DN_List_Iterate(DN_CAST(DN_List *)list, &it, 0);) - result.data[slice_index++] = *it.data; - DN_MSVC_WARNING_POP - DN_ASSERT(slice_index == result.size); - } - return result; -} -#endif // !defined(DN_NO_LIST) - -// NOTE: [$SLIC] DN_Slice ///////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_Slice_Str8Render(DN_Arena *arena, DN_Slice array, DN_Str8 separator) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - DN_USize total_size = 0; - for (DN_USize index = 0; index < array.size; index++) { - if (index) - total_size += separator.size; - DN_Str8 item = array.data[index]; - total_size += item.size; - } - - result = DN_Str8_Alloc(arena, total_size, DN_ZeroMem_No); - if (result.data) { - DN_USize write_index = 0; - for (DN_USize index = 0; index < array.size; index++) { - if (index) { - DN_MEMCPY(result.data + write_index, separator.data, separator.size); - write_index += separator.size; - } - DN_Str8 item = array.data[index]; - DN_MEMCPY(result.data + write_index, item.data, item.size); - write_index += item.size; - } - } - - return result; -} - -DN_API DN_Str8 DN_Slice_Str8RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) -{ - DN_Str8 result = DN_Slice_Str8Render(arena, array, DN_STR8(" ")); - return result; -} - -DN_API DN_Str16 DN_Slice_Str16Render(DN_Arena *arena, DN_Slice array, DN_Str16 separator) -{ - DN_Str16 result = {}; - if (!arena) - return result; - - DN_USize total_size = 0; - for (DN_USize index = 0; index < array.size; index++) { - if (index) - total_size += separator.size; - DN_Str16 item = array.data[index]; - total_size += item.size; - } - - result = {DN_Arena_NewArray(arena, wchar_t, total_size + 1, DN_ZeroMem_No), total_size}; - if (result.data) { - DN_USize write_index = 0; - for (DN_USize index = 0; index < array.size; index++) { - if (index) { - DN_MEMCPY(result.data + write_index, separator.data, separator.size * sizeof(result.data[0])); - write_index += separator.size; - } - DN_Str16 item = array.data[index]; - DN_MEMCPY(result.data + write_index, item.data, item.size * sizeof(result.data[0])); - write_index += item.size; - } - } - - result.data[total_size] = 0; - return result; -} - -DN_API DN_Str16 DN_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array) -{ - DN_Str16 result = DN_Slice_Str16Render(arena, array, DN_STR16(L" ")); - return result; -} - -#if !defined(DN_NO_DSMAP) -// NOTE: [$DMAP] DN_DSMap ///////////////////////////////////////////////////////////////////////// -DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash(DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64NoHash; - result.u64 = u64; - result.hash = DN_CAST(DN_U32) u64; - return result; -} - -DN_API bool DN_DSMap_KeyEquals(DN_DSMapKey lhs, DN_DSMapKey rhs) -{ - bool result = false; - if (lhs.type == rhs.type && lhs.hash == rhs.hash) { - switch (lhs.type) { - case DN_DSMapKeyType_Invalid: result = true; break; - case DN_DSMapKeyType_U64NoHash: result = true; break; - case DN_DSMapKeyType_U64: result = lhs.u64 == rhs.u64; break; - - case DN_DSMapKeyType_BufferAsU64NoHash: /*FALLTHRU*/ - case DN_DSMapKeyType_Buffer: { - if (lhs.buffer_size == rhs.buffer_size) - result = DN_MEMCMP(lhs.buffer_data, rhs.buffer_data, lhs.buffer_size) == 0; - } break; - } - } - return result; -} - -DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) -{ - bool result = DN_DSMap_KeyEquals(lhs, rhs); - return result; -} -#endif // !defined(DN_NO_DSMAP) diff --git a/dqn_containers.h b/dqn_containers.h deleted file mode 100644 index 967f194..0000000 --- a/dqn_containers.h +++ /dev/null @@ -1,347 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ _____|$$ __$$\ $$ __$$\ -// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ / $$ | $$ | $$$$\ $$ |$$ | $$ | $$ |$$ / \__| -// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$$$$ | $$ | $$ $$\$$ |$$$$$\ $$$$$$$ |\$$$$$$\ -// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __$$ | $$ | $$ \$$$$ |$$ __| $$ __$$< \____$$\ -// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | $$ |$$\ $$ | -// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |$$$$$$$$\ $$ | $$ |\$$$$$$ | -// \______/ \______/ \__| \__| \__| \__| \__|\______|\__| \__|\________|\__| \__| \______/ -// -// dqn_containers.h -- Data structures for storing data (e.g. arrays and hash tables) -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$CARR] DN_CArray -- -- Basic operations on C arrays for VArray/SArray/FArray to reuse -// [$VARR] DN_VArray -- DN_VARRAY -- Array backed by virtual memory arena -// [$SARR] DN_SArray -- DN_SARRAY -- Array that are allocated but cannot resize -// [$FARR] DN_FArray -- DN_FARRAY -- Fixed-size arrays -// [$SLIC] DN_Slice -- -- Pointe and length container of data -// [$DMAP] DN_DSMap -- DN_DSMAP -- Hashtable, 70% max load, PoT size, linear probe, chain repair -// [$LIST] DN_List -- DN_LIST -- Chunked linked lists, append only -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$CARR] DN_CArray //////////////////////////////////////////////////////////////////////// -enum DN_ArrayErase -{ - DN_ArrayErase_Unstable, - DN_ArrayErase_Stable, -}; - -struct DN_ArrayEraseResult -{ - // The next index your for-index should be set to such that you can continue - // to iterate the remainder of the array, e.g: - // - // for (DN_USize index = 0; index < array.size; index++) { - // if (erase) - // index = DN_FArray_EraseRange(&array, index, -3, DN_ArrayErase_Unstable); - // } - DN_USize it_index; - DN_USize items_erased; // The number of items erased -}; - -template struct DN_ArrayFindResult -{ - T *data; // Pointer to the value if a match is found, null pointer otherwise - DN_USize index; // Index to the value if a match is found, 0 otherwise -}; - -#if !defined(DN_NO_VARRAY) -// NOTE: [$VARR] DN_VArray //////////////////////////////////////////////////////////////////////// -// TODO(doyle): Add an API for shrinking the array by decomitting pages back to -// the OS. -template struct DN_VArray -{ - T *data; // Pointer to the start of the array items in the block of memory - DN_USize size; // Number of items currently in the array - DN_USize max; // Maximum number of items this array can store - DN_USize commit; // Bytes committed - - T *begin() { return data; } - T *end () { return data + size; } - T const *begin() const { return data; } - T const *end () const { return data + size; } -}; -#endif // !defined(DN_NO_VARRAY) - -#if !defined(DN_NO_SARRAY) -// NOTE: [$SARR] DN_SArray //////////////////////////////////////////////////////////////////////// -template struct DN_SArray -{ - T *data; // Pointer to the start of the array items in the block of memory - DN_USize size; // Number of items currently in the array - DN_USize max; // Maximum number of items this array can store - - T *begin() { return data; } - T *end () { return data + size; } - T const *begin() const { return data; } - T const *end () const { return data + size; } -}; -#endif // !defined(DN_NO_SARRAY) - -#if !defined(DN_NO_FARRAY) -// NOTE: [$FARR] DN_FArray //////////////////////////////////////////////////////////////////////// -template struct DN_FArray -{ - T data[N]; // Pointer to the start of the array items in the block of memory - DN_USize size; // Number of items currently in the array - - T *begin() { return data; } - T *end () { return data + size; } - T const *begin() const { return data; } - T const *end () const { return data + size; } -}; - -template using DN_FArray8 = DN_FArray; -template using DN_FArray16 = DN_FArray; -template using DN_FArray32 = DN_FArray; -template using DN_FArray64 = DN_FArray; -#endif // !defined(DN_NO_FARRAY) - -#if !defined(DN_NO_DSMAP) -// NOTE: [$DMAP] DN_DSMap ///////////////////////////////////////////////////////////////////////// -enum DN_DSMapKeyType -{ - // Key | Key Hash | Map Index - DN_DSMapKeyType_Invalid, - DN_DSMapKeyType_U64, // U64 | Hash(U64) | Hash(U64) % map_size - DN_DSMapKeyType_U64NoHash, // U64 | U64 | U64 % map_size - DN_DSMapKeyType_Buffer, // Buffer | Hash(buffer) | Hash(buffer) % map_size - DN_DSMapKeyType_BufferAsU64NoHash, // Buffer | U64(buffer[0:4]) | U64(buffer[0:4]) % map_size -}; - -struct DN_DSMapKey -{ - DN_DSMapKeyType type; - DN_U32 hash; // Hash to lookup in the map. If it equals, we check that the original key payload matches - void const *buffer_data; - DN_U32 buffer_size; - DN_U64 u64; - bool no_copy_buffer; -}; - -template -struct DN_DSMapSlot -{ - DN_DSMapKey key; // Hash table lookup key - T value; // Hash table value -}; - -typedef DN_U32 DN_DSMapFlags; -enum DN_DSMapFlags_ -{ - DN_DSMapFlags_Nil = 0, - DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, -}; - -using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); -template struct DN_DSMap -{ - DN_U32 *hash_to_slot; // Mapping from hash to a index in the slots array - DN_DSMapSlot *slots; // Values of the array stored contiguously, non-sorted order - DN_U32 size; // Total capacity of the map and is a power of two - DN_U32 occupied; // Number of slots used in the hash table - DN_Arena *arena; // Backing arena for the hash table - DN_Pool pool; // Allocator for keys that are variable-sized buffers - DN_U32 initial_size; // Initial map size, map cannot shrink on erase below this size - DN_DSMapHashFunction *hash_function; // Custom hashing function to use if field is set - DN_U32 hash_seed; // Seed for the hashing function, when 0, DN_DS_MAP_DEFAULT_HASH_SEED is used - DN_DSMapFlags flags; -}; - -template struct DN_DSMapResult -{ - bool found; - DN_DSMapSlot *slot; - T *value; -}; -#endif // !defined(DN_NO_DSMAP) - -#if !defined(DN_NO_LIST) -// NOTE: [$LIST] DN_List ////////////////////////////////////////////////////////////////////////// -template struct DN_ListChunk -{ - T *data; - DN_USize size; - DN_USize count; - DN_ListChunk *next; - DN_ListChunk *prev; -}; - -template struct DN_ListIterator -{ - DN_B32 init; // True if DN_List_Iterate has been called at-least once on this iterator - DN_ListChunk *chunk; // The chunk that the iterator is reading from - DN_USize chunk_data_index; // The index in the chunk the iterator is referencing - DN_USize index; // The index of the item in the list as if it was flat array - T *data; // Pointer to the data the iterator is referencing. Nullptr if invalid. -}; - -template struct DN_List -{ - DN_USize count; // Cumulative count of all items made across all list chunks - DN_USize chunk_size; // When new ListChunk's are required, the minimum 'data' entries to allocate for that node. - DN_ListChunk *head; - DN_ListChunk *tail; -}; -#endif // !defined(DN_NO_LIST) - -template DN_ArrayEraseResult DN_CArray_EraseRange (T *data, DN_USize *size, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -template T * DN_CArray_MakeArray (T *data, DN_USize *size, DN_USize max, DN_USize count, DN_ZeroMem zero_mem); -template T * DN_CArray_InsertArray (T *data, DN_USize *size, DN_USize max, DN_USize index, T const *items, DN_USize count); -template T DN_CArray_PopFront (T *data, DN_USize *size, DN_USize count); -template T DN_CArray_PopBack (T *data, DN_USize *size, DN_USize count); -template DN_ArrayFindResult DN_CArray_Find (T *data, DN_USize size, T const &value); - -#if !defined(DN_NO_VARRAY) -template DN_VArray DN_VArray_InitByteSize (DN_USize byte_size); -template DN_VArray DN_VArray_Init (DN_USize max); -template DN_VArray DN_VArray_InitSlice (DN_Slice slice, DN_USize max); -template DN_VArray DN_VArray_InitCArray (T const (&items)[N], DN_USize max); -template void DN_VArray_Deinit (DN_VArray *array); -template bool DN_VArray_IsValid (DN_VArray const *array); -template DN_Slice DN_VArray_Slice (DN_VArray const *array); -template bool DN_VArray_Reserve (DN_VArray *array, DN_USize count); -template T * DN_VArray_AddArray (DN_VArray *array, T const *items, DN_USize count); -template T * DN_VArray_AddCArray (DN_VArray *array, T const (&items)[N]); -template T * DN_VArray_Add (DN_VArray *array, T const &item); -#define DN_VArray_AddArrayAssert(...) DN_HARD_ASSERT(DN_VArray_AddArray(__VA_ARGS__)) -#define DN_VArray_AddCArrayAssert(...) DN_HARD_ASSERT(DN_VArray_AddCArray(__VA_ARGS__)) -#define DN_VArray_AddAssert(...) DN_HARD_ASSERT(DN_VArray_Add(__VA_ARGS__)) -template T * DN_VArray_MakeArray (DN_VArray *array, DN_USize count, DN_ZeroMem zero_mem); -template T * DN_VArray_Make (DN_VArray *array, DN_ZeroMem zero_mem); -#define DN_VArray_MakeArrayAssert(...) DN_HARD_ASSERT(DN_VArray_MakeArray(__VA_ARGS__)) -#define DN_VArray_MakeAssert(...) DN_HARD_ASSERT(DN_VArray_Make(__VA_ARGS__)) -template T * DN_VArray_InsertArray (DN_VArray *array, DN_USize index, T const *items, DN_USize count); -template T * DN_VArray_InsertCArray (DN_VArray *array, DN_USize index, T const (&items)[N]); -template T * DN_VArray_Insert (DN_VArray *array, DN_USize index, T const &item); -#define DN_VArray_InsertArrayAssert(...) DN_HARD_ASSERT(DN_VArray_InsertArray(__VA_ARGS__)) -#define DN_VArray_InsertCArrayAssert(...) DN_HARD_ASSERT(DN_VArray_InsertCArray(__VA_ARGS__)) -#define DN_VArray_InsertAssert(...) DN_HARD_ASSERT(DN_VArray_Insert(__VA_ARGS__)) -template T DN_VArray_PopFront (DN_VArray *array, DN_USize count); -template T DN_VArray_PopBack (DN_VArray *array, DN_USize count); -template DN_ArrayEraseResult DN_VArray_EraseRange (DN_VArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -template void DN_VArray_Clear (DN_VArray *array, DN_ZeroMem zero_mem); -#endif // !defined(DN_NO_VARRAY) -// NOTE: [$SARR] DN_SArray //////////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_SARRAY) -template DN_SArray DN_SArray_Init (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); -template DN_SArray DN_SArray_InitSlice (DN_Arena *arena, DN_Slice slice, DN_USize size, DN_ZeroMem zero_mem); -template DN_SArray DN_SArray_InitCArray (DN_Arena *arena, T const (&array)[N], DN_USize size, DN_ZeroMem); -template DN_SArray DN_SArray_InitBuffer (T* buffer, DN_USize size); -template bool DN_SArray_IsValid (DN_SArray const *array); -template DN_Slice DN_SArray_Slice (DN_SArray const *array); -template T * DN_SArray_AddArray (DN_SArray *array, T const *items, DN_USize count); -template T * DN_SArray_AddCArray (DN_SArray *array, T const (&items)[N]); -template T * DN_SArray_Add (DN_SArray *array, T const &item); -#define DN_SArray_AddArrayAssert(...) DN_HARD_ASSERT(DN_SArray_AddArray(__VA_ARGS__)) -#define DN_SArray_AddCArrayAssert(...) DN_HARD_ASSERT(DN_SArray_AddCArray(__VA_ARGS__)) -#define DN_SArray_AddAssert(...) DN_HARD_ASSERT(DN_SArray_Add(__VA_ARGS__)) -template T * DN_SArray_MakeArray (DN_SArray *array, DN_USize count, DN_ZeroMem zero_mem); -template T * DN_SArray_Make (DN_SArray *array, DN_ZeroMem zero_mem); -#define DN_SArray_MakeArrayAssert(...) DN_HARD_ASSERT(DN_SArray_MakeArray(__VA_ARGS__)) -#define DN_SArray_MakeAssert(...) DN_HARD_ASSERT(DN_SArray_Make(__VA_ARGS__)) -template T * DN_SArray_InsertArray (DN_SArray *array, DN_USize index, T const *items, DN_USize count); -template T * DN_SArray_InsertCArray (DN_SArray *array, DN_USize index, T const (&items)[N]); -template T * DN_SArray_Insert (DN_SArray *array, DN_USize index, T const &item); -#define DN_SArray_InsertArrayAssert(...) DN_HARD_ASSERT(DN_SArray_InsertArray(__VA_ARGS__)) -#define DN_SArray_InsertCArrayAssert(...) DN_HARD_ASSERT(DN_SArray_InsertCArray(__VA_ARGS__)) -#define DN_SArray_InsertAssert(...) DN_HARD_ASSERT(DN_SArray_Insert(__VA_ARGS__)) -template T DN_SArray_PopFront (DN_SArray *array, DN_USize count); -template T DN_SArray_PopBack (DN_SArray *array, DN_USize count); -template DN_ArrayEraseResult DN_SArray_EraseRange (DN_SArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -template void DN_SArray_Clear (DN_SArray *array); -#endif // !defined(DN_NO_SARRAY) -#if !defined(DN_NO_FARRAY) -#define DN_FArray_ToSArray(array) DN_SArray_InitBuffer((array)->data, DN_ARRAY_UCOUNT((array)->data)) -template DN_FArray DN_FArray_Init (T const *array, DN_USize count); -#define DN_FArray_HasData(array) ((array).data && (array).size) -template DN_FArray DN_FArray_InitSlice (DN_Slice slice); -template DN_FArray DN_FArray_InitCArray (T const (&items)[K]); -template bool DN_FArray_IsValid (DN_FArray const *array); -template DN_USize DN_FArray_Max (DN_FArray const *) { return N; } -template DN_Slice DN_FArray_Slice (DN_FArray const *array); -template T * DN_FArray_AddArray (DN_FArray *array, T const *items, DN_USize count); -template T * DN_FArray_AddCArray (DN_FArray *array, T const (&items)[K]); -template T * DN_FArray_Add (DN_FArray *array, T const &item); -#define DN_FArray_AddArrayAssert(...) DN_HARD_ASSERT(DN_FArray_AddArray(__VA_ARGS__)) -#define DN_FArray_AddCArrayAssert(...) DN_HARD_ASSERT(DN_FArray_AddCArray(__VA_ARGS__)) -#define DN_FArray_AddAssert(...) DN_HARD_ASSERT(DN_FArray_Add(__VA_ARGS__)) -template T * DN_FArray_MakeArray (DN_FArray *array, DN_USize count, DN_ZeroMem zero_mem); -template T * DN_FArray_Make (DN_FArray *array, DN_ZeroMem zero_mem); -#define DN_FArray_MakeArrayAssert(...) DN_HARD_ASSERT(DN_FArray_MakeArray(__VA_ARGS__)) -#define DN_FArray_MakeAssert(...) DN_HARD_ASSERT(DN_FArray_Make(__VA_ARGS__)) -template T * DN_FArray_InsertArray (DN_FArray *array, T const &item, DN_USize index); -template T * DN_FArray_InsertCArray (DN_FArray *array, DN_USize index, T const (&items)[K]); -template T * DN_FArray_Insert (DN_FArray *array, DN_USize index, T const &item); -#define DN_FArray_InsertArrayAssert(...) DN_HARD_ASSERT(DN_FArray_InsertArray(__VA_ARGS__)) -#define DN_FArray_InsertAssert(...) DN_HARD_ASSERT(DN_FArray_Insert(__VA_ARGS__)) -template T DN_FArray_PopFront (DN_FArray *array, DN_USize count); -template T DN_FArray_PopBack (DN_FArray *array, DN_USize count); -template DN_ArrayFindResult DN_FArray_Find (DN_FArray *array, T const &find); -template DN_ArrayEraseResult DN_FArray_EraseRange (DN_FArray *array, DN_USize begin_index, DN_ISize count, DN_ArrayErase erase); -template void DN_FArray_Clear (DN_FArray *array); -#endif // !defined(DN_NO_FARRAY) -#if !defined(DN_NO_SLICE) -#define DN_TO_SLICE(val) DN_Slice_Init((val)->data, (val)->size) -template DN_Slice DN_Slice_Init (T* const data, DN_USize size); -template DN_Slice DN_Slice_InitCArray (DN_Arena *arena, T const (&array)[N]); -template DN_Slice DN_Slice_Copy (DN_Arena *arena, DN_Slice slice); -template DN_Slice DN_Slice_CopyPtr (DN_Arena *arena, T* const data, DN_USize size); -template DN_Slice DN_Slice_Alloc (DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem); - DN_Str8 DN_Slice_Str8Render (DN_Arena *arena, DN_Slice array, DN_Str8 separator); - DN_Str8 DN_Slice_Str8RenderSpaceSeparated (DN_Arena *arena, DN_Slice array); - DN_Str16 DN_Slice_Str16Render (DN_Arena *arena, DN_Slice array, DN_Str16 separator); - DN_Str16 DN_Slice_Str16RenderSpaceSeparated(DN_Arena *arena, DN_Slice array); -#endif // !defined(DN_NO_SLICE) -#if !defined(DN_NO_DSMAP) -template DN_DSMap DN_DSMap_Init (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); -template void DN_DSMap_Deinit (DN_DSMap *map, DN_ZeroMem zero_mem); -template bool DN_DSMap_IsValid (DN_DSMap const *map); -template DN_U32 DN_DSMap_Hash (DN_DSMap const *map, DN_DSMapKey key); -template DN_U32 DN_DSMap_HashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMap_Find (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMap_Make (DN_DSMap *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMap_Set (DN_DSMap *map, DN_DSMapKey key, T const &value); -template DN_DSMapResult DN_DSMap_FindKeyU64 (DN_DSMap const *map, DN_U64 key); -template DN_DSMapResult DN_DSMap_MakeKeyU64 (DN_DSMap *map, DN_U64 key); -template DN_DSMapResult DN_DSMap_SetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); -template DN_DSMapResult DN_DSMap_FindKeyStr8 (DN_DSMap const *map, DN_Str8 key); -template DN_DSMapResult DN_DSMap_MakeKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapResult DN_DSMap_SetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); -template bool DN_DSMap_Resize (DN_DSMap *map, DN_U32 size); -template bool DN_DSMap_Erase (DN_DSMap *map, DN_DSMapKey key); -template bool DN_DSMap_EraseKeyU64 (DN_DSMap *map, DN_U64 key); -template bool DN_DSMap_EraseKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapKey DN_DSMap_KeyBuffer (DN_DSMap const *map, void const *data, DN_U32 size); -template DN_DSMapKey DN_DSMap_KeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_U32 size); -template DN_DSMapKey DN_DSMap_KeyU64 (DN_DSMap const *map, DN_U64 u64); -template DN_DSMapKey DN_DSMap_KeyStr8 (DN_DSMap const *map, DN_Str8 string); -#define DN_DSMap_KeyCStr8(map, string) DN_DSMap_KeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) -DN_API DN_DSMapKey DN_DSMap_KeyU64NoHash (DN_U64 u64); -DN_API bool DN_DSMap_KeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); -DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); -#endif // !defined(DN_NO_DSMAP) -#if !defined(DN_NO_LIST) -template DN_List DN_List_Init (DN_USize chunk_size); -template DN_List DN_List_InitCArray (DN_Arena *arena, DN_USize chunk_size, T const (&array)[N]); -template T * DN_List_At (DN_List *list, DN_USize index, DN_ListChunk **at_chunk); -template void DN_List_Clear (DN_List *list); -template bool DN_List_Iterate (DN_List *list, DN_ListIterator *it, DN_USize start_index); -template T * DN_List_MakeArena (DN_List *list, DN_Arena *arena, DN_USize count); -template T * DN_List_MakePool (DN_List *list, DN_Pool *pool, DN_USize count); -template T * DN_List_AddArena (DN_List *list, DN_Arena *arena, T const &value); -template T * DN_List_AddPool (DN_List *list, DN_Pool *pool, T const &value); -template void DN_List_AddListArena (DN_List *list, DN_Arena *arena, DN_List other); -template void DN_List_AddListArena (DN_List *list, DN_Pool *pool, DN_List other); -template DN_Slice DN_List_ToSliceCopy (DN_List const *list, DN_Arena* arena); -#endif // !defined(DN_NO_LIST) - diff --git a/dqn_debug.cpp b/dqn_debug.cpp deleted file mode 100644 index 398eb49..0000000 --- a/dqn_debug.cpp +++ /dev/null @@ -1,509 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\ $$ _____|$$ __$$\ $$ | $$ |$$ __$$\ -// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ / \__| -// $$ | $$ |$$$$$\ $$$$$$$\ |$$ | $$ |$$ |$$$$\ -// $$ | $$ |$$ __| $$ __$$\ $$ | $$ |$$ |\_$$ | -// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ | $$ | -// $$$$$$$ |$$$$$$$$\ $$$$$$$ |\$$$$$$ |\$$$$$$ | -// \_______/ \________|\_______/ \______/ \______/ -// -// dqn_debug.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$ASAN] DN_Asan ////////////////////////////////////////////////////////////////////////// /// -DN_API void DN_ASAN_PoisonMemoryRegion(void const volatile *ptr, DN_USize size) -{ - if (!ptr || !size) - return; - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - DN_ASSERTF(DN_IsPowerOfTwoAligned(ptr, 8), - "Poisoning requires the pointer to be aligned on an 8 byte boundary"); - - __asan_poison_memory_region(ptr, size); - if (DN_ASAN_VET_POISON) { - DN_HARD_ASSERT(__asan_address_is_poisoned(ptr)); - DN_HARD_ASSERT(__asan_address_is_poisoned((char *)ptr + (size - 1))); - } - #else - (void)ptr; (void)size; - #endif -} - -DN_API void DN_ASAN_UnpoisonMemoryRegion(void const volatile *ptr, DN_USize size) -{ - if (!ptr || !size) - return; - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - __asan_unpoison_memory_region(ptr, size); - if (DN_ASAN_VET_POISON) { - DN_HARD_ASSERT(__asan_region_is_poisoned((void *)ptr, size) == 0); - } - #else - (void)ptr; (void)size; - #endif -} - -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limit) -{ - DN_StackTraceWalkResult result = {}; - #if defined(DN_OS_WIN32) - if (!arena) - return result; - - static DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); - - HANDLE thread = GetCurrentThread(); - result.process = GetCurrentProcess(); - - if (!g_dn_core->win32_sym_initialised) { - g_dn_core->win32_sym_initialised = true; - SymSetOptions(SYMOPT_LOAD_LINES); - if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_WinError error = DN_Win_LastError(tmem.arena); - DN_Log_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_STR_FMT(error.msg)); - } - } - - CONTEXT context; - RtlCaptureContext(&context); - - STACKFRAME64 frame = {}; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; - - DN_FArray raw_frames = {}; - while (raw_frames.size < limit) { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, - result.process, - thread, - &frame, - &context, - nullptr /*ReadMemoryRoutine*/, - SymFunctionTableAccess64, - SymGetModuleBase64, - nullptr /*TranslateAddress*/)) { - break; - } - - // NOTE: It might be useful one day to use frame.AddrReturn.Offset. - // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. - DN_FArray_Add(&raw_frames, frame.AddrPC.Offset); - } - DN_TicketMutex_End(&mutex); - - result.base_addr = DN_Arena_NewArray(arena, uint64_t, raw_frames.size, DN_ZeroMem_No); - result.size = DN_CAST(uint16_t)raw_frames.size; - DN_MEMCPY(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); - #else - (void)limit; (void)arena; - #endif - return result; -} - -DN_API DN_StackTraceWalkResult DN_StackTrace_WalkCRT(uint16_t limit) -{ - DN_StackTraceWalkResult result = {}; - #if defined(DN_OS_WIN32) - static DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); - - HANDLE thread = GetCurrentThread(); - result.process = GetCurrentProcess(); - - if (!g_dn_core->win32_sym_initialised) { - g_dn_core->win32_sym_initialised = true; - SymSetOptions(SYMOPT_LOAD_LINES); - if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - DN_WinError error = DN_Win_LastErrorAlloc(); - DN_Log_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_STR_FMT(error.msg)); - DN_OS_MemDealloc(error.msg.data); - } - } - - CONTEXT context; - RtlCaptureContext(&context); - - STACKFRAME64 frame = {}; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; - - struct FrameChunk - { - uint64_t frames[128]; - FrameChunk *next; - uint8_t size; - }; - - DN_USize total_frames = 0; - FrameChunk frame_chunk = {}; - FrameChunk *frame_chunk_it = &frame_chunk; - for (; total_frames < limit; total_frames++) { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, - result.process, - thread, - &frame, - &context, - nullptr /*ReadMemoryRoutine*/, - SymFunctionTableAccess64, - SymGetModuleBase64, - nullptr /*TranslateAddress*/)) { - break; - } - - // NOTE: It might be useful one day to use frame.AddrReturn.Offset. - // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. - if (frame_chunk_it->size == DN_ARRAY_UCOUNT(frame_chunk_it->frames)) { - FrameChunk *next = DN_CAST(FrameChunk *) DN_OS_MemAlloc(sizeof(*next), DN_ZeroMem_No); - frame_chunk_it = next; - } - - if (!frame_chunk_it) - break; - frame_chunk_it->frames[frame_chunk_it->size++] = frame.AddrPC.Offset; - } - DN_TicketMutex_End(&mutex); - - result.base_addr = DN_CAST(uint64_t *)DN_OS_MemAlloc(sizeof(*result.base_addr) * total_frames, DN_ZeroMem_No); - for (FrameChunk *it = &frame_chunk; it; ) { - FrameChunk *next = it->next; - - // NOTE: Copy - DN_MEMCPY(result.base_addr, it->frames, it->size * sizeof(it->frames[0])); - result.size += it->size; - - // NOTE: Free - if (it != &frame_chunk) - DN_OS_MemDealloc(it); - it = next; - } - #else - DN_INVALID_CODE_PATH; - (void)limit; - #endif - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkStr8CRT(uint16_t limit, uint16_t skip) -{ - DN_StackTraceWalkResult walk_result = DN_StackTrace_WalkCRT(limit); - DN_Str8 result = DN_StackTrace_WalkResultStr8CRT(&walk_result, skip); - return result; -} - -static void DN_StackTrace_AddWalkToStr8Builder_(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) -{ - DN_StackTraceRawFrame raw_frame = {}; - raw_frame.process = walk->process; - for (DN_USize index = skip; index < walk->size; index++) { - raw_frame.base_addr = walk->base_addr[index]; - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder->arena, raw_frame); - DN_Str8Builder_AppendF(builder, "%.*s(%zu): %.*s%s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name), (DN_CAST(int)index == walk->size - 1) ? "" : "\n"); - } -} - -DN_API DN_Str8 DN_StackTrace_WalkStr8CRTNoScratch(uint16_t limit, uint16_t skip) -{ - DN_Arena arena = {}; - arena.flags |= DN_ArenaFlags_NoAllocTrack; - DN_DEFER { DN_Arena_Deinit(&arena); }; - - DN_Str8Builder builder = {}; - builder.arena = &arena; - - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(&arena, limit); - DN_StackTraceRawFrame raw_frame = {}; - raw_frame.process = walk.process; - for (DN_USize index = skip; index < walk.size; index++) { - raw_frame.base_addr = walk.base_addr[index]; - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder.arena, raw_frame); - DN_Str8Builder_AppendF(&builder, "%.*s(%zu): %.*s%s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name), (DN_CAST(int)index == walk.size - 1) ? "" : "\n"); - } - - DN_Str8 result = {}; - result.data = DN_CAST(char *)DN_OS_MemReserve(builder.string_size + 1, DN_OSMemCommit_Yes, DN_OSMemPage_ReadWrite); - if (result.data) { - for (DN_Str8Link *it = builder.head; it; it = it->next) { - DN_MEMCPY(result.data + result.size, it->string.data, it->string.size); - result.size += it->string.size; - } - } - - return result; -} - -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) -{ - bool result = false; - if (!it || !walk || !walk->base_addr || !walk->process) - return result; - - if (it->index >= walk->size) - return false; - - result = true; - it->raw_frame.process = walk->process; - it->raw_frame.base_addr = walk->base_addr[it->index++]; - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkResultStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) -{ - DN_Str8 result {}; - if (!walk || !arena) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - DN_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); - result = DN_Str8Builder_Build(&builder, arena); - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkResultStr8CRT(DN_StackTraceWalkResult const *walk, uint16_t skip) -{ - DN_Str8 result {}; - if (!walk) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - DN_StackTrace_AddWalkToStr8Builder_(walk, &builder, skip); - result = DN_Str8Builder_BuildCRT(&builder); - return result; -} - - -DN_API DN_Slice DN_StackTrace_GetFrames(DN_Arena *arena, uint16_t limit) -{ - DN_Slice result = {}; - if (!arena) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); - if (!walk.size) - return result; - - DN_USize slice_index = 0; - result = DN_Slice_Alloc(arena, walk.size, DN_ZeroMem_No); - for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { - result.data[slice_index++] = DN_StackTrace_RawFrameToFrame(arena, it.raw_frame); - } - return result; -} - -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) -{ - #if defined(DN_OS_WIN32) - // NOTE: Get line+filename ///////////////////////////////////////////////////////////////////// - - // TODO: Why does zero-initialising this with `line = {};` cause - // SymGetLineFromAddr64 function to fail once we are at - // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The - // line and file number are still valid in the result which we use, so, - // we silently ignore this error. - IMAGEHLP_LINEW64 line; - line.SizeOfStruct = sizeof(line); - DWORD line_displacement = 0; - if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) { - line = {}; - } - - // NOTE: Get function name ///////////////////////////////////////////////////////////////////// - - alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; - SYMBOL_INFOW *symbol = DN_CAST(SYMBOL_INFOW *)buffer; - symbol->SizeOfStruct = sizeof(*symbol); - symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); - - uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address - SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); - - // NOTE: Construct result ////////////////////////////////////////////////////////////////////// - - DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16_Size(line.FileName)}; - DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; - - DN_StackTraceFrame result = {}; - result.address = raw_frame.base_addr; - result.line_number = line.LineNumber; - result.file_name = DN_Win_Str16ToStr8(arena, file_name16); - result.function_name = DN_Win_Str16ToStr8(arena, function_name16); - - if (!DN_Str8_HasData(result.function_name)) - result.function_name = DN_STR8(""); - if (!DN_Str8_HasData(result.file_name)) - result.file_name = DN_STR8(""); - #else - DN_StackTraceFrame result = {}; - #endif - return result; -} - -DN_API void DN_StackTrace_Print(uint16_t limit) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Slice stack_trace = DN_StackTrace_GetFrames(tmem.arena, limit); - for (DN_StackTraceFrame &frame : stack_trace) - DN_Print_ErrLnF("%.*s(%I64u): %.*s", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); -} - -DN_API void DN_StackTrace_ReloadSymbols() -{ - #if defined(DN_OS_WIN32) - HANDLE process = GetCurrentProcess(); - SymRefreshModuleList(process); - #endif -} - -// NOTE: [$DEBG] DN_Debug ///////////////////////////////////////////////////////////////////////// -#if defined(DN_LEAK_TRACKING) -DN_API void DN_Debug_TrackAlloc(void *ptr, DN_USize size, bool leak_permitted) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER { - DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); - }; - - // NOTE: If the entry was not added, we are reusing a pointer that has been freed. - // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it - // already existed. - DN_Str8 stack_trace = DN_StackTrace_WalkStr8CRTNoScratch(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_CAST(uint64_t) ptr); - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { - if ((alloc->flags & DN_DebugAllocFlag_Freed) == 0) { - DN_Str8 alloc_size = DN_U64ToByteSizeStr8(alloc_table->arena, alloc->size, DN_U64ByteSizeType_Auto); - DN_Str8 new_alloc_size = DN_U64ToByteSizeStr8(alloc_table->arena, size, DN_U64ByteSizeType_Auto); - DN_HARD_ASSERTF( - alloc->flags & DN_DebugAllocFlag_Freed, - "This pointer is already in the leak tracker, however it has not " - "been freed yet. This same pointer is being ask to be tracked " - "twice in the allocation table, e.g. one if its previous free " - "calls has not being marked freed with an equivalent call to " - "DN_Debug_TrackDealloc()\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is allocating %.*s again at:\n" - "\n" - "%.*s\n" - , - ptr, DN_STR_FMT(alloc_size), - DN_STR_FMT(alloc->stack_trace), - DN_STR_FMT(new_alloc_size), - DN_STR_FMT(stack_trace)); - } - - // NOTE: Pointer was reused, clean up the prior entry - DN_OS_MemRelease(alloc->stack_trace.data, alloc->stack_trace.size); - DN_OS_MemRelease(alloc->freed_stack_trace.data, alloc->freed_stack_trace.size); - *alloc = {}; - } - - alloc->ptr = ptr; - alloc->size = size; - alloc->stack_trace = stack_trace; - alloc->flags |= leak_permitted ? DN_DebugAllocFlag_LeakPermitted : 0; -} - -DN_API void DN_Debug_TrackDealloc(void *ptr) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER { DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); }; - - DN_Str8 stack_trace = DN_StackTrace_WalkStr8CRTNoScratch(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_CAST(uintptr_t) ptr); - DN_HARD_ASSERTF(alloc_entry.found, - "Allocated pointer can not be removed as it does not exist in the " - "allocation table. When this memory was allocated, the pointer was " - "not added to the allocation table [ptr=%p]", - ptr); - - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc->flags & DN_DebugAllocFlag_Freed) { - DN_Str8 freed_size = DN_U64ToByteSizeStr8(alloc_table->arena, alloc->freed_size, DN_U64ByteSizeType_Auto); - DN_HARD_ASSERTF((alloc->flags & DN_DebugAllocFlag_Freed) == 0, - "Double free detected, pointer to free was already marked " - "as freed. Either the pointer was reallocated but not " - "traced, or, the pointer was freed twice.\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer was freed at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is being freed again at:\n" - "\n" - "%.*s\n" - , - ptr, DN_STR_FMT(freed_size), - DN_STR_FMT(alloc->stack_trace), - DN_STR_FMT(alloc->freed_stack_trace), - DN_STR_FMT(stack_trace)); - } - - DN_ASSERT(!DN_Str8_HasData(alloc->freed_stack_trace)); - alloc->flags |= DN_DebugAllocFlag_Freed; - alloc->freed_stack_trace = stack_trace; -} - -DN_API void DN_Debug_DumpLeaks() -{ - uint64_t leak_count = 0; - uint64_t leaked_bytes = 0; - for (DN_USize index = 1; index < g_dn_core->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = g_dn_core->alloc_table.slots + index; - DN_DebugAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_DebugAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_DebugAllocFlag_LeakPermitted); - if (alloc_leaked && !leak_permitted) { - leaked_bytes += alloc->size; - leak_count++; - DN_Str8 alloc_size = DN_U64ToByteSizeStr8(g_dn_core->alloc_table.arena, alloc->size, DN_U64ByteSizeType_Auto); - DN_Log_WarningF("Pointer (0x%p) leaked %.*s at:\n" - "%.*s", - alloc->ptr, DN_STR_FMT(alloc_size), - DN_STR_FMT(alloc->stack_trace)); - } - } - - if (leak_count) { - DN_Str8 leak_size = DN_U64ToByteSizeStr8(&g_dn_core->arena, leaked_bytes, DN_U64ByteSizeType_Auto); - DN_Log_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_STR_FMT(leak_size)); - } -} -#endif // DN_LEAK_TRACKING diff --git a/dqn_debug.h b/dqn_debug.h deleted file mode 100644 index 2ab706e..0000000 --- a/dqn_debug.h +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\ $$ _____|$$ __$$\ $$ | $$ |$$ __$$\ -// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ / \__| -// $$ | $$ |$$$$$\ $$$$$$$\ |$$ | $$ |$$ |$$$$\ -// $$ | $$ |$$ __| $$ __$$\ $$ | $$ |$$ |\_$$ | -// $$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ | $$ | -// $$$$$$$ |$$$$$$$$\ $$$$$$$ |\$$$$$$ |\$$$$$$ | -// \_______/ \________|\_______/ \______/ \______/ -// -// dqn_debug.h -- Tools for debugging -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$ASAN] DN_Asan -- Helpers to manually poison memory using ASAN -// [$STKT] DN_StackTrace -- Create stack traces -// [$DEBG] DN_Debug -- Allocation leak tracking API -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$ASAN] DN_Asan ////////////////////////////////////////////////////////////////////////// -#if !defined(DN_ASAN_POISON) - #define DN_ASAN_POISON 0 -#endif - -#if !defined(DN_ASAN_VET_POISON) - #define DN_ASAN_VET_POISON 0 -#endif - -#define DN_ASAN_POISON_ALIGNMENT 8 - -#if !defined(DN_ASAN_POISON_GUARD_SIZE) - #define DN_ASAN_POISON_GUARD_SIZE 128 -#endif -static_assert(DN_IsPowerOfTwoAligned(DN_ASAN_POISON_GUARD_SIZE, DN_ASAN_POISON_ALIGNMENT), - "ASAN poison guard size must be a power-of-two and aligned to ASAN's alignment" - "requirement (8 bytes)"); - -#if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - #include -#endif - -// NOTE: [$STKT] DN_StackTrace //////////////////////////////////////////////////////////////////// -struct DN_StackTraceFrame -{ - uint64_t address; - uint64_t line_number; - DN_Str8 file_name; - DN_Str8 function_name; -}; - -struct DN_StackTraceRawFrame -{ - void *process; - uint64_t base_addr; -}; - -struct DN_StackTraceWalkResult -{ - void *process; // [Internal] Windows handle to the process - uint64_t *base_addr; // The addresses of the functions in the stack trace - uint16_t size; // The number of `base_addr`'s stored from the walk -}; - -struct DN_StackTraceWalkResultIterator -{ - DN_StackTraceRawFrame raw_frame; - uint16_t index; -}; - -// NOTE: [$DEBG] DN_Debug ///////////////////////////////////////////////////////////////////////// -enum DN_DebugAllocFlag -{ - DN_DebugAllocFlag_Freed = 1 << 0, - DN_DebugAllocFlag_LeakPermitted = 1 << 1, -}; - -struct DN_DebugAlloc -{ - void *ptr; // 8 Pointer to the allocation being tracked - DN_USize size; // 16 Size of the allocation - DN_USize freed_size; // 24 Store the size of the allocation when it is freed - DN_Str8 stack_trace; // 40 Stack trace at the point of allocation - DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed - uint16_t flags; // 72 Bit flags from `DN_DebugAllocFlag` -}; - -static_assert(sizeof(DN_DebugAlloc) == 64 || sizeof(DN_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively - "We aim to keep the allocation record as light as possible as " - "memory tracking can get expensive. Enforce that there is no " - "unexpected padding."); - -// NOTE: [$ASAN] DN_Asan ////////////////////////////////////////////////////////////////////////// -DN_API void DN_ASAN_PoisonMemoryRegion (void const volatile *ptr, DN_USize size); -DN_API void DN_ASAN_UnpoisonMemoryRegion (void const volatile *ptr, DN_USize size); - -// NOTE: [$STKT] DN_StackTrace //////////////////////////////////////////////////////////////////// -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk (DN_Arena *arena, uint16_t limit); -DN_API DN_Str8 DN_StackTrace_WalkStr8CRT (uint16_t limit, uint16_t skip); -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); -DN_API DN_Str8 DN_StackTrace_WalkResultStr8 (DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip); -DN_API DN_Str8 DN_StackTrace_WalkResultStr8CRT(DN_StackTraceWalkResult const *walk, uint16_t skip); -DN_API DN_Slice DN_StackTrace_GetFrames (DN_Arena *arena, uint16_t limit); -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame (DN_Arena *arena, DN_StackTraceRawFrame raw_frame); -DN_API void DN_StackTrace_Print (uint16_t limit); -DN_API void DN_StackTrace_ReloadSymbols (); - -// NOTE: [$DEBG] DN_Debug ///////////////////////////////////////////////////////////////////////// -#if defined(DN_LEAK_TRACKING) -DN_API void DN_Debug_TrackAlloc (void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_Debug_TrackDealloc (void *ptr); -DN_API void DN_Debug_DumpLeaks (); -#else -#define DN_Debug_TrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_Debug_TrackDealloc(ptr) do { (void)ptr; } while (0) -#define DN_Debug_DumpLeaks() do { } while (0) -#endif - - diff --git a/dqn_docs.cpp b/dqn_docs.cpp deleted file mode 100644 index 43d6bad..0000000 --- a/dqn_docs.cpp +++ /dev/null @@ -1,1207 +0,0 @@ -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ -// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| -// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ -// $$ | $$ |$$ | $$ |$$ | \____$$\ -// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | -// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | -// \_______/ \______/ \______/ \______/ -// -// dqn_docs.cpp -- Library documentation via real code examples -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Use this file for documentation and examples of the various APIs in this -// library. Normally docs are written as inline comments in header files, -// however, these quickly go out of date as APIs change. Instead, I provide -// some example code that compiles here that serves to also document the API. -// -// The library header files then become a very minimal reference of exactly the -// function prototypes and definitions instead of massive reams of inline -// comments that visually space out the functions and hinders discoverability -// and/or conciseness of being able to learn the breadth of the APIs. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4702) // unreachable code -void DN_Docs_Demo() -{ - // NOTE: Before using anything in the library, DN_Core_Init() must be - // called, for example: - #if 0 - DN_Core core = {}; - DN_Core_Init(&core, DN_CoreOnInit_Nil); - #endif - - // NOTE: DN_Atomic_SetValue64 ///////////////////////////////////////////////////////////////// - // NOTE: DN_Atomic_SetValue32 ///////////////////////////////////////////////////////////////// - // Atomically set the value into the target using an atomic compare and swap - // idiom. The return value of the function is the value that was last stored - // in the target. - { - uint64_t target = 8; - uint64_t value_to_set = 0xCAFE; - if (DN_Atomic_SetValue64(&target, value_to_set) == 8) { - // Atomic swap was successful, e.g. the last value that this thread - // observed was '8' which is the value we initialised with e.g. no - // other thread has modified the value. - } - } - - // NOTE: DN_BytesToHex //////////////////////////////////////////////////////////////////////// - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - unsigned char bytes[2] = {0xFA, 0xCE}; - DN_Str8 hex = DN_BytesToHex(tmem.arena, bytes, sizeof(bytes)); - DN_ASSERT(hex == DN_STR8("face")); // NOTE: Guaranteed to be null-terminated - } - - // NOTE: DN_CHECK ///////////////////////////////////////////////////////////////////////////// - // - // Check the expression trapping in debug, whilst in release- trapping is - // removed and the expression is evaluated as if it were a normal 'if' branch. - // - // This allows handling of the condition gracefully when compiled out but - // traps to notify the developer in builds when it's compiled in. - { - bool flag = true; - if (DN_CHECKF(flag, "Flag was false!")) { - /// This branch will execute! - } - } - - // NOTE: DN_CPUID ///////////////////////////////////////////////////////////////////////////// - // Execute the 'CPUID' instruction which lets you query the capabilities of - // the current CPU. - - // NOTE: DN_DEFER - // - // A macro that expands to a C++ lambda that executes arbitrary code on - // scope exit. - { - int x = 0; - DN_DEFER { - x = 3; - }; - x = 1; - // On scope exit, DN_DEFER object executes and assigns x = 3 - } - - // NOTE: DN_DSMap ///////////////////////////////////////////////////////////////////////////// - // - // A hash table configured using the presets recommended by Demitri Spanos - // from the Handmade Network (HMN), - // - // - power of two capacity - // - grow by 2x on load >= 75% - // - open-addressing with linear probing - // - separate large values (esp. variable length values) into a separate table - // - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) - // - chain-repair on delete (rehash items in the probe chain after delete) - // - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) - // - // Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 - // - // This hash-table stores slots (values) separate from the hash mapping. - // Hashes are mapped to slots using the hash-to-slot array which is an array - // of slot indexes. This array intentionally only stores indexes to maximise - // usage of the cache line. Linear probing on collision will only cost a - // couple of cycles to fetch from L1 cache the next slot index to attempt. - // - // The slots array stores values contiguously, non-sorted allowing iteration - // of the map. On element erase, the last element is swapped into the - // deleted element causing the non-sorted property of this table. - // - // The 0th slot (DN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved - // for a sentinel value, e.g. all zeros value. After map initialisation the - // 'occupied' value of the array will be set to 1 to exclude the sentinel - // from the capacity of the table. Skip the first value if you are iterating - // the hash table! - // - // This hash-table accept either a U64 or a buffer (ptr + len) as the key. - // In practice this covers a majority of use cases (with string, buffer and - // number keys). It also allows us to minimise our C++ templates to only - // require 1 variable which is the Value part of the hash-table simplifying - // interface complexity and cruft brought by C++. - // - // Keys are value-copied into the hash-table. If the key uses a pointer to a - // buffer, this buffer must be valid throughout the lifetime of the hash - // table! - { - // NOTE: DN_DSMap_Init ////////////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_Deinit ////////////////////////////////////////////////////////////////// - // - // Initialise a hash table where the table size *must* be a - // power-of-two, otherwise an assert will be triggered. If - // initialisation fails (e.g. memory allocation failure) the table is - // returned zero-initialised where a call to 'IsValid' will return - // false. - // - // The map takes ownership of the arena. This means in practice that if the - // map needs to resize (e.g. because the load threshold of the table is - // exceeded), the arena associated with it will be released and the memory - // will be reallocated with the larger capacity and reassigned to the arena. - // - // In simple terms, when the map resizes it invalidates all memory that was - // previously allocated with the given arena! - // - // A 'Deinit' of the map will similarly deallocate the passed in arena (as - // the map takes ownership of the arena). - DN_Arena arena = {}; - DN_DSMap map = DN_DSMap_Init(&arena, /*size*/ 1024, DN_DSMapFlags_Nil); // Size must be PoT! - DN_ASSERT(DN_DSMap_IsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure) - - // NOTE: DN_DSMap_KeyCStringLit /////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_KeyU64 /////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_KeyU64NoHash /////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_KeyBuffer /////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_KeyStr8 /////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_KeyStr8Copy /////////////////////////////////////////////////////////// - // Create a hash-table key where: - // - // KeyCStringLit: Uses a Hash(cstring literal) - // KeyU64: Uses a Hash(U64) - // KeyU64NoHash: Uses a U64 (where it's truncated to 4 bytes) - // KeyBuffer: Uses a Hash(ptr+len) slice of bytes - // KeyStr8: Uses a Hash(string) - // KeyStr8Copy: Uses a Hash(string) that is copied first using the arena - // - // Buffer-based keys memory must persist throughout lifetime of the map. - // Keys are valued copied into the map, alternatively, copy the - // key/buffer before constructing the key. - // - // You *can't* use the map's arena to allocate keys because on resize it - // will deallocate then reallocate the entire arena. - // - // KeyU64NoHash may be useful if you have a source of data that is - // already sufficiently uniformly distributed already (e.g. using 8 - // bytes taken from a SHA256 hash as the key) and the first 4 bytes - // will be used verbatim. - DN_DSMapKey key = DN_DSMap_KeyStr8(&map, DN_STR8("Sample Key")); - - // NOTE: DN_DSMap_Find //////////////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_Make //////////////////////////////////////////////////////////////////// - // NOTE: DN_DSMap_Set //////////////////////////////////////////////////////////////////// - // - // Query or commit key-value pair to the table, where: - // - // Find: does a key-lookup on the table and returns the hash table slot's value - // Make: assigns the key to the table and returns the hash table slot's value - // Set: assigns the key-value to the table and returns the hash table slot's value - // - // A find query will set 'found' to false if it does not exist. - // - // For 'Make' and 'Set', 'found' can be set to 'true' if the item already - // existed in the map prior to the call. If it's the first time the - // key-value pair is being inserted 'found' will be set to 'false'. - // - // If by adding the key-value pair to the table puts the table over 75% load, - // the table will be grown to 2x the current the size before insertion - // completes. - { - DN_DSMapResult set_result = DN_DSMap_Set(&map, key, 0xCAFE); - DN_ASSERT(!set_result.found); // First time we are setting the key-value pair, it wasn't previously in the table - DN_ASSERT(map.occupied == 2); // Sentinel + new element == 2 - } - - // Iterating elements in the array, note that index '0' is the sentinel - // slot! You typically don't care about it! - for (DN_USize index = 1; index < map.occupied; index++) { - DN_DSMapSlot *it = map.slots + index; - DN_DSMapKey it_key = it->key; - int *it_value = &it->value; - DN_ASSERT(*it_value == 0xCAFE); - - DN_ASSERT(DN_Str8_Init(it_key.buffer_data, it_key.buffer_size) == DN_STR8("Sample Key")); - } - - // NOTE: DN_DSMap_Erase /////////////////////////////////////////////////////////////////// - // - // Remove the key-value pair from the table. If by erasing the key-value - // pair from the table puts the table under 25% load, the table will be - // shrunk by 1/2 the current size after erasing. The table will not shrink - // below the initial size that the table was initialised as. - { - bool erased = DN_DSMap_Erase(&map, key); - DN_ASSERT(erased); - DN_ASSERT(map.occupied == 1); // Sentinel element - } - - DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); // Deallocates the 'arena' for us! - } - - // NOTE: DN_DSMap_Hash //////////////////////////////////////////////////////////////////////// - // - // Hash the input key using the custom hash function if it's set on the map, - // otherwise uses the default hashing function (32bit Murmur3). - - // NOTE: DN_DSMap_HashToSlotIndex ///////////////////////////////////////////////////////////// - // - // Calculate the index into the map's 'slots' array from the given hash. - - // NOTE: DN_DSMap_Resize ////////////////////////////////////////////////////////////////////// - // - // Resize the table and move all elements to the new map, note that the new - // size must be a power of two. This function wil fail on memory allocation - // failure, or the requested size is smaller than the current number of - // elements in the map to resize. - - // NOTE: DN_ErrSink ///////////////////////////////////////////////////////////////////////// - // - // Error sinks are a way of accumulating errors from API calls related or - // unrelated into 1 unified error handling pattern. The implemenation of a - // sink requires 2 fundamental design constraints on the APIs supporting - // this pattern. - // - // 1. Pipelining of errors - // Errors emitted over the course of several API calls are accumulated - // into a sink which save the error code and message of the first error - // encountered and can be checked later. - // - // 2. Error proof APIs - // Functions that produce errors must return objects/handles that are - // marked to trigger no-ops used in subsequent functions dependent on it. - // - // Consider the following example demonstrating a conventional error - // handling approach (error values by return/sentinel values) and error - // handling using error-proof and pipelining. - - // (A) Conventional error checking patterns using return/sentinel values - #if 0 - DN_OSFile *file = DN_OS_FileOpen("/path/to/file", ...); - if (file) { - if (!DN_OS_FileWrite(file, "abc")) { - // Error handling! - } - Dnq_OS_FileClose(file); - } else { - // Error handling! - } - #endif - - // (B) Error handling using pipelining and and error proof APIs. APIs that - // produce errors take in the error sink as a parameter. - if (0) { - DN_ErrSink *error = DN_ErrSink_Begin(DN_ErrSinkMode_Nil); - DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); - DN_OS_FileWrite(&file, DN_STR8("abc"), error); - DN_OS_FileClose(&file); - if (DN_ErrSink_EndAndLogErrorF(error, "Failed to write to file")) { - // Do error handling! - } - } - - // Pipeling and error-proof APIs lets you write sequence of instructions and - // defer error checking until it is convenient or necessary. Functions are - // *guaranteed* to return an object that is usable. There are no hidden - // exceptions to be thrown. Functions may opt to still return error values - // by way of return values thereby *not* precluding the ability to check - // every API call either. - // - // Ultimately, this error handling approach gives more flexibility on the - // manner in how errors are handled with less code. - // - // Error sinks can nest begin and end statements. This will open a new scope - // whereby the current captured error pushed onto a stack and the sink will - // be populated by the first error encountered in that scope. - - if (0) { - DN_ErrSink *error = DN_ErrSink_Begin(DN_ErrSinkMode_Nil); - DN_OSFile file = DN_OS_FileOpen(DN_STR8("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); - DN_OS_FileWrite(&file, DN_STR8("abc"), error); - DN_OS_FileClose(&file); - - { - // NOTE: My error sinks are thread-local, so the returned 'error' is - // the same as the 'error' value above. - DN_ErrSink_Begin(DN_ErrSinkMode_Nil); - DN_OS_WriteAll(DN_STR8("/path/to/another/file"), DN_STR8("123"), error); - DN_ErrSink_EndAndLogErrorF(error, "Failed to write to another file"); - } - - if (DN_ErrSink_EndAndLogErrorF(error, "Failed to write to file")) { - // Do error handling! - } - } - - // NOTE: DN_FStr8_Max ///////////////////////////////////////////////////////////////////////// - // - // Return the maximum capacity of the string, e.g. the 'N' template - // parameter of FStr8 - - // NOTE: DN_FStr8_ToStr8 ////////////////////////////////////////////////////////////////////// - // - // Create a slice of the string into a pointer and length string (DN_Str8). - // The lifetime of the slice is bound to the lifetime of the FStr8 and is - // invalidated when the FStr8 is. - - // NOTE: DN_HexToBytes //////////////////////////////////////////////////////////////////////// - { - unsigned char bytes[2]; - DN_USize bytes_written = DN_HexToBytesPtr(DN_STR8("0xFACE"), bytes, sizeof(bytes)); - DN_ASSERT(bytes_written == 2); - DN_ASSERT(bytes[0] == 0xFA); - DN_ASSERT(bytes[1] == 0xCE); - } - - // NOTE: DN_JSONBuilder_Build ///////////////////////////////////////////////////////////////// - // - // Convert the internal JSON buffer in the builder into a string. - - // NOTE: DN_JSONBuilder_KeyValue, DN_JSONBuilder_KeyValueF - // - // Add a JSON key value pair untyped. The value is emitted directly without - // checking the contents of value. - // - // All other functions internally call into this function which is the main - // workhorse of the builder. - - // NOTE: DN_JSON_Builder_ObjectEnd - // - // End a JSON object in the builder, generates internally a '}' string - - // NOTE: DN_JSON_Builder_ArrayEnd - // - // End a JSON array in the builder, generates internally a ']' string - - // NOTE: DN_JSONBuilder_LiteralNamed - // - // Add a named JSON key-value object whose value is directly written to - // the following '"": ' (e.g. useful for emitting the 'null' - // value) - - // NOTE: DN_JSONBuilder_U64 ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_U64Named ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_I64 ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_I64Named ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_F64 ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_F64Named ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_Bool ///////////////////////////////////////////////////////////// - // NOTE: DN_JSONBuilder_BoolNamed ///////////////////////////////////////////////////////////// - // - // Add the named JSON data type as a key-value object. The named variants - // generates internally the key-value pair, e.g. - // - // "": - // - // And the non-named version emit just the 'value' portion - - // NOTE: DN_List_Iterate ////////////////////////////////////////////////////////////////////// - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_List list = DN_List_Init(/*chunk_size*/ 128); - for (DN_ListIterator it = {}; DN_List_Iterate(&list, &it, 0);) { - int *item = it.data; - (void)item; - } - } - - // NOTE: DN_LogProc /////////////////////////////////////////////////////////////////////////// - // - // Function prototype of the logging interface exposed by this library. Logs - // emitted using the DN_Log_* family of functions are routed through this - // routine. - - // NOTE: DN_FNV1A ///////////////////////////////////////////////////////////////////////////// - { - // Using the default hash as defined by DN_FNV1A32_SEED and - // DN_FNV1A64_SEED for 32/64bit hashes respectively - uint32_t buffer1 = 0xCAFE0000; - uint32_t buffer2 = 0xDEAD0000; - { - uint64_t hash = DN_FNV1A64_Hash(&buffer1, sizeof(buffer1)); - hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); // Chained hashing - (void)hash; - } - - // You can use a custom seed by skipping the 'Hash' call and instead - // calling 'Iterate' immediately. - { - uint64_t custom_seed = 0xABCDEF12; - uint64_t hash = DN_FNV1A64_Iterate(&buffer1, sizeof(buffer1), custom_seed); - hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); - (void)hash; - } - } - - // NOTE: DN_FmtBuffer3DotTruncate ////////////////////////////////////////////////////////////// - { - char buffer[8] = {}; - int buffer_chars_written = DN_FmtBuffer3DotTruncate(buffer, sizeof(buffer), "This string is longer than %d characters", DN_CAST(int)(sizeof(buffer) - 1)); - if (0) { // Prints "This ..." which is exactly 8 characters long - printf("%.*s", buffer_chars_written, buffer); - } - } - - // NOTE: DN_MurmurHash3 /////////////////////////////////////////////////////////////////////// - // MurmurHash3 was written by Austin Appleby, and is placed in the public - // domain. The author (Austin Appleby) hereby disclaims copyright to this source - // code. - // - // Note - The x86 and x64 versions do _not_ produce the same results, as the - // algorithms are optimized for their respective platforms. You can still - // compile and run any of them on any platform, but your performance with the - // non-native version will be less than optimal. - - // NOTE: DN_OS_DateUnixTime - // - // Produce the time elapsed since the unix epoch - { - uint64_t now = DN_OS_DateUnixTimeS(); - (void)now; - } - - // NOTE: DN_OS_DirIterate ///////////////////////////////////////////////////////////////////// - // - // Iterate the files within the passed in folder - for (DN_OSDirIterator it = {}; DN_OS_DirIterate(DN_STR8("."), &it); ) { - printf("%.*s\n", DN_STR_FMT(it.file_name)); - } - - // NOTE: DN_OS_FileDelete - // - // This function can only delete files and it can *only* delete directories - // if it is empty otherwise this function fails. - - // NOTE: DN_OS_WriteAllSafe - // Writes the file at the path first by appending '.tmp' to the 'path' to - // write to. If the temporary file is written successfully then the file is - // copied into 'path', for example: - // - // path: C:/Home/my.txt - // tmp_path: C:/Home/my.txt.tmp - // - // If 'tmp_path' is written to successfuly, the file will be copied over into - // 'path'. - if (0) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_ErrSink *error = DN_ErrSink_Begin(DN_ErrSinkMode_Nil); - DN_OS_WriteAllSafe(/*path*/ DN_STR8("C:/Home/my.txt"), /*buffer*/ DN_STR8("Hello world"), error); - DN_ErrSink_EndAndLogErrorF(error, ""); - } - - // NOTE: DN_OS_EstimateTSCPerSecond /////////////////////////////////////////////////////////// - // - // Estimate how many timestamp count's (TSC) there are per second. TSC - // is evaluated by calling __rdtsc() or the equivalent on the platform. This - // value can be used to convert TSC durations into seconds. - // - // The 'duration_ms_to_gauge_tsc_frequency' parameter specifies how many - // milliseconds to spend measuring the TSC rate of the current machine. - // 100ms is sufficient to produce a fairly accurate result with minimal - // blocking in applications if calculated on startup.. - // - // This may return 0 if querying the CPU timestamp counter is not supported - // on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). - - // NOTE: DN_OS_EXEDir ///////////////////////////////////////////////////////////////////////// - // - // Retrieve the executable directory without the trailing '/' or ('\' for - // windows). If this fails an empty string is returned. - - // NOTE: DN_OS_PerfCounterFrequency /////////////////////////////////////////////////////////// - // - // Get the number of ticks in the performance counter per second for the - // operating system you're running on. This value can be used to calculate - // duration from OS performance counter ticks. - - // NOTE: DN_OS_Path* ////////////////////////////////////////////////////////////////////////// - // Construct paths ensuring the native OS path separators are used in the - // string. In 99% of cases you can use 'PathConvertF' which converts the - // given path in one shot ensuring native path separators in the string. - // - // path: C:\Home/My/Folder - // converted: C:/Home/My/Folder (On Unix) - // C:\Home\My\Folder (On Windows) - // - // If you need to construct a path dynamically you can use the builder-esque - // interface to build a path's step-by-step using the 'OSPath' data structure. - // With this API you can append paths piece-meal to build the path after all - // pieces are appended. - // - // You may append a singular or nested path to the builder. In the builder, - // the string is scanned and separated into path separated chunks and stored - // in the builder, e.g. these are all valid to pass into 'PathAdd', - // 'PathAddRef' ... e.t.c - // - // "path/to/your/desired/folder" is valid - // "path" is valid - // "path/to\your/desired\folder" is valid - // - // 'PathPop' removes the last appended path from the current path stored in - // the 'OSPath': - // - // path: path/to/your/desired/folder - // popped_path: path/to/your/desired - - // NOTE: DN_OS_SecureRNGBytes ///////////////////////////////////////////////////////////////// - // - // Generate cryptographically secure bytes - - // NOTE: DN_PCG32 ///////////////////////////////////////////////////////////////////////////// - // - // Random number generator of the PCG family. Implementation taken from - // Martins Mmozeiko from Handmade Network. - // https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 - { - DN_PCG32 rng = DN_PCG32_Init(0xb917'a66c'1d9b'3bd8); - - // NOTE: DN_PCG32_Range /////////////////////////////////////////////////////////////////// - // - // Generate a value in the [low, high) interval - uint32_t u32_value = DN_PCG32_Range(&rng, 32, 64); - DN_ASSERT(u32_value >= 32 && u32_value < 64); - - // NOTE: DN_PCG32_NextF32 ///////////////////////////////////////////////////////////////// - // NOTE: DN_PCG32_NextF64 ///////////////////////////////////////////////////////////////// - // - // Generate a float/double in the [0, 1) interval - DN_F64 f64_value = DN_PCG32_NextF64(&rng); - DN_ASSERT(f64_value >= 0.f && f64_value < 1.f); - - // NOTE: DN_PCG32_Advance ///////////////////////////////////////////////////////////////// - // - // Step the random number generator by 'delta' steps - DN_PCG32_Advance(&rng, /*delta*/ 5); - } - - #if !defined(DN_NO_PROFILER) - // NOTE: [$PROF] DN_Profiler ////////////////////////////////////////////////////////////////// - // - // A profiler based off Casey Muratori's Computer Enhance course, Performance - // Aware Programming. This profiler measures function elapsed time using the - // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count - // that can be converted into a duration. - // - // This profiler uses a double buffer scheme for storing profiling markers. - // After an application's typical update/frame cycle you can swap the - // profiler's buffer whereby the front buffer contains the previous frames - // profiling metrics and the back buffer will be populated with the new - // frame's profiling metrics. - { - enum Zone { Zone_MainLoop, Zone_Count }; - DN_ProfilerZone profiler_zone_main_update = DN_Profiler_BeginZone(Zone_MainLoop); - - // NOTE: DN_Profiler_AnchorBuffer ///////////////////////////////////////////////////// - // - // Retrieve the requested buffer from the profiler for - // writing/reading profiling metrics. Pass in the enum to specify - // which buffer to grab from the profiler. - // - // The front buffer contains the previous frame's profiling metrics - // and the back buffer is where the profiler is currently writing - // to. - // - // For end user intents and purposes, you likely only need to read - // the front buffer which contain the metrics that you can visualise - // regarding the most profiling metrics recorded. - - // NOTE: DN_Profiler_ReadBuffer /////////////////////////////////////////////////////////// - // - // Retrieve the buffer of anchors of which there are - // `DN_PROFILER_ANCHOR_BUFFER_SIZE` anchors from the most recent run - // of the profiler after you have called `SwapAnchorBuffer` to trigger - // the double buffer - DN_ProfilerAnchor *read_anchors = DN_Profiler_ReadBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = read_anchors + index; - if (DN_Str8_HasData(anchor->name)) { - // ... - } - } - - // NOTE: DN_Profiler_WriteBuffer ////////////////////////////////////////////////////////// - // - // Same as `ReadBuffer` however we return the buffer that the profiler - // is currently writing anchors into. - DN_ProfilerAnchor *write_anchors = DN_Profiler_WriteBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = write_anchors + index; - if (DN_Str8_HasData(anchor->name)) { - // ... - } - } - - DN_Profiler_EndZone(profiler_zone_main_update); - DN_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! - DN_MEMSET(g_dn_core->profiler, 0, sizeof(g_dn_core->profiler)); - } - #endif // !defined(DN_NO_PROFILER) - - // NOTE: DN_Raycast_LineIntersectV2 /////////////////////////////////////////////////////////// - // Calculate the intersection point of 2 rays returning a `t` value - // which is how much along the direction of the 'ray' did the intersection - // occur. - // - // The arguments passed in do not need to be normalised for the function to - // work. - - // NOTE: DN_Safe_* //////////////////////////////////////////////////////////////////////////// - // - // Performs the arithmetic operation and uses DN_CHECK on the operation to - // check if it overflows. If it overflows the MAX value of the integer is - // returned in add and multiply operations, and, the minimum is returned in - // subtraction and division. - - // NOTE: DN_Safe_SaturateCast* //////////////////////////////////////////////////////////////// - // - // Truncate the passed in value to the return type clamping the resulting - // value to the max value of the desired data type. It DN_CHECK's the - // truncation. - // - // The following sentinel values are returned when saturated, - // USize -> Int: INT_MAX - // USize -> I8: INT8_MAX - // USize -> I16: INT16_MAX - // USize -> I32: INT32_MAX - // USize -> I64: INT64_MAX - // - // U64 -> UInt: UINT_MAX - // U64 -> U8: UINT8_MAX - // U64 -> U16: UINT16_MAX - // U64 -> U32: UINT32_MAX - // - // USize -> U8: UINT8_MAX - // USize -> U16: UINT16_MAX - // USize -> U32: UINT32_MAX - // USize -> U64: UINT64_MAX - // - // ISize -> Int: INT_MIN or INT_MAX - // ISize -> I8: INT8_MIN or INT8_MAX - // ISize -> I16: INT16_MIN or INT16_MAX - // ISize -> I32: INT32_MIN or INT32_MAX - // ISize -> I64: INT64_MIN or INT64_MAX - // - // ISize -> UInt: 0 or UINT_MAX - // ISize -> U8: 0 or UINT8_MAX - // ISize -> U16: 0 or UINT16_MAX - // ISize -> U32: 0 or UINT32_MAX - // ISize -> U64: 0 or UINT64_MAX - // - // I64 -> ISize: DN_ISIZE_MIN or DN_ISIZE_MAX - // I64 -> I8: INT8_MIN or INT8_MAX - // I64 -> I16: INT16_MIN or INT16_MAX - // I64 -> I32: INT32_MIN or INT32_MAX - // - // Int -> I8: INT8_MIN or INT8_MAX - // Int -> I16: INT16_MIN or INT16_MAX - // Int -> U8: 0 or UINT8_MAX - // Int -> U16: 0 or UINT16_MAX - // Int -> U32: 0 or UINT32_MAX - // Int -> U64: 0 or UINT64_MAX - - // NOTE: DN_StackTrace //////////////////////////////////////////////////////////////////////// - // Emit stack traces at the calling site that these functions are invoked - // from. - // - // For some applications, it may be viable to generate raw stack traces and - // store just the base addresses of the call stack from the 'Walk' - // functions. This reduces the memory overhead and required to hold onto - // stack traces and resolve the addresses on-demand when required. - // - // However if your application is loading and/or unloading shared libraries, - // on Windows it may be impossible for the application to resolve raw base - // addresses if they become invalid over time. In these applications you - // must convert the raw stack traces before the unloading occurs, and when - // loading new shared libraries, 'ReloadSymbols' must be called to ensure - // the debug APIs are aware of how to resolve the new addresses imported - // into the address space. - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - - // NOTE: DN_StackTrace_Walk /////////////////////////////////////////////////////////////// - // - // Generate a stack trace as a series of addresses to the base of the - // functions on the call-stack at the current instruction pointer. The - // addresses are stored in order from the current executing function - // first to the most ancestor function last in the walk. - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, /*depth limit*/ 128); - - // Loop over the addresses produced in the stack trace - for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { - - // NOTE: DN_StackTrace_RawFrameToFrame //////////////////////////////////////////////// - // - // Converts the base address into a human readable stack trace - // entry (e.g. address, line number, file and function name). - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(tmem.arena, it.raw_frame); - - // You may then print out the frame like so - if (0) - printf("%.*s(%" PRIu64 "): %.*s\n", DN_STR_FMT(frame.file_name), frame.line_number, DN_STR_FMT(frame.function_name)); - } - - // If you load new shared-libraries into the address space it maybe - // necessary to call into 'ReloadSymbols' to ensure that the OS is able - // to resolve the new addresses. - DN_StackTrace_ReloadSymbols(); - - // NOTE: DN_StackTrace_GetFrames ////////////////////////////////////////////////////////// - // - // Helper function to create a stack trace and automatically convert the - // raw frames into human readable frames. This function effectively - // calls 'Walk' followed by 'RawFrameToFrame'. - DN_Slice frames = DN_StackTrace_GetFrames(tmem.arena, /*depth limit*/ 128); - (void)frames; - } - - // NOTE: DN_Str8_Alloc //////////////////////////////////////////////////////////////////////// - // - // Allocates a string with the requested 'size'. An additional byte is - // always requested from the allocator to null-terminate the buffer. This - // allows the string to be used with C-style string APIs. - // - // The returned string's 'size' member variable does *not* include this - // additional null-terminating byte. - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_Str8_Alloc(tmem.arena, /*size*/ 1, DN_ZeroMem_Yes); - DN_ASSERT(string.size == 1); - DN_ASSERT(string.data[string.size] == 0); // It is null-terminated! - } - - // NOTE: DN_Str8_BinarySplit ////////////////////////////////////////////////////////////////// - // - // Splits a string into 2 substrings occuring prior and after the first - // occurence of the delimiter. Neither strings include the matched - // delimiter. If no delimiter is found, the 'rhs' of the split will be - // empty. - { - DN_Str8BinarySplitResult dot_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8(".")); - DN_Str8BinarySplitResult slash_split = DN_Str8_BinarySplit(/*string*/ DN_STR8("abc.def.ghi"), /*delimiter*/ DN_STR8("/")); - DN_ASSERT(dot_split.lhs == DN_STR8("abc") && dot_split.rhs == DN_STR8("def.ghi")); - DN_ASSERT(slash_split.lhs == DN_STR8("abc.def.ghi") && slash_split.rhs == DN_STR8("")); - - // Loop that walks the string and produces ("abc", "def", "ghi") - for (DN_Str8 it = DN_STR8("abc.def.ghi"); it.size; ) { - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(it, DN_STR8(".")); - DN_Str8 chunk = split.lhs; // "abc", "def", ... - it = split.rhs; - (void)chunk; - } - } - - // NOTE: DN_Str8_FileNameFromPath ///////////////////////////////////////////////////////////// - // - // Takes a slice to the file name from a file path. The file name is - // evaluated by searching from the end of the string backwards to the first - // occurring path separator '/' or '\'. If no path separator is found, the - // original string is returned. This function preserves the file extension - // if there were any. - { - { - DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/item.txt")); - DN_ASSERT(string == DN_STR8("item.txt")); - } - { - // TODO(doyle): Intuitively this seems incorrect. Empty string instead? - DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder/")); - DN_ASSERT(string == DN_STR8("C:/Folder")); - } - { - DN_Str8 string = DN_Str8_FileNameFromPath(DN_STR8("C:/Folder")); - DN_ASSERT(string == DN_STR8("Folder")); - } - } - - // NOTE: DN_Str8_FilePathNoExtension ////////////////////////////////////////////////////////// - // - // This function preserves the original string if no extension was found. - // An extension is defined as the substring after the last '.' encountered - // in the string. - { - DN_Str8 string = DN_Str8_FilePathNoExtension(DN_STR8("C:/Folder/item.txt.bak")); - DN_ASSERT(string == DN_STR8("C:/Folder/item.txt")); - } - - // NOTE: DN_Str8_FileNameNoExtension ////////////////////////////////////////////////////////// - // - // This function is the same as calling 'FileNameFromPath' followed by - // 'FilePathNoExtension' - { - DN_Str8 string = DN_Str8_FileNameNoExtension(DN_STR8("C:/Folder/item.txt.bak")); - DN_ASSERT(string == DN_STR8("item.txt")); - } - - // NOTE: DN_Str8_Replace /////////////////////////////////////////////////////////// - // NOTE: DN_Str8_ReplaceInsensitive /////////////////////////////////////////////////////////// - // - // Replace any matching substring 'find' with 'replace' in the passed in - // 'string'. The 'start_index' may be specified to offset which index the - // string will start doing replacements from. - // - // String replacements are not done inline and the returned string will - // always be a newly allocated copy, irrespective of if any replacements - // were done or not. - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_Str8_Replace(/*string*/ DN_STR8("Foo Foo Bar"), - /*find*/ DN_STR8("Foo"), - /*replace*/ DN_STR8("Moo"), - /*start_index*/ 1, - /*arena*/ tmem.arena, - /*eq_case*/ DN_Str8EqCase_Sensitive); - DN_ASSERT(string == DN_STR8("Foo Moo Bar")); - } - - // NOTE: DN_Str8_Segment ////////////////////////////////////////////////////////////////////// - // - // Add a delimiting 'segment_char' every 'segment_size' number of characters - // in the string. - // - // Reverse segment delimits the string counting 'segment_size' from the back - // of the string. - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_Str8_Segment(tmem.arena, /*string*/ DN_STR8("123456789"), /*segment_size*/ 3, /*segment_char*/ ','); - DN_ASSERT(string == DN_STR8("123,456,789")); - } - - // NOTE: DN_Str8_Split //////////////////////////////////////////////////////////////////////// - { - // Splits the string at each delimiter into substrings occuring prior and - // after until the next delimiter. - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - { - DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, - /*string*/ DN_STR8("192.168.8.1"), - /*delimiter*/ DN_STR8("."), - /*mode*/ DN_Str8SplitIncludeEmptyStrings_No); - DN_ASSERT(splits.size == 4); - DN_ASSERT(splits.data[0] == DN_STR8("192") && splits.data[1] == DN_STR8("168") && splits.data[2] == DN_STR8("8") && splits.data[3] == DN_STR8("1")); - } - - // You can include empty strings that occur when splitting by setting - // the split mode to include empty strings. - { - DN_Slice splits = DN_Str8_SplitAlloc(/*arena*/ tmem.arena, - /*string*/ DN_STR8("a--b"), - /*delimiter*/ DN_STR8("-"), - /*mode*/ DN_Str8SplitIncludeEmptyStrings_Yes); - DN_ASSERT(splits.size == 3); - DN_ASSERT(splits.data[0] == DN_STR8("a") && splits.data[1] == DN_STR8("") && splits.data[2] == DN_STR8("b")); - } - } - - // NOTE: DN_Str8_ToI64 //////////////////////////////////////////////////////////////////////// - // NOTE: DN_Str8_ToU64 //////////////////////////////////////////////////////////////////////// - // - // Convert a number represented as a string to a signed 64 bit number. - // - // The 'separator' is an optional digit separator for example, if - // 'separator' is set to ',' then '1,234' will successfully be parsed to - // '1234'. If no separator is desired, you may pass in '0' in which - // '1,234' will *not* be succesfully parsed. - // - // Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, - // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of - // digits, the seperator or the permitted prefixes as previously mentioned - // otherwise this function will return false, i.e. "1234 dog" will cause the - // function to return false, however, the output is greedily converted and - // will be evaluated to "1234". - // - // 'ToU64' only '+' prefix is permitted - // 'ToI64' either '+' or '-' prefix is permitted - { - { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ ','); - DN_ASSERT(result.success && result.value == -1234); - } - { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1,234"), /*separator*/ 0); - DN_ASSERT(!result.success && result.value == 1); // 1 because it's a greedy conversion - } - } - - // NOTE: DN_Str8_TrimByteOrderMark //////////////////////////////////////////////////////////// - // - // Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the - // string if it's present. - - // NOTE: DN_STR_FMT /////////////////////////////////////////////////////////////////////////// - // - // Unpacks a string struct that has the fields {.data, .size} for printing a - // pointer and length style string using the printf format specifier "%.*s" - // - // printf("%.*s\n", DN_STR_FMT(DN_STR8("Hello world"))); - - // NOTE: DN_Str8Builder_AppendF //////////////////////////////////////////////////////////// - // NOTE: DN_Str8Builder_AppendFV //////////////////////////////////////////////////////////// - // NOTE: DN_Str8Builder_AppendRef //////////////////////////////////////////////////////////// - // NOTE: DN_Str8Builder_AppendCopy //////////////////////////////////////////////////////////// - // - // - Appends a string to the string builder as follows - // - // AppendRef: Stores the string slice by value - // AppendCopy: Stores the string slice by copy (with builder's arena) - // AppendF/V: Constructs a format string and calls 'AppendRef' - - // NOTE: DN_Str8Builder_Build /////////////////////////////////////////////////////////// - // NOTE: DN_Str8Builder_BuildCRT /////////////////////////////////////////////////////////// - // - // Constructs the final string by merging all the appended strings into - // one merged string. - // - // The CRT variant calls into 'malloc' and the string *must* be released - // using 'free'. - - // NOTE: DN_Str8Builder_BuildSlice /////////////////////////////////////////////////////////// - // - // Constructs the final string into an array of strings (e.g. a slice) - - // NOTE: DN_TicketMutex /////////////////////////////////////////////////////////////////////// - // - // A mutex implemented using an atomic compare and swap on tickets handed - // out for each critical section. - // - // This mutex serves ticket in order and will block all other threads until - // the tickets are returned in order. The thread with the oldest ticket that - // has not been returned has right of way to execute, all other threads will - // be blocked in an atomic compare and swap loop. block execution by going - // into an atomic - // - // When a thread is blocked by this mutex, a spinlock intrinsic '_mm_pause' is - // used to yield the CPU and reduce spinlock on the thread. This mutex is not - // ideal for long blocking operations. This mutex does not issue any syscalls - // and relies entirely on atomic instructions. - { - DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); // Simple procedural mutual exclusion lock - DN_TicketMutex_End(&mutex); - - // NOTE: DN_TicketMutex_MakeTicket //////////////////////////////////////////////////////// - // - // Request the next available ticket for locking from the mutex. - DN_UInt ticket = DN_TicketMutex_MakeTicket(&mutex); - - if (DN_TicketMutex_CanLock(&mutex, ticket)) { - // NOTE: DN_TicketMutex_BeginTicket /////////////////////////////////////////////////// - // - // Locks the mutex using the given ticket if possible. If it's not - // the next ticket to be locked the executing thread will block - // until the mutex can lock the ticket, i.e. All prior tickets are - // returned, in sequence, to the mutex. - DN_TicketMutex_BeginTicket(&mutex, ticket); - DN_TicketMutex_End(&mutex); - } - } - - // NOTE: DN_ThreadContext ///////////////////////////////////////////////////////////////////// - // - // Each thread is assigned in their thread-local storage (TLS) tmem and - // permanent arena allocators. These can be used for allocations with a - // lifetime scoped to the lexical scope or for storing data permanently - // using the arena paradigm. - // - // TLS in this implementation is implemented using the `thread_local` C/C++ - // keyword. - // - // 99% of the time you will want DN_TLS_TMem(...) which returns you a - // temporary arena for function lifetime allocations. On scope exit, the - // arena is cleared out. - // - // This library's paradigm revolves heavily around arenas including tmem - // arenas into child functions for temporary calculations. If an arena is - // passed into a function, this poses a problem sometimes known as - // 'arena aliasing'. - // - // If an arena aliases another arena (e.g. the arena passed in) is the same - // as the tmem arena requested in the function, we risk the tmem arena - // on scope exit deallocating memory belonging to the caller. - // - // To avoid this we the 'DN_TLS_TMem(...)' API takes in a list of arenas - // to ensure that we provide a tmem arena that *won't* alias with the - // caller's arena. If arena aliasing occurs, with ASAN on, generally - // the library will trap and report use-after-poison once violated. - { - DN_TLSTMem tmem_a = DN_TLS_TMem(nullptr); - - // Now imagine we call a function where we pass tmem_a.arena down - // into it .. If we call tmem again, we need to pass in the arena - // to prevent aliasing. - DN_TLSTMem tmem_b = DN_TLS_TMem(tmem_a.arena); - DN_ASSERT(tmem_a.arena != tmem_b.arena); - } - - // @proc DN_Thread_GetTMem - // @desc Retrieve the per-thread temporary arena allocator that is reset on scope - // exit. - - // The tmem arena must be deconflicted with any existing arenas in the - // function to avoid trampling over each other's memory. Consider the situation - // where the tmem arena is passed into the function. Inside the function, if - // the same arena is reused then, if both arenas allocate, when the inner arena - // is reset, this will undo the passed in arena's allocations in the function. - - // @param[in] conflict_arena A pointer to the arena currently being used in the - // function - - // NOTE: DN_U64ToStr8 ///////////////////////////////////////////////////////////////////////// - { - DN_U64Str8 string = DN_U64ToStr8(123123, ','); - if (0) // Prints "123,123" - printf("%.*s", DN_STR_FMT(string)); - } - - // NOTE: DN_U64ToAge ////////////////////////////////////////////////////////////////////////// - { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_U64ToAge(tmem.arena, DN_HOURS_TO_S(2) + DN_MINS_TO_S(30), DN_U64AgeUnit_All); - if (0) // Prints "2hr 30m" - printf("%.*s", DN_STR_FMT(string)); - } - - // NOTE: DN_VArray //////////////////////////////////////////////////////////////////////////// - // - // An array that is backed by virtual memory by reserving addressing space - // and comitting pages as items are allocated in the array. This array never - // reallocs, instead you should reserve the upper bound of the memory you - // will possibly ever need (e.g. 16GB) and let the array commit physical - // pages on demand. - // - // On 64 bit operating systems you are given 48 bits of addressable space - // giving you 256 TB of reservable memory. This gives you practically - // an unlimited array capacity that avoids reallocs and only consumes memory - // that is actually occupied by the array. - // - // Each page that is committed into the array will be at page/allocation - // granularity which are always cache aligned. This array essentially retains - // all the benefits of normal arrays, - // - // - contiguous memory - // - O(1) random access - // - O(N) iterate - // - // In addition to no realloc on expansion or shrinking. - // - { - // NOTE: DN_VArray_Init /////////////////////////////////////////////////////////// - // NOTE: DN_VArray_InitByteSize /////////////////////////////////////////////////////////// - // - // Initialise an array with the requested byte size or item capacity - // respectively. The returned array may have a higher capacity than the - // requested amount since requested memory from the OS may have a certain - // alignment requirement (e.g. on Windows reserve/commit are 64k/4k - // aligned). - DN_VArray array = DN_VArray_Init(1024); - DN_ASSERT(array.size == 0 && array.max >= 1024); - - // NOTE: DN_VArray_Make ////////////////////////////////////////////////////////////// - // NOTE: DN_VArray_Add ////////////////////////////////////////////////////////////// - // NOTE: DN_VArray_MakeArray ////////////////////////////////////////////////////////////// - // NOTE: DN_VArray_AddArray ////////////////////////////////////////////////////////////// - // - // Allocate items from the array where: - // - // Make: creates a zero-init item from the array - // Add: creates a zero-init item and memcpy passed in data into the item - // - // If the array has run out of capacity or was never initialised, a null - // pointer is returned. - int *item = DN_VArray_Add(&array, 0xCAFE); - DN_ASSERT(*item == 0xCAFE && array.size == 1); - - // NOTE: DN_VArray_AddCArray ///////////////////////////////////////////////////////////// - DN_VArray_AddCArray(&array, {1, 2, 3}); - DN_ASSERT(array.size == 4); - - // TODO(doyle): There's a bug here with the negative erase! - // Loop over the array items and erase 1 item. - #if 0 - for (DN_USize index = 0; index < array.size; index++) { - if (index != 1) - continue; - - // NOTE: DN_VArray_EraseRange ///////////////////////////////////////////////////////// - // - // Erase the next 'count' items at 'begin_index' in the array. - // 'count' can be positive or negative which dictates the if we - // erase forward from the 'begin_index' or in reverse. - // - // This operation will invalidate all pointers to the array! - // - // A stable erase will shift all elements after the erase ranged - // into the range preserving the order of prior elements. Unstable - // erase will move the tail elements into the range being erased. - // - // Erase range returns a result that contains the next iterator - // index that can be used to update the your for loop index if you - // are trying to iterate over the array. - - // TODO(doyle): There's a bug here! This doesn't work. - // Erase index 0 with the negative count! - DN_ArrayEraseResult erase_result = DN_VArray_EraseRange(&array, - /*begin_index*/ index, - /*count*/ -1, - /*erase*/ DN_ArrayErase_Stable); - DN_ASSERT(erase_result.items_erased == 1); - - // Use the index returned to continue linearly iterating the array - index = erase_result.it_index; - DN_ASSERT(array.data[index + 1] == 2); // Next loop iteration will process item '2' - } - - DN_ASSERT(array.size == 3 && - array.data[0] == 1 && - array.data[1] == 2 && - array.data[2] == 3); - #endif - - // NOTE: DN_VArray_Reserve //////////////////////////////////////////////////////////////////// - // - // Ensure that the requested number of items are backed by physical pages - // from the OS. Calling this pre-emptively will minimise syscalls into the - // kernel to request memory. The requested items will be rounded up to the - // in bytes to the allocation granularity of OS allocation APIs hence the - // reserved space may be greater than the requested amount (e.g. this is 4k - // on Windows). - DN_VArray_Reserve(&array, /*count*/ 8); - - DN_VArray_Deinit(&array); - } - - // NOTE: DN_Win_LastError ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_ErrorCodeToMsg ///////////////////////////////////////////////////////////// - #if defined(DN_PLATFORM_WIN32) - if (0) { - // Generate the error string for the last Win32 API called that return - // an error value. - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_WinError get_last_error = DN_Win_LastError(tmem.arena); - printf("Error (%lu): %.*s", get_last_error.code, DN_STR_FMT(get_last_error.msg)); - - // Alternatively, pass in the error code directly - DN_WinError error_msg_for_code = DN_Win_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); - printf("Error (%lu): %.*s", error_msg_for_code.code, DN_STR_FMT(error_msg_for_code.msg)); - } - - // NOTE: DN_Win_MakeProcessDPIAware /////////////////////////////////////////////////////////// - // - // Call once at application start-up to ensure that the application is DPI - // aware on Windows and ensure that application UI is scaled up - // appropriately for the monitor. - - // NOTE: DN_Win_Str8ToStr16 ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str8ToStr16Buffer ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str16ToStr8 ///////////////////////////////////////////////////////////// - // NOTE: DN_Win_Str16ToStr8Buffer ///////////////////////////////////////////////////////////// - // - // Convert a UTF8 <-> UTF16 string. - // - // The exact size buffer required for this function can be determined by - // calling this function with the 'dest' set to null and 'dest_size' set to - // 0, the return size is the size required for conversion not-including - // space for the null-terminator. This function *always* null-terminates the - // input buffer. - // - // Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) - // written/required for conversion. 0 if there was a conversion error and can be - // queried using 'DN_Win_LastError' - #endif -} -DN_MSVC_WARNING_POP diff --git a/dqn_external.h b/dqn_external.h deleted file mode 100644 index d63f461..0000000 --- a/dqn_external.h +++ /dev/null @@ -1,293 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$\ -// $$ _____|$$ | $$ |\__$$ __|$$ _____|$$ __$$\ $$$\ $$ |$$ __$$\ $$ | -// $$ | \$$\ $$ | $$ | $$ | $$ | $$ |$$$$\ $$ |$$ / $$ |$$ | -// $$$$$\ \$$$$ / $$ | $$$$$\ $$$$$$$ |$$ $$\$$ |$$$$$$$$ |$$ | -// $$ __| $$ $$< $$ | $$ __| $$ __$$< $$ \$$$$ |$$ __$$ |$$ | -// $$ | $$ /\$$\ $$ | $$ | $$ | $$ |$$ |\$$$ |$$ | $$ |$$ | -// $$$$$$$$\ $$ / $$ | $$ | $$$$$$$$\ $$ | $$ |$$ | \$$ |$$ | $$ |$$$$$$$$\ -// \________|\__| \__| \__| \________|\__| \__|\__| \__|\__| \__|\________| -// -// dqn_external.h -- Third party dependencies -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$OS_H] OS Headers //////////////////////////////////////////////////////////////////////// -#if !defined(DN_OS_WIN32) || defined(DN_OS_WIN32_USE_PTHREADS) - #include - #include -#endif - -#if defined(DN_OS_UNIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include // errno - #include // O_RDONLY ... etc - #include // ioctl - #include // pid_t - #include // waitpid - #include // getrandom - #include // stat - #include // mmap - #include // clock_gettime, nanosleep - #include // access, gettid, write - - #if defined(DN_PLATFORM_EMSCRIPTEN) - #else - #include // sendfile - #include // FICLONE - #endif -#endif - -#if defined(DN_PLATFORM_EMSCRIPTEN) - #include // emscripten_fetch (for DN_OSHttpResponse) -#endif - -// NOTE: [$STBS] stb_sprintf /////////////////////////////////////////////////////////////////////// -#if defined(DN_USE_STD_PRINTF) - #include - #define DN_SPRINTF(...) sprintf(__VA_ARGS__) - #define DN_SNPRINTF(...) snprintf(__VA_ARGS__) - #define DN_VSPRINTF(...) vsprintf(__VA_ARGS__) - #define DN_VSNPRINTF(...) vsnprintf(__VA_ARGS__) -#else - #define DN_SPRINTF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) - #define DN_SNPRINTF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) - #define DN_VSPRINTF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) - #define DN_VSNPRINTF(...) STB_SPRINTF_DECORATE(vsnprintf)(__VA_ARGS__) - - #if (DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__)) && defined(DN_COMPILER_MSVC) - #error The STB implementation of sprintf triggers MSVCs implementation of ASAN. Compiling ASAN with STB sprintf is not supported. - - // NOTE: stb_sprintf assumes c-string literals are 4 byte aligned which is - // always true, however, reading past the end of a string whose size is not - // a multiple of 4 is UB causing ASAN to complain. This is practically safe - // and guaranteed by all compilers so we mute this. - // - // ==12072==ERROR: AddressSanitizer: global-buffer-overflow on address - // READ of size 4 at 0x7ff6f442a0d8 thread T0 - // #0 0x7ff6f42d3be8 in stbsp_vsprintfcb C:\Home\Code\dn\dn_external.cpp:199 - - #define STBSP__ASAN __declspec(no_sanitize_address) - #endif - -// stb_sprintf - v1.10 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// github:aganm (attribute format) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -//// -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -/////////////// -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -//////////// -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -/////// -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -/////////////////////////////////////////////////////////////////// -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) - #if defined(__has_feature) && defined(__has_attribute) - #if __has_feature(address_sanitizer) - #if __has_attribute(__no_sanitize__) - #define STBSP__ASAN __attribute__((__no_sanitize__("address"))) - #elif __has_attribute(__no_sanitize_address__) - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #elif __has_attribute(__no_address_safety_analysis__) - #define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) - #endif - #endif - #endif -#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) - #if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ - #define STBSP__ASAN __attribute__((__no_sanitize_address__)) - #endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#if defined(__has_attribute) - #if __has_attribute(format) - #define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) - #endif -#endif - -#ifndef STBSP__ATTRIBUTE_FORMAT -#define STBSP__ATTRIBUTE_FORMAT(fmt,va) -#endif - -#ifdef _MSC_VER -#define STBSP__NOTUSED(v) (void)(v) -#else -#define STBSP__NOTUSED(v) (void)sizeof(v) -#endif - -#include // for va_arg(), va_list() -#include // size_t, ptrdiff_t - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); - -STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); -#endif // STB_SPRINTF_H_INCLUDE -#endif // !defined(DN_USE_STD_PRINTF) diff --git a/dqn_helpers.cpp b/dqn_helpers.cpp deleted file mode 100644 index f6ce45d..0000000 --- a/dqn_helpers.cpp +++ /dev/null @@ -1,1530 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ -// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ -// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| -// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ -// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ -// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | -// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | -// \__| \__|\________|\________|\__| \________|\__| \__| \______/ -// -// dqn_helpers.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$PCGX] DN_PCG32 ///////////////////////////////////////////////////////////////////////// -#define DN_PCG_DEFAULT_MULTIPLIER_64 6364136223846793005ULL -#define DN_PCG_DEFAULT_INCREMENT_64 1442695040888963407ULL - -DN_API DN_PCG32 DN_PCG32_Init(uint64_t seed) -{ - DN_PCG32 result = {}; - DN_PCG32_Next(&result); - result.state += seed; - DN_PCG32_Next(&result); - return result; -} - -DN_API uint32_t DN_PCG32_Next(DN_PCG32 *rng) -{ - uint64_t state = rng->state; - rng->state = state * DN_PCG_DEFAULT_MULTIPLIER_64 + DN_PCG_DEFAULT_INCREMENT_64; - - // XSH-RR - uint32_t value = (uint32_t)((state ^ (state >> 18)) >> 27); - int rot = state >> 59; - return rot ? (value >> rot) | (value << (32 - rot)) : value; -} - -DN_API uint64_t DN_PCG32_Next64(DN_PCG32 *rng) -{ - uint64_t value = DN_PCG32_Next(rng); - value <<= 32; - value |= DN_PCG32_Next(rng); - return value; -} - -DN_API uint32_t DN_PCG32_Range(DN_PCG32 *rng, uint32_t low, uint32_t high) -{ - uint32_t bound = high - low; - uint32_t threshold = -(int32_t)bound % bound; - - for (;;) { - uint32_t r = DN_PCG32_Next(rng); - if (r >= threshold) - return low + (r % bound); - } -} - -DN_API float DN_PCG32_NextF32(DN_PCG32 *rng) -{ - uint32_t x = DN_PCG32_Next(rng); - return (float)(int32_t)(x >> 8) * 0x1.0p-24f; -} - -DN_API double DN_PCG32_NextF64(DN_PCG32 *rng) -{ - uint64_t x = DN_PCG32_Next64(rng); - return (double)(int64_t)(x >> 11) * 0x1.0p-53; -} - -DN_API void DN_PCG32_Advance(DN_PCG32 *rng, uint64_t delta) -{ - uint64_t cur_mult = DN_PCG_DEFAULT_MULTIPLIER_64; - uint64_t cur_plus = DN_PCG_DEFAULT_INCREMENT_64; - - uint64_t acc_mult = 1; - uint64_t acc_plus = 0; - - while (delta != 0) { - if (delta & 1) { - acc_mult *= cur_mult; - acc_plus = acc_plus * cur_mult + cur_plus; - } - cur_plus = (cur_mult + 1) * cur_plus; - cur_mult *= cur_mult; - delta >>= 1; - } - - rng->state = acc_mult * rng->state + acc_plus; -} - -#if !defined(DN_NO_JSON_BUILDER) -// NOTE: [$JSON] DN_JSONBuilder /////////////////////////////////////////////////////////////////// -DN_API DN_JSONBuilder DN_JSONBuilder_Init(DN_Arena *arena, int spaces_per_indent) -{ - DN_JSONBuilder result = {}; - result.spaces_per_indent = spaces_per_indent; - result.string_builder.arena = arena; - return result; -} - -DN_API DN_Str8 DN_JSONBuilder_Build(DN_JSONBuilder const *builder, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8Builder_Build(&builder->string_builder, arena); - return result; -} - -DN_API void DN_JSONBuilder_KeyValue(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) -{ - if (key.size == 0 && value.size == 0) - return; - - DN_JSONBuilderItem item = DN_JSONBuilderItem_KeyValue; - if (value.size >= 1) { - if (value.data[0] == '{' || value.data[0] == '[') - item = DN_JSONBuilderItem_OpenContainer; - else if (value.data[0] == '}' || value.data[0] == ']') - item = DN_JSONBuilderItem_CloseContainer; - } - - bool adding_to_container_with_items = - item != DN_JSONBuilderItem_CloseContainer && (builder->last_item == DN_JSONBuilderItem_KeyValue || - builder->last_item == DN_JSONBuilderItem_CloseContainer); - - uint8_t prefix_size = 0; - char prefix[2] = {0}; - if (adding_to_container_with_items) - prefix[prefix_size++] = ','; - - if (builder->last_item != DN_JSONBuilderItem_Empty) - prefix[prefix_size++] = '\n'; - - if (item == DN_JSONBuilderItem_CloseContainer) - builder->indent_level--; - - int spaces_per_indent = builder->spaces_per_indent ? builder->spaces_per_indent : 2; - int spaces = builder->indent_level * spaces_per_indent; - - if (key.size) { - DN_Str8Builder_AppendF(&builder->string_builder, - "%.*s%*c\"%.*s\": %.*s", - prefix_size, - prefix, - spaces, - ' ', - DN_STR_FMT(key), - DN_STR_FMT(value)); - } else { - if (spaces == 0) - DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%.*s", prefix_size, prefix, DN_STR_FMT(value)); - else - DN_Str8Builder_AppendF(&builder->string_builder, "%.*s%*c%.*s", prefix_size, prefix, spaces, ' ', DN_STR_FMT(value)); - } - - if (item == DN_JSONBuilderItem_OpenContainer) - builder->indent_level++; - - builder->last_item = item; -} - -DN_API void DN_JSONBuilder_KeyValueFV(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, va_list args) -{ - DN_TLSTMem tmem = DN_TLS_TMem(builder->string_builder.arena); - DN_Str8 value = DN_Str8_InitFV(tmem.arena, value_fmt, args); - DN_JSONBuilder_KeyValue(builder, key, value); -} - -DN_API void DN_JSONBuilder_KeyValueF(DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...) -{ - va_list args; - va_start(args, value_fmt); - DN_JSONBuilder_KeyValueFV(builder, key, value_fmt, args); - va_end(args); -} - -DN_API void DN_JSONBuilder_ObjectBeginNamed(DN_JSONBuilder *builder, DN_Str8 name) -{ - DN_JSONBuilder_KeyValue(builder, name, DN_STR8("{")); -} - -DN_API void DN_JSONBuilder_ObjectEnd(DN_JSONBuilder *builder) -{ - DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("}")); -} - -DN_API void DN_JSONBuilder_ArrayBeginNamed(DN_JSONBuilder *builder, DN_Str8 name) -{ - DN_JSONBuilder_KeyValue(builder, name, DN_STR8("[")); -} - -DN_API void DN_JSONBuilder_ArrayEnd(DN_JSONBuilder *builder) -{ - DN_JSONBuilder_KeyValue(builder, DN_STR8(""), DN_STR8("]")); -} - -DN_API void DN_JSONBuilder_Str8Named(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) -{ - DN_JSONBuilder_KeyValueF(builder, key, "\"%.*s\"", value.size, value.data); -} - -DN_API void DN_JSONBuilder_LiteralNamed(DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value) -{ - DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value.size, value.data); -} - -DN_API void DN_JSONBuilder_U64Named(DN_JSONBuilder *builder, DN_Str8 key, uint64_t value) -{ - DN_JSONBuilder_KeyValueF(builder, key, "%I64u", value); -} - -DN_API void DN_JSONBuilder_I64Named(DN_JSONBuilder *builder, DN_Str8 key, int64_t value) -{ - DN_JSONBuilder_KeyValueF(builder, key, "%I64d", value); -} - -DN_API void DN_JSONBuilder_F64Named(DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places) -{ - if (!builder) - return; - - if (decimal_places >= 16) - decimal_places = 16; - - // NOTE: Generate the format string for the float, depending on how many - // decimals places it wants. - char float_fmt[16]; - if (decimal_places > 0) { - // NOTE: Emit the format string "%.f" i.e. %.1f - DN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%.%df", decimal_places); - } else { - // NOTE: Emit the format string "%f" - DN_SNPRINTF(float_fmt, sizeof(float_fmt), "%%f"); - } - DN_JSONBuilder_KeyValueF(builder, key, float_fmt, value); -} - -DN_API void DN_JSONBuilder_BoolNamed(DN_JSONBuilder *builder, DN_Str8 key, bool value) -{ - DN_Str8 value_string = value ? DN_STR8("true") : DN_STR8("false"); - DN_JSONBuilder_KeyValueF(builder, key, "%.*s", value_string.size, value_string.data); -} -#endif // !defined(DN_NO_JSON_BUILDER) - -// NOTE: [$BITS] DN_Bit /////////////////////////////////////////////////////////////////////////// -DN_API void DN_Bit_UnsetInplace(DN_USize *flags, DN_USize bitfield) -{ - *flags = (*flags & ~bitfield); -} - -DN_API void DN_Bit_SetInplace(DN_USize *flags, DN_USize bitfield) -{ - *flags = (*flags | bitfield); -} - -DN_API bool DN_Bit_IsSet(DN_USize bits, DN_USize bits_to_set) -{ - auto result = DN_CAST(bool)((bits & bits_to_set) == bits_to_set); - return result; -} - -DN_API bool DN_Bit_IsNotSet(DN_USize bits, DN_USize bits_to_check) -{ - auto result = !DN_Bit_IsSet(bits, bits_to_check); - return result; -} - -// NOTE: [$SAFE] DN_Safe ////////////////////////////////////////////////////////////////////////// -DN_API int64_t DN_Safe_AddI64(int64_t a, int64_t b) -{ - int64_t result = DN_CHECKF(a <= INT64_MAX - b, "a=%zd, b=%zd", a, b) ? (a + b) : INT64_MAX; - return result; -} - -DN_API int64_t DN_Safe_MulI64(int64_t a, int64_t b) -{ - int64_t result = DN_CHECKF(a <= INT64_MAX / b, "a=%zd, b=%zd", a, b) ? (a * b) : INT64_MAX; - return result; -} - -DN_API uint64_t DN_Safe_AddU64(uint64_t a, uint64_t b) -{ - uint64_t result = DN_CHECKF(a <= UINT64_MAX - b, "a=%zu, b=%zu", a, b) ? (a + b) : UINT64_MAX; - return result; -} - -DN_API uint64_t DN_Safe_SubU64(uint64_t a, uint64_t b) -{ - uint64_t result = DN_CHECKF(a >= b, "a=%zu, b=%zu", a, b) ? (a - b) : 0; - return result; -} - -DN_API uint64_t DN_Safe_MulU64(uint64_t a, uint64_t b) -{ - uint64_t result = DN_CHECKF(a <= UINT64_MAX / b, "a=%zu, b=%zu", a, b) ? (a * b) : UINT64_MAX; - return result; -} - -DN_API uint32_t DN_Safe_SubU32(uint32_t a, uint32_t b) -{ - uint32_t result = DN_CHECKF(a >= b, "a=%u, b=%u", a, b) ? (a - b) : 0; - return result; -} - -// NOTE: DN_Safe_SaturateCastUSizeToI* //////////////////////////////////////////////////////////// -// INT*_MAX literals will be promoted to the type of uintmax_t as uintmax_t is -// the highest possible rank (unsigned > signed). -DN_API int DN_Safe_SaturateCastUSizeToInt(DN_USize val) -{ - int result = DN_CHECK(DN_CAST(uintmax_t) val <= INT_MAX) ? DN_CAST(int) val : INT_MAX; - return result; -} - -DN_API int8_t DN_Safe_SaturateCastUSizeToI8(DN_USize val) -{ - int8_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT8_MAX) ? DN_CAST(int8_t) val : INT8_MAX; - return result; -} - -DN_API int16_t DN_Safe_SaturateCastUSizeToI16(DN_USize val) -{ - int16_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT16_MAX) ? DN_CAST(int16_t) val : INT16_MAX; - return result; -} - -DN_API int32_t DN_Safe_SaturateCastUSizeToI32(DN_USize val) -{ - int32_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT32_MAX) ? DN_CAST(int32_t) val : INT32_MAX; - return result; -} - -DN_API int64_t DN_Safe_SaturateCastUSizeToI64(DN_USize val) -{ - int64_t result = DN_CHECK(DN_CAST(uintmax_t) val <= INT64_MAX) ? DN_CAST(int64_t) val : INT64_MAX; - return result; -} - -// NOTE: DN_Safe_SaturateCastUSizeToU* //////////////////////////////////////////////////////////// -// Both operands are unsigned and the lowest rank operand will be promoted to -// match the highest rank operand. -DN_API uint8_t DN_Safe_SaturateCastUSizeToU8(DN_USize val) -{ - uint8_t result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(uint8_t) val : UINT8_MAX; - return result; -} - -DN_API uint16_t DN_Safe_SaturateCastUSizeToU16(DN_USize val) -{ - uint16_t result = DN_CHECK(val <= UINT16_MAX) ? DN_CAST(uint16_t) val : UINT16_MAX; - return result; -} - -DN_API uint32_t DN_Safe_SaturateCastUSizeToU32(DN_USize val) -{ - uint32_t result = DN_CHECK(val <= UINT32_MAX) ? DN_CAST(uint32_t) val : UINT32_MAX; - return result; -} - -DN_API uint64_t DN_Safe_SaturateCastUSizeToU64(DN_USize val) -{ - uint64_t result = DN_CHECK(DN_CAST(uint64_t) val <= UINT64_MAX) ? DN_CAST(uint64_t) val : UINT64_MAX; - return result; -} - -// NOTE: DN_Safe_SaturateCastU64To* /////////////////////////////////////////////////////////////// -DN_API int DN_Safe_SaturateCastU64ToInt(uint64_t val) -{ - int result = DN_CHECK(val <= INT_MAX) ? DN_CAST(int)val : INT_MAX; - return result; -} - -DN_API int8_t DN_Safe_SaturateCastU64ToI8(uint64_t val) -{ - int8_t result = DN_CHECK(val <= INT8_MAX) ? DN_CAST(int8_t)val : INT8_MAX; - return result; -} - -DN_API int16_t DN_Safe_SaturateCastU64ToI16(uint64_t val) -{ - int16_t result = DN_CHECK(val <= INT16_MAX) ? DN_CAST(int16_t)val : INT16_MAX; - return result; -} - -DN_API int32_t DN_Safe_SaturateCastU64ToI32(uint64_t val) -{ - int32_t result = DN_CHECK(val <= INT32_MAX) ? DN_CAST(int32_t)val : INT32_MAX; - return result; -} - -DN_API int64_t DN_Safe_SaturateCastU64ToI64(uint64_t val) -{ - int64_t result = DN_CHECK(val <= INT64_MAX) ? DN_CAST(int64_t)val : INT64_MAX; - return result; -} - -// Both operands are unsigned and the lowest rank operand will be promoted to -// match the highest rank operand. -DN_API unsigned int DN_Safe_SaturateCastU64ToUInt(uint64_t val) -{ - unsigned int result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(unsigned int) val : UINT_MAX; - return result; -} - -DN_API uint8_t DN_Safe_SaturateCastU64ToU8(uint64_t val) -{ - uint8_t result = DN_CHECK(val <= UINT8_MAX) ? DN_CAST(uint8_t) val : UINT8_MAX; - return result; -} - -DN_API uint16_t DN_Safe_SaturateCastU64ToU16(uint64_t val) -{ - uint16_t result = DN_CHECK(val <= UINT16_MAX) ? DN_CAST(uint16_t) val : UINT16_MAX; - return result; -} - -DN_API uint32_t DN_Safe_SaturateCastU64ToU32(uint64_t val) -{ - uint32_t result = DN_CHECK(val <= UINT32_MAX) ? DN_CAST(uint32_t) val : UINT32_MAX; - return result; -} - -// NOTE: DN_Safe_SaturateCastISizeToI* //////////////////////////////////////////////////////////// -// Both operands are signed so the lowest rank operand will be promoted to -// match the highest rank operand. -DN_API int DN_Safe_SaturateCastISizeToInt(DN_ISize val) -{ - DN_ASSERT(val >= INT_MIN && val <= INT_MAX); - int result = DN_CAST(int) DN_CLAMP(val, INT_MIN, INT_MAX); - return result; -} - -DN_API int8_t DN_Safe_SaturateCastISizeToI8(DN_ISize val) -{ - DN_ASSERT(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API int16_t DN_Safe_SaturateCastISizeToI16(DN_ISize val) -{ - DN_ASSERT(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API int32_t DN_Safe_SaturateCastISizeToI32(DN_ISize val) -{ - DN_ASSERT(val >= INT32_MIN && val <= INT32_MAX); - int32_t result = DN_CAST(int32_t) DN_CLAMP(val, INT32_MIN, INT32_MAX); - return result; -} - -DN_API int64_t DN_Safe_SaturateCastISizeToI64(DN_ISize val) -{ - DN_ASSERT(DN_CAST(int64_t) val >= INT64_MIN && DN_CAST(int64_t) val <= INT64_MAX); - int64_t result = DN_CAST(int64_t) DN_CLAMP(DN_CAST(int64_t) val, INT64_MIN, INT64_MAX); - return result; -} - -// NOTE: DN_Safe_SaturateCastISizeToU* //////////////////////////////////////////////////////////// -// If the value is a negative integer, we clamp to 0. Otherwise, we know that -// the value is >=0, we can upcast safely to bounds check against the maximum -// allowed value. -DN_API unsigned int DN_Safe_SaturateCastISizeToUInt(DN_ISize val) -{ - unsigned int result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT_MAX)) - result = DN_CAST(unsigned int) val; - else - result = UINT_MAX; - } - return result; -} - -DN_API uint8_t DN_Safe_SaturateCastISizeToU8(DN_ISize val) -{ - uint8_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX)) - result = DN_CAST(uint8_t) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API uint16_t DN_Safe_SaturateCastISizeToU16(DN_ISize val) -{ - uint16_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX)) - result = DN_CAST(uint16_t) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API uint32_t DN_Safe_SaturateCastISizeToU32(DN_ISize val) -{ - uint32_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT32_MAX)) - result = DN_CAST(uint32_t) val; - else - result = UINT32_MAX; - } - return result; -} - -DN_API uint64_t DN_Safe_SaturateCastISizeToU64(DN_ISize val) -{ - uint64_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT64_MAX)) - result = DN_CAST(uint64_t) val; - else - result = UINT64_MAX; - } - return result; -} - -// NOTE: DN_Safe_SaturateCastI64To* /////////////////////////////////////////////////////////////// -// Both operands are signed so the lowest rank operand will be promoted to -// match the highest rank operand. -DN_API DN_ISize DN_Safe_SaturateCastI64ToISize(int64_t val) -{ - DN_CHECK(val >= DN_ISIZE_MIN && val <= DN_ISIZE_MAX); - DN_ISize result = DN_CAST(int64_t) DN_CLAMP(val, DN_ISIZE_MIN, DN_ISIZE_MAX); - return result; -} - -DN_API int8_t DN_Safe_SaturateCastI64ToI8(int64_t val) -{ - DN_CHECK(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API int16_t DN_Safe_SaturateCastI64ToI16(int64_t val) -{ - DN_CHECK(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API int32_t DN_Safe_SaturateCastI64ToI32(int64_t val) -{ - DN_CHECK(val >= INT32_MIN && val <= INT32_MAX); - int32_t result = DN_CAST(int32_t) DN_CLAMP(val, INT32_MIN, INT32_MAX); - return result; -} - -DN_API unsigned int DN_Safe_SaturateCastI64ToUInt(int64_t val) -{ - unsigned int result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT_MAX)) - result = DN_CAST(unsigned int) val; - else - result = UINT_MAX; - } - return result; -} - -DN_API DN_ISize DN_Safe_SaturateCastI64ToUSize(int64_t val) -{ - DN_USize result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= DN_USIZE_MAX)) - result = DN_CAST(DN_USize) val; - else - result = DN_USIZE_MAX; - } - return result; -} - -DN_API uint8_t DN_Safe_SaturateCastI64ToU8(int64_t val) -{ - uint8_t result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX)) - result = DN_CAST(uint8_t) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API uint16_t DN_Safe_SaturateCastI64ToU16(int64_t val) -{ - uint16_t result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX)) - result = DN_CAST(uint16_t) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API uint32_t DN_Safe_SaturateCastI64ToU32(int64_t val) -{ - uint32_t result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT32_MAX)) - result = DN_CAST(uint32_t) val; - else - result = UINT32_MAX; - } - return result; -} - -DN_API uint64_t DN_Safe_SaturateCastI64ToU64(int64_t val) -{ - uint64_t result = 0; - if (DN_CHECK(val >= DN_CAST(int64_t) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT64_MAX)) - result = DN_CAST(uint64_t) val; - else - result = UINT64_MAX; - } - return result; -} - -// NOTE: DN_Safe_SaturateCastIntTo* /////////////////////////////////////////////////////////////// -DN_API int8_t DN_Safe_SaturateCastIntToI8(int val) -{ - DN_CHECK(val >= INT8_MIN && val <= INT8_MAX); - int8_t result = DN_CAST(int8_t) DN_CLAMP(val, INT8_MIN, INT8_MAX); - return result; -} - -DN_API int16_t DN_Safe_SaturateCastIntToI16(int val) -{ - DN_CHECK(val >= INT16_MIN && val <= INT16_MAX); - int16_t result = DN_CAST(int16_t) DN_CLAMP(val, INT16_MIN, INT16_MAX); - return result; -} - -DN_API uint8_t DN_Safe_SaturateCastIntToU8(int val) -{ - uint8_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT8_MAX)) - result = DN_CAST(uint8_t) val; - else - result = UINT8_MAX; - } - return result; -} - -DN_API uint16_t DN_Safe_SaturateCastIntToU16(int val) -{ - uint16_t result = 0; - if (DN_CHECK(val >= DN_CAST(DN_ISize) 0)) { - if (DN_CHECK(DN_CAST(uintmax_t) val <= UINT16_MAX)) - result = DN_CAST(uint16_t) val; - else - result = UINT16_MAX; - } - return result; -} - -DN_API uint32_t DN_Safe_SaturateCastIntToU32(int val) -{ - static_assert(sizeof(val) <= sizeof(uint32_t), "Sanity check to allow simplifying of casting"); - uint32_t result = 0; - if (DN_CHECK(val >= 0)) - result = DN_CAST(uint32_t) val; - return result; -} - -DN_API uint64_t DN_Safe_SaturateCastIntToU64(int val) -{ - static_assert(sizeof(val) <= sizeof(uint64_t), "Sanity check to allow simplifying of casting"); - uint64_t result = 0; - if (DN_CHECK(val >= 0)) - result = DN_CAST(uint64_t) val; - return result; -} - -// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// -DN_API int DN_FmtBuffer3DotTruncate(char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - int size_required = DN_VSNPRINTF(buffer, size, fmt, args); - int result = DN_MAX(DN_MIN(size_required, size - 1), 0); - if (result == size - 1) { - buffer[size - 2] = '.'; - buffer[size - 3] = '.'; - } - va_end(args); - return result; -} - -DN_API DN_U64Str8 DN_U64ToStr8(uint64_t val, char separator) -{ - DN_U64Str8 result = {}; - if (val == 0) { - result.data[result.size++] = '0'; - } else { - // NOTE: The number is written in reverse because we form the string by - // dividing by 10, so we write it in, then reverse it out after all is - // done. - DN_U64Str8 temp = {}; - for (DN_USize digit_count = 0; val > 0; digit_count++) { - if (separator && (digit_count != 0) && (digit_count % 3 == 0)) - temp.data[temp.size++] = separator; - - auto digit = DN_CAST(char)(val % 10); - temp.data[temp.size++] = '0' + digit; - val /= 10; - } - - // NOTE: Reverse the string - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6293) // Ill-defined for-loop - DN_MSVC_WARNING_DISABLE( - 6385) // Reading invalid data from 'temp.data' NOTE(doyle): Unsigned overflow is valid for loop termination - for (DN_USize temp_index = temp.size - 1; temp_index < temp.size; temp_index--) { - char ch = temp.data[temp_index]; - result.data[result.size++] = ch; - } - DN_MSVC_WARNING_POP - } - - return result; -} - -DN_API DN_U64ByteSize DN_U64ToByteSize(uint64_t bytes, DN_U64ByteSizeType desired_type) -{ - DN_U64ByteSize result = {}; - result.bytes = DN_CAST(DN_F64)bytes; - if (!DN_CHECK(desired_type != DN_U64ByteSizeType_Count)) { - result.suffix = DN_U64ByteSizeTypeString(result.type); - return result; - } - - if (desired_type == DN_U64ByteSizeType_Auto) { - for (; result.type < DN_U64ByteSizeType_Count && result.bytes >= 1024.0; result.type = DN_CAST(DN_U64ByteSizeType)(DN_CAST(DN_USize)result.type + 1)) - result.bytes /= 1024.0; - } else { - for (; result.type < desired_type; result.type = DN_CAST(DN_U64ByteSizeType)(DN_CAST(DN_USize)result.type + 1)) - result.bytes /= 1024.0; - } - - result.suffix = DN_U64ByteSizeTypeString(result.type); - return result; -} - -DN_API DN_Str8 DN_U64ToByteSizeStr8(DN_Arena *arena, uint64_t bytes, DN_U64ByteSizeType desired_type) -{ - DN_U64ByteSize byte_size = DN_U64ToByteSize(bytes, desired_type); - DN_Str8 result = DN_Str8_InitF(arena, "%.2f%.*s", byte_size.bytes, DN_STR_FMT(byte_size.suffix)); - return result; -} - -DN_API DN_Str8 DN_U64ByteSizeTypeString(DN_U64ByteSizeType type) -{ - DN_Str8 result = DN_STR8(""); - switch (type) { - case DN_U64ByteSizeType_B: result = DN_STR8("B"); break; - case DN_U64ByteSizeType_KiB: result = DN_STR8("KiB"); break; - case DN_U64ByteSizeType_MiB: result = DN_STR8("MiB"); break; - case DN_U64ByteSizeType_GiB: result = DN_STR8("GiB"); break; - case DN_U64ByteSizeType_TiB: result = DN_STR8("TiB"); break; - case DN_U64ByteSizeType_Count: result = DN_STR8(""); break; - case DN_U64ByteSizeType_Auto: result = DN_STR8(""); break; - } - return result; -} - -DN_API DN_Str8 DN_U64ToAge(DN_Arena *arena, uint64_t age_s, DN_U64AgeUnit unit) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - uint64_t remainder = age_s; - - if (unit & DN_U64AgeUnit_Year) { - DN_USize value = remainder / DN_YEARS_TO_S(1); - remainder -= DN_YEARS_TO_S(value); - if (value) - DN_Str8Builder_AppendF(&builder, "%s%I64uyr", builder.string_size ? " " : "", value); - } - - if (unit & DN_U64AgeUnit_Week) { - DN_USize value = remainder / DN_WEEKS_TO_S(1); - remainder -= DN_WEEKS_TO_S(value); - if (value) - DN_Str8Builder_AppendF(&builder, "%s%I64uw", builder.string_size ? " " : "", value); - } - - if (unit & DN_U64AgeUnit_Day) { - DN_USize value = remainder / DN_DAYS_TO_S(1); - remainder -= DN_DAYS_TO_S(value); - if (value) - DN_Str8Builder_AppendF(&builder, "%s%I64ud", builder.string_size ? " " : "", value); - } - - if (unit & DN_U64AgeUnit_Hr) { - DN_USize value = remainder / DN_HOURS_TO_S(1); - remainder -= DN_HOURS_TO_S(value); - if (value) - DN_Str8Builder_AppendF(&builder, "%s%I64uh", builder.string_size ? " " : "", value); - } - - if (unit & DN_U64AgeUnit_Min) { - DN_USize value = remainder / DN_MINS_TO_S(1); - remainder -= DN_MINS_TO_S(value); - if (value) - DN_Str8Builder_AppendF(&builder, "%s%I64um", builder.string_size ? " " : "", value); - } - - if (unit & DN_U64AgeUnit_Sec) { - DN_USize value = remainder; - DN_Str8Builder_AppendF(&builder, "%s%I64us", builder.string_size ? " " : "", value); - } - - result = DN_Str8Builder_Build(&builder, arena); - return result; -} - -DN_API DN_Str8 DN_F64ToAge(DN_Arena *arena, DN_F64 age_s, DN_U64AgeUnit unit) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - DN_F64 remainder = age_s; - - if (unit & DN_U64AgeUnit_Year) { - DN_F64 value = remainder / DN_CAST(DN_F64)DN_YEARS_TO_S(1); - if (value >= 1.0) { - remainder -= DN_YEARS_TO_S(value); - DN_Str8Builder_AppendF(&builder, "%s%.1fyr", builder.string_size ? " " : "", value); - } - } - - if (unit & DN_U64AgeUnit_Week) { - DN_F64 value = remainder / DN_CAST(DN_F64)DN_WEEKS_TO_S(1); - if (value >= 1.0) { - remainder -= DN_WEEKS_TO_S(value); - DN_Str8Builder_AppendF(&builder, "%s%.1fw", builder.string_size ? " " : "", value); - } - } - - if (unit & DN_U64AgeUnit_Day) { - DN_F64 value = remainder / DN_CAST(DN_F64)DN_DAYS_TO_S(1); - if (value >= 1.0) { - remainder -= DN_WEEKS_TO_S(value); - DN_Str8Builder_AppendF(&builder, "%s%.1fd", builder.string_size ? " " : "", value); - } - } - - if (unit & DN_U64AgeUnit_Hr) { - DN_F64 value = remainder / DN_CAST(DN_F64)DN_HOURS_TO_S(1); - if (value >= 1.0) { - remainder -= DN_HOURS_TO_S(value); - DN_Str8Builder_AppendF(&builder, "%s%.1fh", builder.string_size ? " " : "", value); - } - } - - if (unit & DN_U64AgeUnit_Min) { - DN_F64 value = remainder / DN_CAST(DN_F64)DN_MINS_TO_S(1); - if (value >= 1.0) { - remainder -= DN_MINS_TO_S(value); - DN_Str8Builder_AppendF(&builder, "%s%.1fm", builder.string_size ? " " : "", value); - } - } - - if (unit & DN_U64AgeUnit_Sec) { - DN_F64 value = remainder; - DN_Str8Builder_AppendF(&builder, "%s%.1fs", builder.string_size ? " " : "", value); - } - - result = DN_Str8Builder_Build(&builder, arena); - return result; -} - -DN_API uint64_t DN_HexToU64(DN_Str8 hex) -{ - DN_Str8 real_hex = DN_Str8_TrimPrefix(DN_Str8_TrimPrefix(hex, DN_STR8("0x")), DN_STR8("0X")); - DN_USize max_hex_size = sizeof(uint64_t) * 2 /*hex chars per byte*/; - DN_ASSERT(real_hex.size <= max_hex_size); - - DN_USize size = DN_MIN(max_hex_size, real_hex.size); - uint64_t result = 0; - for (DN_USize index = 0; index < size; index++) { - char ch = real_hex.data[index]; - DN_CharHexToU8 val = DN_Char_HexToU8(ch); - if (!val.success) - break; - result = (result << 4) | val.value; - } - return result; -} - -DN_API DN_Str8 DN_U64ToHex(DN_Arena *arena, uint64_t number, uint32_t flags) -{ - DN_Str8 prefix = {}; - if ((flags & DN_HexU64Str8Flags_0xPrefix)) - prefix = DN_STR8("0x"); - - char const *fmt = (flags & DN_HexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; - DN_USize required_size = DN_CStr8_FSize(fmt, number) + prefix.size; - DN_Str8 result = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); - - if (DN_Str8_HasData(result)) { - DN_MEMCPY(result.data, prefix.data, prefix.size); - int space = DN_CAST(int) DN_MAX((result.size - prefix.size) + 1, 0); /*null-terminator*/ - DN_SNPRINTF(result.data + prefix.size, space, fmt, number); - } - return result; -} - -DN_API DN_U64HexStr8 DN_U64ToHexStr8(uint64_t number, DN_U64HexStr8Flags flags) -{ - DN_Str8 prefix = {}; - if (flags & DN_HexU64Str8Flags_0xPrefix) - prefix = DN_STR8("0x"); - - DN_U64HexStr8 result = {}; - DN_MEMCPY(result.data, prefix.data, prefix.size); - result.size += DN_CAST(int8_t) prefix.size; - - char const *fmt = (flags & DN_HexU64Str8Flags_UppercaseHex) ? "%I64X" : "%I64x"; - int size = DN_SNPRINTF(result.data + result.size, DN_ARRAY_UCOUNT(result.data) - result.size, fmt, number); - result.size += DN_CAST(uint8_t) size; - DN_ASSERT(result.size < DN_ARRAY_UCOUNT(result.data)); - - // NOTE: snprintf returns the required size of the format string - // irrespective of if there's space or not, but, always null terminates so - // the last byte is wasted. - result.size = DN_MIN(result.size, DN_ARRAY_UCOUNT(result.data) - 1); - return result; -} - -DN_API bool DN_BytesToHexPtr(void const *src, DN_USize src_size, char *dest, DN_USize dest_size) -{ - if (!src || !dest) - return false; - - if (!DN_CHECK(dest_size >= src_size * 2)) - return false; - - char const *HEX = "0123456789abcdef"; - unsigned char const *src_u8 = DN_CAST(unsigned char const *) src; - for (DN_USize src_index = 0, dest_index = 0; src_index < src_size; src_index++) { - char byte = src_u8[src_index]; - char hex01 = (byte >> 4) & 0b1111; - char hex02 = (byte >> 0) & 0b1111; - dest[dest_index++] = HEX[(int)hex01]; - dest[dest_index++] = HEX[(int)hex02]; - } - - return true; -} - -DN_API DN_Str8 DN_BytesToHex(DN_Arena *arena, void const *src, DN_USize size) -{ - DN_Str8 result = {}; - if (!src || size <= 0) - return result; - - result = DN_Str8_Alloc(arena, size * 2, DN_ZeroMem_No); - result.data[result.size] = 0; - bool converted = DN_BytesToHexPtr(src, size, result.data, result.size); - DN_ASSERT(converted); - return result; -} - -DN_API DN_USize DN_HexToBytesPtrUnchecked(DN_Str8 hex, void *dest, DN_USize dest_size) -{ - DN_USize result = 0; - unsigned char *dest_u8 = DN_CAST(unsigned char *) dest; - - for (DN_USize hex_index = 0; hex_index < hex.size; hex_index += 2, result += 1) { - char hex01 = hex.data[hex_index]; - char hex02 = (hex_index + 1 < hex.size) ? hex.data[hex_index + 1] : 0; - char bit4_01 = DN_Char_HexToU8(hex01).value; - char bit4_02 = DN_Char_HexToU8(hex02).value; - char byte = (bit4_01 << 4) | (bit4_02 << 0); - dest_u8[result] = byte; - } - - DN_ASSERT(result <= dest_size); - return result; -} - -DN_API DN_USize DN_HexToBytesPtr(DN_Str8 hex, void *dest, DN_USize dest_size) -{ - hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); - hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); - - DN_USize result = 0; - if (!DN_Str8_HasData(hex)) - return result; - - // NOTE: Trimmed hex can be "0xf" -> "f" or "0xAB" -> "AB" - // Either way, the size can be odd or even, hence we round up to the nearest - // multiple of two to ensure that we calculate the min buffer size orrectly. - DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); - DN_USize min_buffer_size = hex_size_rounded_up / 2; - if (hex.size <= 0 || !DN_CHECK(dest_size >= min_buffer_size)) { - return result; - } - - result = DN_HexToBytesPtrUnchecked(hex, dest, dest_size); - return result; -} - -DN_API DN_Str8 DN_HexToBytesUnchecked(DN_Arena *arena, DN_Str8 hex) -{ - DN_USize hex_size_rounded_up = hex.size + (hex.size % 2); - DN_Str8 result = DN_Str8_Alloc(arena, (hex_size_rounded_up / 2), DN_ZeroMem_No); - if (result.data) { - DN_USize bytes_written = DN_HexToBytesPtr(hex, result.data, result.size); - DN_ASSERT(bytes_written == result.size); - } - return result; -} - -DN_API DN_Str8 DN_HexToBytes(DN_Arena *arena, DN_Str8 hex) -{ - hex = DN_Str8_TrimPrefix(hex, DN_STR8("0x")); - hex = DN_Str8_TrimPrefix(hex, DN_STR8("0X")); - - DN_Str8 result = {}; - if (!DN_Str8_HasData(hex)) - return result; - - if (!DN_CHECK(DN_Str8_IsAll(hex, DN_Str8IsAll_Hex))) - return result; - - result = DN_HexToBytesUnchecked(arena, hex); - return result; -} - - -// NOTE: [$CORE] DN_Core ////////////////////////////////////////////////////////////////////////// -DN_Core *g_dn_core; - -DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) -{ - // NOTE: Init check /////////////////////////////////////////////////////////////////////////// - if (core->init) - return; - - #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_STR8(#label)}; - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - - // NOTE: Setup OS ////////////////////////////////////////////////////////////////////////////// - { - #if defined(DN_OS_WIN32) - SYSTEM_INFO system_info = {}; - GetSystemInfo(&system_info); - core->os_page_size = system_info.dwPageSize; - core->os_alloc_granularity = system_info.dwAllocationGranularity; - QueryPerformanceFrequency(&core->win32_qpc_frequency); - - HMODULE module = LoadLibraryA("kernel32.dll"); - g_dn_win_set_thread_description = DN_CAST(DN_WinSetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - #else - // TODO(doyle): Get the proper page size from the OS. - core->os_page_size = DN_KILOBYTES(4); - core->os_alloc_granularity = DN_KILOBYTES(64); - #endif - } - - core->init_mutex = DN_OS_MutexInit(); - DN_OS_MutexLock(&core->init_mutex); - DN_DEFER { - DN_OS_MutexUnlock(&core->init_mutex); - }; - - DN_Core_SetPointer(core); - core->init = true; - core->cpu_report = DN_CPU_Report(); - - // NOTE Initialise fields ////////////////////////////////////////////////////////////////////// - #if !defined(DN_NO_PROFILER) - core->profiler = &core->profiler_default_instance; - #endif - - // NOTE: BEGIN IMPORTANT ORDER OF STATEMENTS /////////////////////////////////////////////////// - #if defined(DN_LEAK_TRACKING) - // NOTE: Setup the allocation table with allocation tracking turned off on - // the arena we're using to initialise the table. - core->alloc_table_arena = DN_Arena_InitSize(DN_MEGABYTES(1), DN_KILOBYTES(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); - core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); - #endif - - core->arena = DN_Arena_InitSize(DN_KILOBYTES(64), DN_KILOBYTES(4), DN_ArenaFlags_AllocCanLeak); - core->pool = DN_Pool_Init(&core->arena, /*align*/ 0); - DN_ArenaCatalog_Init(&core->arena_catalog, &core->pool); - DN_ArenaCatalog_AddF(&core->arena_catalog, &core->arena, "DN Core"); - - #if defined(DN_LEAK_TRACKING) - DN_ArenaCatalog_AddF(&core->arena_catalog, &core->alloc_table_arena, "DN Allocation Table"); - #endif - - // NOTE: Initialise tmem arenas which allocate memory and will be - // recorded to the now initialised allocation table. The initialisation - // of tmem memory may request tmem memory itself in leak tracing mode. - // This is supported as the tmem arenas defer allocation tracking until - // initialisation is done. - DN_TLS_Init(&core->tls); - DN_OS_ThreadSetTLS(&core->tls); - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - // NOTE: END IMPORTANT ORDER OF STATEMENTS ///////////////////////////////////////////////////// - - core->exe_dir = DN_OS_EXEDir(&core->arena); - - // NOTE: Print out init features /////////////////////////////////////////////////////////////// - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - if (on_init & DN_CoreOnInit_LogLibFeatures) { - DN_Str8Builder_AppendRef(&builder, DN_STR8("DN Library initialised:\n")); - - DN_F64 page_size_kib = core->os_page_size / 1024.0; - DN_F64 alloc_granularity_kib = core->os_alloc_granularity / 1024.0; - DN_Str8Builder_AppendF( - &builder, " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n", page_size_kib, alloc_granularity_kib); - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - if (DN_ASAN_POISON) { - DN_Str8Builder_AppendF( - &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); - DN_Str8Builder_AppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); - } - #endif - - #if defined(DN_LEAK_TRACKING) - DN_Str8Builder_AppendRef(&builder, DN_STR8(" Allocation leak tracing\n")); - #endif - - #if !defined(DN_NO_PROFILER) - DN_Str8Builder_AppendRef(&builder, DN_STR8(" TSC profiler available\n")); - #endif - - #if defined(DN_USE_STD_PRINTF) - DN_Str8Builder_AppendRef(&builder, DN_STR8(" Using stdio's printf functions\n")); - #else - DN_Str8Builder_AppendRef(&builder, DN_STR8(" Using stb_sprintf functions\n")); - #endif - - // TODO(doyle): Add stacktrace feature log - } - - if (on_init & DN_CoreOnInit_LogCPUFeatures) { - DN_CPUReport const *report = &core->cpu_report; - DN_Str8 brand = DN_Str8_TrimWhitespaceAround(DN_Str8_Init(report->brand, sizeof(report->brand) - 1)); - DN_Str8Builder_AppendF(&builder, - " CPU '%.*s' from '%s' detected:\n", - DN_STR_FMT(brand), - report->vendor); - - DN_USize longest_feature_name = 0; - DN_FOR_UINDEX(feature_index, DN_CPUFeature_Count) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - longest_feature_name = DN_MAX(longest_feature_name, feature_decl.label.size); - } - - DN_FOR_UINDEX(feature_index, DN_CPUFeature_Count) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - bool has_feature = DN_CPU_HasFeature(report, feature_decl.value); - DN_Str8Builder_AppendF(&builder, - " %.*s:%*s%s\n", - DN_STR_FMT(feature_decl.label), - DN_CAST(int)(longest_feature_name - feature_decl.label.size), - "", - has_feature ? "available" : "not available"); - } - } - - DN_Str8 info_log = DN_Str8Builder_Build(&builder, tmem.arena); - if (DN_Str8_HasData(info_log)) - DN_Log_DebugF("%.*s", DN_STR_FMT(info_log)); -} - -DN_API void DN_Core_BeginFrame() -{ - DN_Atomic_SetValue64(&g_dn_core->mem_allocs_frame, 0); -} - -DN_API void DN_Core_SetPointer(DN_Core *library) -{ - if (library) { - g_dn_core = library; - DN_OS_ThreadSetTLS(&library->tls); - } -} - -#if !defined(DN_NO_PROFILER) -DN_API void DN_Core_SetProfiler(DN_Profiler *profiler) -{ - if (profiler) - g_dn_core->profiler = profiler; -} -#endif - -DN_API void DN_Core_SetLogCallback(DN_LogProc *proc, void *user_data) -{ - g_dn_core->log_callback = proc; - g_dn_core->log_user_data = user_data; -} - -DN_API void DN_Core_DumpThreadContextArenaStat(DN_Str8 file_path) -{ -#if defined(DN_DEBUG_THREAD_CONTEXT) - // NOTE: Open a file to write the arena stats to - FILE *file = nullptr; - fopen_s(&file, file_path.data, "a+b"); - if (file) { - DN_Log_ErrorF("Failed to dump thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); - return; - } - - // NOTE: Copy the stats from library book-keeping - // NOTE: Extremely short critical section, copy the stats then do our - // work on it. - DN_ArenaStat stats[DN_CArray_CountI(g_dn_core->thread_context_arena_stats)]; - int stats_size = 0; - - DN_TicketMutex_Begin(&g_dn_core->thread_context_mutex); - stats_size = g_dn_core->thread_context_arena_stats_count; - DN_MEMCPY(stats, g_dn_core->thread_context_arena_stats, sizeof(stats[0]) * stats_size); - DN_TicketMutex_End(&g_dn_core->thread_context_mutex); - - // NOTE: Print the cumulative stat - DN_DateHMSTimeStr now = DN_Date_HMSLocalTimeStrNow(); - fprintf(file, - "Time=%.*s %.*s | Thread Context Arenas | Count=%d\n", - now.date_size, - now.date, - now.hms_size, - now.hms, - g_dn_core->thread_context_arena_stats_count); - - // NOTE: Write the cumulative thread arena data - { - DN_ArenaStat stat = {}; - for (DN_USize index = 0; index < stats_size; index++) { - DN_ArenaStat const *current = stats + index; - stat.capacity += current->capacity; - stat.used += current->used; - stat.wasted += current->wasted; - stat.blocks += current->blocks; - - stat.capacity_hwm = DN_MAX(stat.capacity_hwm, current->capacity_hwm); - stat.used_hwm = DN_MAX(stat.used_hwm, current->used_hwm); - stat.wasted_hwm = DN_MAX(stat.wasted_hwm, current->wasted_hwm); - stat.blocks_hwm = DN_MAX(stat.blocks_hwm, current->blocks_hwm); - } - - DN_ArenaStatStr stats_string = DN_Arena_StatStr(&stat); - fprintf(file, " [ALL] CURR %.*s\n", stats_string.size, stats_string.data); - } - - // NOTE: Print individual thread arena data - for (DN_USize index = 0; index < stats_size; index++) { - DN_ArenaStat const *current = stats + index; - DN_ArenaStatStr current_string = DN_Arena_StatStr(current); - fprintf(file, " [%03d] CURR %.*s\n", DN_CAST(int) index, current_string.size, current_string.data); - } - - fclose(file); - DN_Log_InfoF("Dumped thread context arenas [file=%.*s]", DN_STR_FMT(file_path)); -#else - (void)file_path; -#endif // #if defined(DN_DEBUG_THREAD_CONTEXT) -} - -DN_API DN_Arena *DN_Core_AllocArenaF(DN_USize reserve, DN_USize commit, uint8_t arena_flags, char const *fmt, ...) -{ - DN_ASSERT(g_dn_core->init); - va_list args; - va_start(args, fmt); - DN_ArenaCatalog *catalog = &g_dn_core->arena_catalog; - DN_Arena *result = DN_ArenaCatalog_AllocFV(catalog, reserve, commit, arena_flags, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_Core_EraseArena(DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena) -{ - DN_ArenaCatalog *catalog = &g_dn_core->arena_catalog; - bool result = DN_ArenaCatalog_Erase(catalog, arena, free_arena); - return result; -} - -#if !defined(DN_NO_PROFILER) -// NOTE: [$PROF] DN_Profiler ////////////////////////////////////////////////////////////////////// -DN_API DN_ProfilerZoneScope::DN_ProfilerZoneScope(DN_Str8 name, uint16_t anchor_index) -{ - zone = DN_Profiler_BeginZoneAtIndex(name, anchor_index); -} - -DN_API DN_ProfilerZoneScope::~DN_ProfilerZoneScope() -{ - DN_Profiler_EndZone(zone); -} - -DN_API DN_ProfilerAnchor *DN_Profiler_ReadBuffer() -{ - uint8_t mask = DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors) - 1; - DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer - 1) & mask]; - return result; -} - -DN_API DN_ProfilerAnchor *DN_Profiler_WriteBuffer() -{ - uint8_t mask = DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors) - 1; - DN_ProfilerAnchor *result = g_dn_core->profiler->anchors[(g_dn_core->profiler->active_anchor_buffer + 0) & mask]; - return result; -} - -DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex(DN_Str8 name, uint16_t anchor_index) -{ - DN_ProfilerAnchor *anchor = DN_Profiler_WriteBuffer() + anchor_index; - // TODO: We need per-thread-local-storage profiler so that we can use these apis - // across threads. For now, we let them overwrite each other but this is not tenable. -#if 0 - if (DN_Str8_HasData(anchor->name) && anchor->name != name) - DN_ASSERTF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_STR_FMT(anchor->name), DN_STR_FMT(name)); -#endif - anchor->name = name; - DN_ProfilerZone result = {}; - result.begin_tsc = DN_CPU_TSC(); - result.anchor_index = anchor_index; - result.parent_zone = g_dn_core->profiler->parent_zone; - result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; - g_dn_core->profiler->parent_zone = anchor_index; - return result; -} - -DN_API void DN_Profiler_EndZone(DN_ProfilerZone zone) -{ - uint64_t elapsed_tsc = DN_CPU_TSC() - zone.begin_tsc; - DN_ProfilerAnchor *anchor_buffer = DN_Profiler_WriteBuffer(); - DN_ProfilerAnchor *anchor = anchor_buffer + zone.anchor_index; - - anchor->hit_count++; - anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; - anchor->tsc_exclusive += elapsed_tsc; - - DN_ProfilerAnchor *parent_anchor = anchor_buffer + zone.parent_zone; - parent_anchor->tsc_exclusive -= elapsed_tsc; - g_dn_core->profiler->parent_zone = zone.parent_zone; -} - -DN_API void DN_Profiler_SwapAnchorBuffer() -{ - g_dn_core->profiler->active_anchor_buffer++; - g_dn_core->profiler->parent_zone = 0; - DN_ProfilerAnchor *anchors = DN_Profiler_WriteBuffer(); - DN_MEMSET(anchors, - 0, - DN_ARRAY_UCOUNT(g_dn_core->profiler->anchors[0]) * sizeof(g_dn_core->profiler->anchors[0][0])); -} - -DN_API void DN_Profiler_Dump(uint64_t tsc_per_second) -{ - DN_ProfilerAnchor *anchors = DN_Profiler_ReadBuffer(); - for (size_t anchor_index = 1; anchor_index < DN_PROFILER_ANCHOR_BUFFER_SIZE; anchor_index++) { - DN_ProfilerAnchor const *anchor = anchors + anchor_index; - if (!anchor->hit_count) - continue; - - uint64_t tsc_exclusive = anchor->tsc_exclusive; - uint64_t tsc_inclusive = anchor->tsc_inclusive; - DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; - if (tsc_exclusive == tsc_inclusive) { - DN_Print_LnF("%.*s[%u]: %.1fms", DN_STR_FMT(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); - } else { - DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_CAST(DN_F64) tsc_per_second; - DN_Print_LnF("%.*s[%u]: %.1f/%.1fms", - DN_STR_FMT(anchor->name), - anchor->hit_count, - tsc_exclusive_milliseconds, - tsc_inclusive_milliseconds); - } - } -} -#endif // !defined(DN_NO_PROFILER) - -// NOTE: [$JOBQ] DN_JobQueue /////////////////////////////////////////////////////////////////////// -DN_API DN_JobQueueSPMC DN_OS_JobQueueSPMCInit() -{ - DN_JobQueueSPMC result = {}; - result.thread_wait_for_job_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - result.wait_for_completion_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - result.complete_queue_write_semaphore = DN_OS_SemaphoreInit(DN_ARRAY_UCOUNT(result.complete_queue)); - result.mutex = DN_OS_MutexInit(); - return result; -} - -DN_API bool DN_OS_JobQueueSPMCCanAdd(DN_JobQueueSPMC const *queue, uint32_t count) -{ - uint32_t read_index = queue->read_index; - uint32_t write_index = queue->write_index; - uint32_t size = write_index - read_index; - bool result = (size + count) <= DN_ARRAY_UCOUNT(queue->jobs); - return result; -} - -DN_API bool DN_OS_JobQueueSPMCAddArray(DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count) -{ - if (!queue) - return false; - - uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1; - uint32_t read_index = queue->read_index; - uint32_t write_index = queue->write_index; - uint32_t size = write_index - read_index; - - if ((size + count) > DN_ARRAY_UCOUNT(queue->jobs)) - return false; - - for (size_t offset = 0; offset < count; offset++) { - uint32_t wrapped_write_index = (write_index + offset) & pot_mask; - queue->jobs[wrapped_write_index] = jobs[offset]; - } - - DN_OS_MutexLock(&queue->mutex); - queue->write_index += count; - DN_OS_SemaphoreIncrement(&queue->thread_wait_for_job_semaphore, count); - DN_OS_MutexUnlock(&queue->mutex); - return true; -} - -DN_API bool DN_OS_JobQueueSPMCAdd(DN_JobQueueSPMC *queue, DN_Job job) -{ - bool result = DN_OS_JobQueueSPMCAddArray(queue, &job, 1); - return result; -} - -DN_API int32_t DN_OS_JobQueueSPMCThread(DN_OSThread *thread) -{ - DN_JobQueueSPMC *queue = DN_CAST(DN_JobQueueSPMC *) thread->user_context; - uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1; - static_assert(DN_ARRAY_UCOUNT(queue->jobs) == DN_ARRAY_UCOUNT(queue->complete_queue), "PoT mask is used to mask access to both arrays"); - - for (;;) { - DN_OS_SemaphoreWait(&queue->thread_wait_for_job_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); - if (queue->quit) - break; - - DN_ASSERT(queue->read_index != queue->write_index); - - DN_OS_MutexLock(&queue->mutex); - uint32_t wrapped_read_index = queue->read_index & pot_mask; - DN_Job job = queue->jobs[wrapped_read_index]; - queue->read_index += 1; - DN_OS_MutexUnlock(&queue->mutex); - - job.elapsed_tsc -= DN_CPU_TSC(); - job.func(thread, job.user_context); - job.elapsed_tsc += DN_CPU_TSC(); - - if (job.add_to_completion_queue) { - DN_OS_SemaphoreWait(&queue->complete_queue_write_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); - DN_OS_MutexLock(&queue->mutex); - queue->complete_queue[(queue->complete_write_index++ & pot_mask)] = job; - DN_OS_MutexUnlock(&queue->mutex); - DN_OS_SemaphoreIncrement(&queue->complete_queue_write_semaphore, 1); - } - - // NOTE: Update finish counter - DN_OS_MutexLock(&queue->mutex); - queue->finish_index += 1; - - // NOTE: If all jobs are finished and we have another thread who is - // blocked via `WaitForCompletion` for this job queue, we will go - // release the semaphore to wake them all up. - bool all_jobs_finished = queue->finish_index == queue->write_index; - if (all_jobs_finished && queue->threads_waiting_for_completion) { - DN_OS_SemaphoreIncrement(&queue->wait_for_completion_semaphore, - queue->threads_waiting_for_completion); - queue->threads_waiting_for_completion = 0; - } - DN_OS_MutexUnlock(&queue->mutex); - } - - return queue->quit_exit_code; -} - -DN_API void DN_OS_JobQueueSPMCWaitForCompletion(DN_JobQueueSPMC *queue) -{ - DN_OS_MutexLock(&queue->mutex); - if (queue->finish_index == queue->write_index) { - DN_OS_MutexUnlock(&queue->mutex); - return; - } - queue->threads_waiting_for_completion++; - DN_OS_MutexUnlock(&queue->mutex); - - DN_OS_SemaphoreWait(&queue->wait_for_completion_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); -} - -DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs(DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size) -{ - DN_USize result = 0; - if (!queue || !jobs || jobs_size <= 0) - return result; - - uint32_t const pot_mask = DN_ARRAY_UCOUNT(queue->jobs) - 1; - DN_OS_MutexLock(&queue->mutex); - while (queue->complete_read_index < queue->complete_write_index && result < jobs_size) { - jobs[result++] = queue->complete_queue[(queue->complete_read_index++ & pot_mask)]; - } - DN_OS_MutexUnlock(&queue->mutex); - - return result; -} diff --git a/dqn_helpers.h b/dqn_helpers.h deleted file mode 100644 index 8be471a..0000000 --- a/dqn_helpers.h +++ /dev/null @@ -1,628 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$$$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ -// $$ | $$ |$$ _____|$$ | $$ __$$\ $$ _____|$$ __$$\ $$ __$$\ -// $$ | $$ |$$ | $$ | $$ | $$ |$$ | $$ | $$ |$$ / \__| -// $$$$$$$$ |$$$$$\ $$ | $$$$$$$ |$$$$$\ $$$$$$$ |\$$$$$$\ -// $$ __$$ |$$ __| $$ | $$ ____/ $$ __| $$ __$$< \____$$\ -// $$ | $$ |$$ | $$ | $$ | $$ | $$ | $$ |$$\ $$ | -// $$ | $$ |$$$$$$$$\ $$$$$$$$\ $$ | $$$$$$$$\ $$ | $$ |\$$$$$$ | -// \__| \__|\________|\________|\__| \________|\__| \__| \______/ -// -// dqn_helpers.h -- Helper functions/data structures -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$PCG3] DN_PCG32 -- -- RNG from the PCG family -// [$JSON] DN_JSONBuilder -- DN_JSON_BUILDER -- Construct json output -// [$BHEX] DN_Bin -- DN_BIN -- Binary <-> hex helpers -// [$BSEA] DN_BinarySearch -- -- Binary search -// [$BITS] DN_Bit -- -- Bitset manipulation -// [$SAFE] DN_Safe -- -- Safe arithmetic, casts, asserts -// [$MISC] Misc -- -- Uncategorised helper functions -// [$DLIB] DN_Library -- -- Globally shared runtime data for this library -// [$PROF] DN_Profiler -- DN_PROFILER -- Profiler that measures using a timestamp counter -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$PCGX] DN_PCG32 ///////////////////////////////////////////////////////////////////////// -struct DN_PCG32 { uint64_t state; }; - -#if !defined(DN_NO_JSON_BUILDER) -// NOTE: [$JSON] DN_JSONBuilder /////////////////////////////////////////////////////////////////// -enum DN_JSONBuilderItem -{ - DN_JSONBuilderItem_Empty, - DN_JSONBuilderItem_OpenContainer, - DN_JSONBuilderItem_CloseContainer, - DN_JSONBuilderItem_KeyValue, -}; - -struct DN_JSONBuilder -{ - bool use_stdout; // When set, ignore the string builder and dump immediately to stdout - DN_Str8Builder string_builder; // (Internal) - int indent_level; // (Internal) - int spaces_per_indent; // The number of spaces per indent level - DN_JSONBuilderItem last_item; -}; -#endif // !defined(DN_NO_JSON_BUIDLER) - -// NOTE: [$BSEA] DN_BinarySearch ////////////////////////////////////////////////////////////////// -template -using DN_BinarySearchLessThanProc = bool(T const &lhs, T const &rhs); - -template -bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); - -enum DN_BinarySearchType -{ - // Index of the match. If no match is found, found is set to false and the - // index is set to the index where the match should be inserted/exist, if - // it were in the array - DN_BinarySearchType_Match, - - // Index of the first element in the array that is `element >= find`. If no such - // item is found or the array is empty, then, the index is set to the array - // size and found is set to `false`. - // - // For example: - // int array[] = {0, 1, 2, 3, 4, 5}; - // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4, DN_BinarySearchType_LowerBound); - // printf("%zu\n", result.index); // Prints index '4' - - DN_BinarySearchType_LowerBound, - - // Index of the first element in the array that is `element > find`. If no such - // item is found or the array is empty, then, the index is set to the array - // size and found is set to `false`. - // - // For example: - // int array[] = {0, 1, 2, 3, 4, 5}; - // DN_BinarySearchResult result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4, DN_BinarySearchType_UpperBound); - // printf("%zu\n", result.index); // Prints index '5' - - DN_BinarySearchType_UpperBound, -}; - -struct DN_BinarySearchResult -{ - bool found; - DN_USize index; -}; - -template -using DN_QSortLessThanProc = bool(T const &a, T const &b, void *user_context); - -// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// -struct DN_U64Str8 -{ - char data[27+1]; // NOTE(dn): 27 is the maximum size of uint64_t including a separator - uint8_t size; -}; - -enum DN_U64ByteSizeType -{ - DN_U64ByteSizeType_B, - DN_U64ByteSizeType_KiB, - DN_U64ByteSizeType_MiB, - DN_U64ByteSizeType_GiB, - DN_U64ByteSizeType_TiB, - DN_U64ByteSizeType_Count, - DN_U64ByteSizeType_Auto, -}; - -struct DN_U64ByteSize -{ - DN_U64ByteSizeType type; - DN_Str8 suffix; // "KiB", "MiB", "GiB" .. e.t.c - DN_F64 bytes; -}; - -typedef uint32_t DN_U64AgeUnit; -enum DN_U64AgeUnit_ -{ - DN_U64AgeUnit_Sec = 1 << 0, - DN_U64AgeUnit_Min = 1 << 1, - DN_U64AgeUnit_Hr = 1 << 2, - DN_U64AgeUnit_Day = 1 << 3, - DN_U64AgeUnit_Week = 1 << 4, - DN_U64AgeUnit_Year = 1 << 5, - DN_U64AgeUnit_HMS = DN_U64AgeUnit_Sec | DN_U64AgeUnit_Min | DN_U64AgeUnit_Hr, - DN_U64AgeUnit_All = DN_U64AgeUnit_HMS | DN_U64AgeUnit_Day | DN_U64AgeUnit_Week | DN_U64AgeUnit_Year, -}; - -struct DN_U64HexStr8 -{ - char data[2 /*0x*/ + 16 /*hex*/ + 1 /*null-terminator*/]; - uint8_t size; -}; - -typedef uint32_t DN_U64HexStr8Flags; -enum DN_U64HexStr8Flags_ -{ - DN_HexU64Str8Flags_Nil = 0, - DN_HexU64Str8Flags_0xPrefix = 1 << 0, /// Add the '0x' prefix from the string - DN_HexU64Str8Flags_UppercaseHex = 1 << 1, /// Use uppercase ascii characters for hex -}; - -#if !defined(DN_NO_PROFILER) -// NOTE: [$PROF] DN_Profiler ////////////////////////////////////////////////////////////////////// -#if !defined(DN_PROFILER_ANCHOR_BUFFER_SIZE) - #define DN_PROFILER_ANCHOR_BUFFER_SIZE 256 -#endif - -struct DN_ProfilerAnchor -{ - // Inclusive refers to the time spent to complete the function call - // including all children functions. - // - // Exclusive refers to the time spent in the function, not including any - // time spent in children functions that we call that are also being - // profiled. If we recursively call into ourselves, the time we spent in - // our function is accumulated. - uint64_t tsc_inclusive; - uint64_t tsc_exclusive; - uint16_t hit_count; - DN_Str8 name; -}; - -struct DN_ProfilerZone -{ - uint16_t anchor_index; - uint64_t begin_tsc; - uint16_t parent_zone; - uint64_t elapsed_tsc_at_zone_start; -}; - -#if defined(__cplusplus) -struct DN_ProfilerZoneScope -{ - DN_ProfilerZoneScope(DN_Str8 name, uint16_t anchor_index); - ~DN_ProfilerZoneScope(); - DN_ProfilerZone zone; -}; -#define DN_Profiler_ZoneScopeAtIndex(name, anchor_index) auto DN_UNIQUE_NAME(profile_zone_) = DN_ProfilerZoneScope(DN_STR8(name), anchor_index) -#define DN_Profiler_ZoneScope(name) DN_Profiler_ZoneScopeAtIndex(name, __COUNTER__ + 1) -#endif - -#define DN_Profiler_ZoneBlockIndex(name, index) \ - for (DN_ProfilerZone DN_UNIQUE_NAME(profile_zone__) = DN_Profiler_BeginZoneAtIndex(name, index), DN_UNIQUE_NAME(dummy__) = {}; \ - DN_UNIQUE_NAME(dummy__).begin_tsc == 0; \ - DN_Profiler_EndZone(DN_UNIQUE_NAME(profile_zone__)), DN_UNIQUE_NAME(dummy__).begin_tsc = 1) - -#define DN_Profiler_ZoneBlock(name) DN_Profiler_ZoneBlockIndex(DN_STR8(name), __COUNTER__ + 1) - -enum DN_ProfilerAnchorBuffer -{ - DN_ProfilerAnchorBuffer_Back, - DN_ProfilerAnchorBuffer_Front, -}; - -struct DN_Profiler -{ - DN_ProfilerAnchor anchors[2][DN_PROFILER_ANCHOR_BUFFER_SIZE]; - uint8_t active_anchor_buffer; - uint16_t parent_zone; -}; -#endif // !defined(DN_NO_PROFILER) - -// NOTE: [$JOBQ] DN_JobQueue /////////////////////////////////////////////////////////////////////// -typedef void (DN_JobQueueFunc)(DN_OSThread *thread, void *user_context); -struct DN_Job -{ - DN_JobQueueFunc *func; // The function to invoke for the job - void *user_context; // Pointer user can set to use in their `job_func` - uint64_t elapsed_tsc; - uint16_t user_tag; // Arbitrary value the user can set to identiy the type of `user_context` this job has - bool add_to_completion_queue; // When true, on job completion, job must be dequeued from the completion queue via `GetFinishedJobs` -}; - -#if !defined(DN_JOB_QUEUE_SPMC_SIZE) - #define DN_JOB_QUEUE_SPMC_SIZE 128 -#endif - -struct DN_JobQueueSPMC -{ - DN_OSMutex mutex; - DN_OSSemaphore thread_wait_for_job_semaphore; - DN_OSSemaphore wait_for_completion_semaphore; - DN_U32 threads_waiting_for_completion; - - DN_Job jobs[DN_JOB_QUEUE_SPMC_SIZE]; - DN_B32 quit; - DN_U32 quit_exit_code; - DN_U32 volatile read_index; - DN_U32 volatile finish_index; - DN_U32 volatile write_index; - - DN_OSSemaphore complete_queue_write_semaphore; - DN_Job complete_queue[DN_JOB_QUEUE_SPMC_SIZE]; - DN_U32 volatile complete_read_index; - DN_U32 volatile complete_write_index; -}; - -// NOTE: [$CORE] DN_Core ////////////////////////////////////////////////////////////////////////// -// Book-keeping data for the library and allow customisation of certain features -// provided. -struct DN_Core -{ - bool init; // True if the library has been initialised via `DN_Library_Init` - DN_OSMutex init_mutex; - DN_Str8 exe_dir; // The directory of the current executable - DN_Arena arena; - DN_Pool pool; - DN_ArenaCatalog arena_catalog; - bool slow_verification_checks; // Enable expensive library verification checks - DN_CPUReport cpu_report; - DN_TLS tls; // Thread local storage state for the main thread. - - // NOTE: Logging /////////////////////////////////////////////////////////////////////////////// - DN_LogProc * log_callback; // Set this pointer to override the logging routine - void * log_user_data; // User pointer passed into 'log_callback' - bool log_to_file; // Output logs to file as well as standard out - DN_OSFile log_file; // TODO(dn): Hmmm, how should we do this... ? - DN_TicketMutex log_file_mutex; // Is locked when instantiating the log_file for the first time - bool log_no_colour; // Disable colours in the logging output - - // NOTE: Memory //////////////////////////////////////////////////////////////////////////////// - // Total OS mem allocs in lifetime of program (e.g. malloc, VirtualAlloc, HeapAlloc ...). This - // only includes allocations routed through the library such as the growing nature of arenas or - // using the memory allocation routines in the library like DN_OS_MemCommit and so forth. - uint64_t mem_allocs_total; - uint64_t mem_allocs_frame; // Total OS mem allocs since the last 'DN_Core_FrameBegin' was invoked - - // NOTE: Leak Tracing ////////////////////////////////////////////////////////////////////////// - #if defined(DN_LEAK_TRACKING) - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_Arena alloc_table_arena; - #endif - - // NOTE: Win32 ///////////////////////////////////////////////////////////////////////////////// - #if defined(DN_OS_WIN32) - LARGE_INTEGER win32_qpc_frequency; - DN_TicketMutex win32_bcrypt_rng_mutex; - void * win32_bcrypt_rng_handle; - bool win32_sym_initialised; - #endif - - // NOTE: OS //////////////////////////////////////////////////////////////////////////////////// - uint32_t os_page_size; - uint32_t os_alloc_granularity; - - // NOTE: Profiler ////////////////////////////////////////////////////////////////////////////// - #if !defined(DN_NO_PROFILER) - DN_Profiler * profiler; - DN_Profiler profiler_default_instance; - #endif -}; - -enum DN_CoreOnInit -{ - DN_CoreOnInit_Nil = 0, - DN_CoreOnInit_LogLibFeatures = 1 << 0, - DN_CoreOnInit_LogCPUFeatures = 1 << 1, - DN_CoreOnInit_LogAllFeatures = DN_CoreOnInit_LogLibFeatures | DN_CoreOnInit_LogCPUFeatures, -}; - -// NOTE: [$PCGX] DN_PCG32 ///////////////////////////////////////////////////////////////////////// -DN_API DN_PCG32 DN_PCG32_Init (uint64_t seed); -DN_API uint32_t DN_PCG32_Next (DN_PCG32 *rng); -DN_API uint64_t DN_PCG32_Next64 (DN_PCG32 *rng); -DN_API uint32_t DN_PCG32_Range (DN_PCG32 *rng, uint32_t low, uint32_t high); -DN_API DN_F32 DN_PCG32_NextF32 (DN_PCG32 *rng); -DN_API DN_F64 DN_PCG32_NextF64 (DN_PCG32 *rng); -DN_API void DN_PCG32_Advance (DN_PCG32 *rng, uint64_t delta); - -#if !defined(DN_NO_JSON_BUILDER) -// NOTE: [$JSON] DN_JSONBuilder /////////////////////////////////////////////////////////////////// -#define DN_JSONBuilder_Object(builder) \ - DN_DEFER_LOOP(DN_JSONBuilder_ObjectBegin(builder), \ - DN_JSONBuilder_ObjectEnd(builder)) - -#define DN_JSONBuilder_ObjectNamed(builder, name) \ - DN_DEFER_LOOP(DN_JSONBuilder_ObjectBeginNamed(builder, name), \ - DN_JSONBuilder_ObjectEnd(builder)) - -#define DN_JSONBuilder_Array(builder) \ - DN_DEFER_LOOP(DN_JSONBuilder_ArrayBegin(builder), \ - DN_JSONBuilder_ArrayEnd(builder)) - -#define DN_JSONBuilder_ArrayNamed(builder, name) \ - DN_DEFER_LOOP(DN_JSONBuilder_ArrayBeginNamed(builder, name), \ - DN_JSONBuilder_ArrayEnd(builder)) - - -DN_API DN_JSONBuilder DN_JSONBuilder_Init (DN_Arena *arena, int spaces_per_indent); -DN_API DN_Str8 DN_JSONBuilder_Build (DN_JSONBuilder const *builder, DN_Arena *arena); -DN_API void DN_JSONBuilder_KeyValue (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); -DN_API void DN_JSONBuilder_KeyValueF (DN_JSONBuilder *builder, DN_Str8 key, char const *value_fmt, ...); -DN_API void DN_JSONBuilder_ObjectBeginNamed (DN_JSONBuilder *builder, DN_Str8 name); -DN_API void DN_JSONBuilder_ObjectEnd (DN_JSONBuilder *builder); -DN_API void DN_JSONBuilder_ArrayBeginNamed (DN_JSONBuilder *builder, DN_Str8 name); -DN_API void DN_JSONBuilder_ArrayEnd (DN_JSONBuilder *builder); -DN_API void DN_JSONBuilder_Str8Named (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); -DN_API void DN_JSONBuilder_LiteralNamed (DN_JSONBuilder *builder, DN_Str8 key, DN_Str8 value); -DN_API void DN_JSONBuilder_U64Named (DN_JSONBuilder *builder, DN_Str8 key, uint64_t value); -DN_API void DN_JSONBuilder_I64Named (DN_JSONBuilder *builder, DN_Str8 key, int64_t value); -DN_API void DN_JSONBuilder_F64Named (DN_JSONBuilder *builder, DN_Str8 key, double value, int decimal_places); -DN_API void DN_JSONBuilder_BoolNamed (DN_JSONBuilder *builder, DN_Str8 key, bool value); - -#define DN_JSONBuilder_ObjectBegin(builder) DN_JSONBuilder_ObjectBeginNamed(builder, DN_STR8("")) -#define DN_JSONBuilder_ArrayBegin(builder) DN_JSONBuilder_ArrayBeginNamed(builder, DN_STR8("")) -#define DN_JSONBuilder_Str8(builder, value) DN_JSONBuilder_Str8Named(builder, DN_STR8(""), value) -#define DN_JSONBuilder_Literal(builder, value) DN_JSONBuilder_LiteralNamed(builder, DN_STR8(""), value) -#define DN_JSONBuilder_U64(builder, value) DN_JSONBuilder_U64Named(builder, DN_STR8(""), value) -#define DN_JSONBuilder_I64(builder, value) DN_JSONBuilder_I64Named(builder, DN_STR8(""), value) -#define DN_JSONBuilder_F64(builder, value) DN_JSONBuilder_F64Named(builder, DN_STR8(""), value) -#define DN_JSONBuilder_Bool(builder, value) DN_JSONBuilder_BoolNamed(builder, DN_STR8(""), value) -#endif // !defined(DN_NO_JSON_BUILDER) - -// NOTE: [$BSEA] DN_BinarySearch ////////////////////////////////////////////////////////////////// -template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs); -template DN_BinarySearchResult DN_BinarySearch (T const *array, - DN_USize array_size, - T const &find, - DN_BinarySearchType type = DN_BinarySearchType_Match, - DN_BinarySearchLessThanProc less_than = DN_BinarySearch_DefaultLessThan); - -// NOTE: [$QSOR] DN_QSort ///////////////////////////////////////////////////////////////////////// -template bool DN_QSort_DefaultLessThan(T const &lhs, T const &rhs); -template void DN_QSort (T *array, - DN_USize array_size, - void *user_context, - DN_QSortLessThanProc less_than = DN_QSort_DefaultLessThan); - -// NOTE: [$BITS] DN_Bit /////////////////////////////////////////////////////////////////////////// -DN_API void DN_Bit_UnsetInplace (DN_USize *flags, DN_USize bitfield); -DN_API void DN_Bit_SetInplace (DN_USize *flags, DN_USize bitfield); -DN_API bool DN_Bit_IsSet (DN_USize bits, DN_USize bits_to_set); -DN_API bool DN_Bit_IsNotSet (DN_USize bits, DN_USize bits_to_check); -#define DN_Bit_ClearNextLSB(value) (value) & ((value) - 1) - -// NOTE: [$SAFE] DN_Safe ////////////////////////////////////////////////////////////////////////// -DN_API int64_t DN_Safe_AddI64 (int64_t a, int64_t b); -DN_API int64_t DN_Safe_MulI64 (int64_t a, int64_t b); - -DN_API uint64_t DN_Safe_AddU64 (uint64_t a, uint64_t b); -DN_API uint64_t DN_Safe_MulU64 (uint64_t a, uint64_t b); - -DN_API uint64_t DN_Safe_SubU64 (uint64_t a, uint64_t b); -DN_API uint32_t DN_Safe_SubU32 (uint32_t a, uint32_t b); - -DN_API int DN_Safe_SaturateCastUSizeToInt (DN_USize val); -DN_API int8_t DN_Safe_SaturateCastUSizeToI8 (DN_USize val); -DN_API int16_t DN_Safe_SaturateCastUSizeToI16 (DN_USize val); -DN_API int32_t DN_Safe_SaturateCastUSizeToI32 (DN_USize val); -DN_API int64_t DN_Safe_SaturateCastUSizeToI64 (DN_USize val); - -DN_API int DN_Safe_SaturateCastU64ToInt (uint64_t val); -DN_API int8_t DN_Safe_SaturateCastU8ToI8 (uint64_t val); -DN_API int16_t DN_Safe_SaturateCastU16ToI16 (uint64_t val); -DN_API int32_t DN_Safe_SaturateCastU32ToI32 (uint64_t val); -DN_API int64_t DN_Safe_SaturateCastU64ToI64 (uint64_t val); -DN_API unsigned int DN_Safe_SaturateCastU64ToUInt (uint64_t val); -DN_API uint8_t DN_Safe_SaturateCastU64ToU8 (uint64_t val); -DN_API uint16_t DN_Safe_SaturateCastU64ToU16 (uint64_t val); -DN_API uint32_t DN_Safe_SaturateCastU64ToU32 (uint64_t val); - -DN_API uint8_t DN_Safe_SaturateCastUSizeToU8 (DN_USize val); -DN_API uint16_t DN_Safe_SaturateCastUSizeToU16 (DN_USize val); -DN_API uint32_t DN_Safe_SaturateCastUSizeToU32 (DN_USize val); -DN_API uint64_t DN_Safe_SaturateCastUSizeToU64 (DN_USize val); - -DN_API int DN_Safe_SaturateCastISizeToInt (DN_ISize val); -DN_API int8_t DN_Safe_SaturateCastISizeToI8 (DN_ISize val); -DN_API int16_t DN_Safe_SaturateCastISizeToI16 (DN_ISize val); -DN_API int32_t DN_Safe_SaturateCastISizeToI32 (DN_ISize val); -DN_API int64_t DN_Safe_SaturateCastISizeToI64 (DN_ISize val); - -DN_API unsigned int DN_Safe_SaturateCastISizeToUInt (DN_ISize val); -DN_API uint8_t DN_Safe_SaturateCastISizeToU8 (DN_ISize val); -DN_API uint16_t DN_Safe_SaturateCastISizeToU16 (DN_ISize val); -DN_API uint32_t DN_Safe_SaturateCastISizeToU32 (DN_ISize val); -DN_API uint64_t DN_Safe_SaturateCastISizeToU64 (DN_ISize val); - -DN_API DN_ISize DN_Safe_SaturateCastI64ToISize (int64_t val); -DN_API int8_t DN_Safe_SaturateCastI64ToI8 (int64_t val); -DN_API int16_t DN_Safe_SaturateCastI64ToI16 (int64_t val); -DN_API int32_t DN_Safe_SaturateCastI64ToI32 (int64_t val); - -DN_API unsigned int DN_Safe_SaturateCastI64ToUInt (int64_t val); -DN_API DN_ISize DN_Safe_SaturateCastI64ToUSize (int64_t val); -DN_API uint8_t DN_Safe_SaturateCastI64ToU8 (int64_t val); -DN_API uint16_t DN_Safe_SaturateCastI64ToU16 (int64_t val); -DN_API uint32_t DN_Safe_SaturateCastI64ToU32 (int64_t val); -DN_API uint64_t DN_Safe_SaturateCastI64ToU64 (int64_t val); - -DN_API int8_t DN_Safe_SaturateCastIntToI8 (int val); -DN_API int16_t DN_Safe_SaturateCastIntToI16 (int val); -DN_API uint8_t DN_Safe_SaturateCastIntToU8 (int val); -DN_API uint16_t DN_Safe_SaturateCastIntToU16 (int val); -DN_API uint32_t DN_Safe_SaturateCastIntToU32 (int val); -DN_API uint64_t DN_Safe_SaturateCastIntToU64 (int val); - -// NOTE: [$MISC] Misc ////////////////////////////////////////////////////////////////////////////// -DN_API int DN_FmtBuffer3DotTruncate (char *buffer, int size, DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_U64Str8 DN_U64ToStr8 (uint64_t val, char separator); -DN_API DN_U64ByteSize DN_U64ToByteSize (uint64_t bytes, DN_U64ByteSizeType type); -DN_API DN_Str8 DN_U64ToByteSizeStr8 (DN_Arena *arena, uint64_t bytes, DN_U64ByteSizeType desired_type); -DN_API DN_Str8 DN_U64ByteSizeTypeString (DN_U64ByteSizeType type); -DN_API DN_Str8 DN_U64ToAge (DN_Arena *arena, uint64_t age_s, DN_U64AgeUnit unit); -DN_API DN_Str8 DN_F64ToAge (DN_Arena *arena, DN_F64 age_s, DN_U64AgeUnit unit); - -DN_API uint64_t DN_HexToU64 (DN_Str8 hex); -DN_API DN_Str8 DN_U64ToHex (DN_Arena *arena, uint64_t number, DN_U64HexStr8Flags flags); -DN_API DN_U64HexStr8 DN_U64ToHexStr8 (uint64_t number, uint32_t flags); - -DN_API bool DN_BytesToHexPtr (void const *src, DN_USize src_size, char *dest); -DN_API DN_Str8 DN_BytesToHex (DN_Arena *arena, void const *src, DN_USize size); -#define DN_BytesToHex_TLS(...) DN_BytesToHex(DN_TLS_TopArena(), __VA_ARGS__) - -DN_API DN_USize DN_HexToBytesPtrUnchecked (DN_Str8 hex, void *dest, DN_USize dest_size); -DN_API DN_USize DN_HexToBytesPtr (DN_Str8 hex, void *dest, DN_USize dest_size); -DN_API DN_Str8 DN_HexToBytesUnchecked (DN_Arena *arena, DN_Str8 hex); -#define DN_HexToBytesUnchecked_TLS(...) DN_HexToBytesUnchecked(DN_TLS_TopArena(), __VA_ARGS__) -DN_API DN_Str8 DN_HexToBytes (DN_Arena *arena, DN_Str8 hex); -#define DN_HexToBytes_TLS(...) DN_HexToBytes(DN_TLS_TopArena(), __VA_ARGS__) - -// NOTE: [$PROF] DN_Profiler ////////////////////////////////////////////////////////////////////// -DN_API DN_ProfilerAnchor * DN_Profiler_ReadBuffer (); -DN_API DN_ProfilerAnchor * DN_Profiler_WriteBuffer (); -#define DN_Profiler_BeginZone(name) DN_Profiler_BeginZoneAtIndex(DN_STR8(name), __COUNTER__ + 1) -DN_API DN_ProfilerZone DN_Profiler_BeginZoneAtIndex (DN_Str8 name, uint16_t anchor_index); -DN_API void DN_Profiler_EndZone (DN_ProfilerZone zone); -DN_API DN_ProfilerAnchor * DN_Profiler_AnchorBuffer (DN_ProfilerAnchorBuffer buffer); -DN_API void DN_Profiler_SwapAnchorBuffer (); -DN_API void DN_Profiler_Dump (uint64_t tsc_per_second); - -// NOTE: [$JOBQ] DN_JobQueue /////////////////////////////////////////////////////////////////////// -DN_API DN_JobQueueSPMC DN_OS_JobQueueSPMCInit (); -DN_API bool DN_OS_JobQueueSPMCCanAdd (DN_JobQueueSPMC const *queue, uint32_t count); -DN_API bool DN_OS_JobQueueSPMCAddArray (DN_JobQueueSPMC *queue, DN_Job *jobs, uint32_t count); -DN_API bool DN_OS_JobQueueSPMCAdd (DN_JobQueueSPMC *queue, DN_Job job); -DN_API void DN_OS_JobQueueSPMCWaitForCompletion (DN_JobQueueSPMC *queue); -DN_API int32_t DN_OS_JobQueueSPMCThread (DN_OSThread *thread); -DN_API DN_USize DN_OS_JobQueueSPMCGetFinishedJobs (DN_JobQueueSPMC *queue, DN_Job *jobs, DN_USize jobs_size); - -// NOTE: DN_Core /////////////////////////////////////////////////////////////////////////////// -DN_API void DN_Core_Init (DN_Core *core, DN_CoreOnInit on_init); -DN_API void DN_Core_BeginFrame (); -DN_API void DN_Core_SetPointer (DN_Core *core); -#if !defined(DN_NO_PROFILER) -DN_API void DN_Core_SetProfiler (DN_Profiler *profiler); -#endif -DN_API void DN_Core_SetLogCallback (DN_LogProc *proc, void *user_data); -DN_API void DN_Core_DumpThreadContextArenaStat (DN_Str8 file_path); -DN_API DN_Arena * DN_Core_AllocArenaF (DN_USize reserve, DN_USize commit, uint8_t arena_flags, char const *fmt, ...); -DN_API bool DN_Core_EraseArena (DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena); - -// NOTE: [$BSEA] DN_BinarySearch ////////////////////////////////////////////////////////////////// -template -bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) -{ - bool result = lhs < rhs; - return result; -} - -template -DN_BinarySearchResult DN_BinarySearch(T const *array, - DN_USize array_size, - T const &find, - DN_BinarySearchType type, - DN_BinarySearchLessThanProc less_than) -{ - DN_BinarySearchResult result = {}; - if (!array || array_size <= 0 || !less_than) - return result; - - T const *end = array + array_size; - T const *first = array; - T const *last = end; - while (first != last) { - DN_USize count = last - first; - T const *it = first + (count / 2); - - bool advance_first = false; - if (type == DN_BinarySearchType_UpperBound) - advance_first = !less_than(find, it[0]); - else - advance_first = less_than(it[0], find); - - if (advance_first) - first = it + 1; - else - last = it; - } - - switch (type) { - case DN_BinarySearchType_Match: { - result.found = first != end && !less_than(find, *first); - } break; - - case DN_BinarySearchType_LowerBound: /*FALLTHRU*/ - case DN_BinarySearchType_UpperBound: { - result.found = first != end; - } break; - } - - result.index = first - array; - return result; -} - -// NOTE: [$QSOR] DN_QSort ///////////////////////////////////////////////////////////////////////// -template -bool DN_QSort_DefaultLessThan(T const &lhs, T const &rhs, void *user_context) -{ - (void)user_context; - bool result = lhs < rhs; - return result; -} - -template -void DN_QSort(T *array, DN_USize array_size, void *user_context, DN_QSortLessThanProc less_than) -{ - if (!array || array_size <= 1 || !less_than) - return; - - // NOTE: Insertion Sort, under 24->32 is an optimal amount ///////////////////////////////////// - const DN_USize QSORT_THRESHOLD = 24; - if (array_size < QSORT_THRESHOLD) { - for (DN_USize item_to_insert_index = 1; item_to_insert_index < array_size; item_to_insert_index++) { - for (DN_USize index = 0; index < item_to_insert_index; index++) { - if (!less_than(array[index], array[item_to_insert_index], user_context)) { - T item_to_insert = array[item_to_insert_index]; - for (DN_USize i = item_to_insert_index; i > index; i--) - array[i] = array[i - 1]; - - array[index] = item_to_insert; - break; - } - } - } - return; - } - - // NOTE: Quick sort, under 24->32 is an optimal amount ///////////////////////////////////////// - DN_USize last_index = array_size - 1; - DN_USize pivot_index = array_size / 2; - DN_USize partition_index = 0; - DN_USize start_index = 0; - - // Swap pivot with last index, so pivot is always at the end of the array. - // This makes logic much simpler. - DN_SWAP(array[last_index], array[pivot_index]); - pivot_index = last_index; - - // 4^, 8, 7, 5, 2, 3, 6 - if (less_than(array[start_index], array[pivot_index], user_context)) - partition_index++; - start_index++; - - // 4, |8, 7, 5^, 2, 3, 6* - // 4, 5, |7, 8, 2^, 3, 6* - // 4, 5, 2, |8, 7, ^3, 6* - // 4, 5, 2, 3, |7, 8, ^6* - for (DN_USize index = start_index; index < last_index; index++) { - if (less_than(array[index], array[pivot_index], user_context)) { - DN_SWAP(array[partition_index], array[index]); - partition_index++; - } - } - - // Move pivot to right of partition - // 4, 5, 2, 3, |6, 8, ^7* - DN_SWAP(array[partition_index], array[pivot_index]); - DN_QSort(array, partition_index, user_context, less_than); - - // Skip the value at partion index since that is guaranteed to be sorted. - // 4, 5, 2, 3, (x), 8, 7 - DN_USize one_after_partition_index = partition_index + 1; - DN_QSort(array + one_after_partition_index, (array_size - one_after_partition_index), user_context, less_than); -} diff --git a/dqn_math.cpp b/dqn_math.cpp deleted file mode 100644 index bc02eb7..0000000 --- a/dqn_math.cpp +++ /dev/null @@ -1,1572 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$\ $$$$$$$$\ $$\ $$\ -// $$$\ $$$ |$$ __$$\\__$$ __|$$ | $$ | -// $$$$\ $$$$ |$$ / $$ | $$ | $$ | $$ | -// $$\$$\$$ $$ |$$$$$$$$ | $$ | $$$$$$$$ | -// $$ \$$$ $$ |$$ __$$ | $$ | $$ __$$ | -// $$ |\$ /$$ |$$ | $$ | $$ | $$ | $$ | -// $$ | \_/ $$ |$$ | $$ | $$ | $$ | $$ | -// \__| \__|\__| \__| \__| \__| \__| -// -// dqn_math.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -#if !defined(DN_NO_V2) -// NOTE: [$VEC2] Vector2 /////////////////////////////////////////////////////////////////////////// -// NOTE: DN_V2I32 -DN_API bool operator==(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator!=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator>=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2I32 lhs, DN_V2I32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -DN_API DN_V2I32 operator-(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2I32 operator-(DN_V2I32 lhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(-lhs.x, -lhs.y); - return result; -} - -DN_API DN_V2I32 operator+(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, DN_F32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2I32 operator*(DN_V2I32 lhs, int32_t rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_V2I32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, DN_F32 rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2I32 operator/(DN_V2I32 lhs, int32_t rhs) -{ - DN_V2I32 result = DN_V2I32_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator*=(DN_V2I32 &lhs, int32_t rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator/=(DN_V2I32 &lhs, int32_t rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2I32 &operator-=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2I32 &operator+=(DN_V2I32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2I32 DN_V2I32_Min(DN_V2I32 a, DN_V2I32 b) -{ - DN_V2I32 result = DN_V2I32_Init2N(DN_MIN(a.x, b.x), DN_MIN(a.y, b.y)); - return result; -} - -DN_API DN_V2I32 DN_V2I32_Max(DN_V2I32 a, DN_V2I32 b) -{ - DN_V2I32 result = DN_V2I32_Init2N(DN_MAX(a.x, b.x), DN_MAX(a.y, b.y)); - return result; -} - -DN_API DN_V2I32 DN_V2I32_Abs(DN_V2I32 a) -{ - DN_V2I32 result = DN_V2I32_Init2N(DN_ABS(a.x), DN_ABS(a.y)); - return result; -} - -// NOTE: DN_V2U16 -DN_API bool operator!=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator>=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2U16 lhs, DN_V2U16 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -DN_API DN_V2U16 operator-(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2U16 operator+(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, DN_F32 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2U16 operator*(DN_V2U16 lhs, int32_t rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_V2U16 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, DN_F32 rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2U16 operator/(DN_V2U16 lhs, int32_t rhs) -{ - DN_V2U16 result = DN_V2U16_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator*=(DN_V2U16 &lhs, int32_t rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator/=(DN_V2U16 &lhs, int32_t rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2U16 &operator-=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2U16 &operator+=(DN_V2U16 &lhs, DN_V2U16 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -// NOTE: DN_V2 -DN_API bool operator!=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y); - return result; -} - -DN_API bool operator>=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y); - return result; -} - -DN_API bool operator<=(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y); - return result; -} - -DN_API bool operator<(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y); - return result; -} - -DN_API bool operator>(DN_V2F32 lhs, DN_V2F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y); - return result; -} - -// NOTE: DN_V2F32 operator- ////////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 operator-(DN_V2F32 lhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(-lhs.x, -lhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs.x, lhs.y - rhs.y); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs, lhs.y - rhs); - return result; -} - -DN_API DN_V2F32 operator-(DN_V2F32 lhs, int32_t rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x - rhs, lhs.y - rhs); - return result; -} - -// NOTE: DN_V2F32 operator+ ////////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs.x, lhs.y + rhs.y); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs, lhs.y + rhs); - return result; -} - -DN_API DN_V2F32 operator+(DN_V2F32 lhs, int32_t rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x + rhs, lhs.y + rhs); - return result; -} - -// NOTE: DN_V2F32 operator* ////////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs.x, lhs.y * rhs.y); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -DN_API DN_V2F32 operator*(DN_V2F32 lhs, int32_t rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x * rhs, lhs.y * rhs); - return result; -} - -// NOTE: DN_V2F32 operator/ ////////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_V2I32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs.x, lhs.y / rhs.y); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, DN_F32 rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -DN_API DN_V2F32 operator/(DN_V2F32 lhs, int32_t rhs) -{ - DN_V2F32 result = DN_V2F32_Init2N(lhs.x / rhs, lhs.y / rhs); - return result; -} - -// NOTE: DN_V2F32 operator*/ ///////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V2F32 &operator*=(DN_V2F32 &lhs, int32_t rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -// NOTE: DN_V2F32 operator// ///////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V2F32 &operator/=(DN_V2F32 &lhs, int32_t rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -// NOTE: DN_V2F32 operator-/ ///////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V2F32 &operator-=(DN_V2F32 &lhs, int32_t rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -// NOTE: DN_V2F32 operator+/ ///////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_V2I32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, DN_F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 &operator+=(DN_V2F32 &lhs, int32_t rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_V2F32 DN_V2F32_Min(DN_V2F32 a, DN_V2F32 b) -{ - DN_V2F32 result = DN_V2F32_Init2N(DN_MIN(a.x, b.x), DN_MIN(a.y, b.y)); - return result; -} - -DN_API DN_V2F32 DN_V2F32_Max(DN_V2F32 a, DN_V2F32 b) -{ - DN_V2F32 result = DN_V2F32_Init2N(DN_MAX(a.x, b.x), DN_MAX(a.y, b.y)); - return result; -} - -DN_API DN_V2F32 DN_V2F32_Abs(DN_V2F32 a) -{ - DN_V2F32 result = DN_V2F32_Init2N(DN_ABS(a.x), DN_ABS(a.y)); - return result; -} - -DN_API DN_F32 DN_V2F32_Dot(DN_V2F32 a, DN_V2F32 b) -{ - // NOTE: Scalar projection of B onto A ///////////////////////////////////////////////////////// - // - // Scalar projection calculates the signed distance between `b` and `a` - // where `a` is a unit vector then, the dot product calculates the projection - // of `b` onto the infinite line that the direction of `a` represents. This - // calculation is the signed distance. - // - // signed_distance = dot_product(a, b) = (a.x * b.x) + (a.y * b.y) - // - // Y - // ^ b - // | /| - // | / | - // | / | - // | / | Projection - // | / | - // |/ V - // +--->--------> X - // . a . - // . . - // |------| <- Calculated signed distance - // - // The signed-ness of the result indicates the relationship: - // - // Distance <0 means `b` is behind `a` - // Distance >0 means `b` is in-front of `a` - // Distance ==0 means `b` is perpendicular to `a` - // - // If `a` is not normalized then the signed-ness of the result still holds - // however result no longer represents the actual distance between the - // 2 objects. One of the vectors must be normalised (e.g. turned into a unit - // vector). - // - // NOTE: Vector projection ///////////////////////////////////////////////////////////////////// - // - // Vector projection calculates the exact X,Y coordinates of where `b` meets - // `a` when it was projected. This is calculated by multipying the - // 'scalar projection' result by the unit vector of `a` - // - // vector_projection = a * signed_distance = a * dot_product(a, b) - - DN_F32 result = (a.x * b.x) + (a.y * b.y); - return result; -} - -DN_API DN_F32 DN_V2F32_LengthSq_V2x2(DN_V2F32 lhs, DN_V2F32 rhs) -{ - // NOTE: Pythagoras's theorem (a^2 + b^2 = c^2) without the square root - DN_F32 a = rhs.x - lhs.x; - DN_F32 b = rhs.y - lhs.y; - DN_F32 c_squared = DN_SQUARED(a) + DN_SQUARED(b); - DN_F32 result = c_squared; - return result; -} - -DN_API DN_F32 DN_V2F32_Length_V2x2(DN_V2F32 lhs, DN_V2F32 rhs) -{ - DN_F32 result_squared = DN_V2F32_LengthSq_V2x2(lhs, rhs); - DN_F32 result = DN_SQRTF(result_squared); - return result; -} - -DN_API DN_F32 DN_V2F32_LengthSq(DN_V2F32 lhs) -{ - // NOTE: Pythagoras's theorem without the square root - DN_F32 c_squared = DN_SQUARED(lhs.x) + DN_SQUARED(lhs.y); - DN_F32 result = c_squared; - return result; -} - -DN_API DN_F32 DN_V2F32_Length(DN_V2F32 lhs) -{ - DN_F32 c_squared = DN_V2F32_LengthSq(lhs); - DN_F32 result = DN_SQRTF(c_squared); - return result; -} - -DN_API DN_V2F32 DN_V2F32_Normalise(DN_V2F32 a) -{ - DN_F32 length = DN_V2F32_Length(a); - DN_V2F32 result = a / length; - return result; -} - -DN_API DN_V2F32 DN_V2F32_Perpendicular(DN_V2F32 a) -{ - // NOTE: Matrix form of a 2D vector can be defined as - // - // x' = x cos(t) - y sin(t) - // y' = x sin(t) + y cos(t) - // - // Calculate a line perpendicular to a vector means rotating the vector by - // 90 degrees - // - // x' = x cos(90) - y sin(90) - // y' = x sin(90) + y cos(90) - // - // Where `cos(90) = 0` and `sin(90) = 1` then, - // - // x' = -y - // y' = +x - - DN_V2F32 result = DN_V2F32_Init2N(-a.y, a.x); - return result; -} - -DN_API DN_V2F32 DN_V2F32_Reflect(DN_V2F32 in, DN_V2F32 surface) -{ - DN_V2F32 normal = DN_V2F32_Perpendicular(surface); - DN_V2F32 normal_norm = DN_V2F32_Normalise(normal); - DN_F32 signed_dist = DN_V2F32_Dot(in, normal_norm); - DN_V2F32 result = DN_V2F32_Init2N(in.x, in.y + (-signed_dist * 2.f)); - return result; -} - -DN_API DN_F32 DN_V2F32_Area(DN_V2F32 a) -{ - DN_F32 result = a.w * a.h; - return result; -} -#endif // !defined(DN_NO_V2) - -#if !defined(DN_NO_V3) -// NOTE: [$VEC3] Vector3 /////////////////////////////////////////////////////////////////////////// -DN_API bool operator!=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator==(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z); - return result; -} - -DN_API bool operator>=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z); - return result; -} - -DN_API bool operator<=(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z); - return result; -} - -DN_API bool operator< (DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z); - return result; -} - -DN_API bool operator>(DN_V3F32 lhs, DN_V3F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z); - return result; -} - -DN_API DN_V3F32 operator-(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); - return result; -} - -DN_API DN_V3F32 operator-(DN_V3F32 lhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(-lhs.x, -lhs.y, -lhs.z); - return result; -} - -DN_API DN_V3F32 operator+(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, DN_F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); - return result; -} - -DN_API DN_V3F32 operator*(DN_V3F32 lhs, int32_t rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_V3F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, DN_F32 rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); - return result; -} - -DN_API DN_V3F32 operator/(DN_V3F32 lhs, int32_t rhs) -{ - DN_V3F32 result = DN_V3F32_Init3F32(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs); - return result; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator*=(DN_V3F32 &lhs, int32_t rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, DN_F32 rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator/=(DN_V3F32 &lhs, int32_t rhs) -{ - lhs = lhs / rhs; - return lhs; -} - -DN_API DN_V3F32 &operator-=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V3F32 &operator+=(DN_V3F32 &lhs, DN_V3F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_F32 DN_V3_LengthSq(DN_V3F32 a) -{ - DN_F32 result = DN_SQUARED(a.x) + DN_SQUARED(a.y) + DN_SQUARED(a.z); - return result; -} - -DN_API DN_F32 DN_V3_Length(DN_V3F32 a) -{ - DN_F32 length_sq = DN_SQUARED(a.x) + DN_SQUARED(a.y) + DN_SQUARED(a.z); - DN_F32 result = DN_SQRTF(length_sq); - return result; -} - -DN_API DN_V3F32 DN_V3_Normalise(DN_V3F32 a) -{ - DN_F32 length = DN_V3_Length(a); - DN_V3F32 result = a / length; - return result; -} -#endif // !defined(DN_NO_V3) - -#if !defined(DN_NO_V4) -// NOTE: [$VEC4] Vector4 /////////////////////////////////////////////////////////////////////////// -DN_API bool operator==(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x == rhs.x) && (lhs.y == rhs.y) && (lhs.z == rhs.z) && (lhs.w == rhs.w); - return result; -} - -DN_API bool operator!=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API bool operator>=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x >= rhs.x) && (lhs.y >= rhs.y) && (lhs.z >= rhs.z) && (lhs.w >= rhs.w); - return result; -} - -DN_API bool operator<=(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z) && (lhs.w <= rhs.w); - return result; -} - -DN_API bool operator< (DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z) && (lhs.w < rhs.w); - return result; -} - -DN_API bool operator>(DN_V4F32 lhs, DN_V4F32 rhs) -{ - bool result = (lhs.x > rhs.x) && (lhs.y > rhs.y) && (lhs.z > rhs.z) && (lhs.w > rhs.w); - return result; -} - -DN_API DN_V4F32 operator-(DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); - return result; -} - -DN_API DN_V4F32 operator-(DN_V4F32 lhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(-lhs.x, -lhs.y, -lhs.z, -lhs.w); - return result; -} - -DN_API DN_V4F32 operator+(DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); - return result; -} - -DN_API DN_V4F32 operator* (DN_V4F32 lhs, DN_V4F32 rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); - return result; -} - -DN_API DN_V4F32 operator*(DN_V4F32 lhs, DN_F32 rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); - return result; -} - -DN_API DN_V4F32 operator*(DN_V4F32 lhs, int32_t rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); - return result; -} - -DN_API DN_V4F32 operator/(DN_V4F32 lhs, DN_F32 rhs) -{ - DN_V4F32 result = DN_V4F32_Init4N(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); - return result; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, DN_F32 rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator*=(DN_V4F32 &lhs, int32_t rhs) -{ - lhs = lhs * rhs; - return lhs; -} - -DN_API DN_V4F32 &operator-=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs - rhs; - return lhs; -} - -DN_API DN_V4F32 &operator+=(DN_V4F32 &lhs, DN_V4F32 rhs) -{ - lhs = lhs + rhs; - return lhs; -} - -DN_API DN_F32 DN_V4F32Dot(DN_V4F32 a, DN_V4F32 b) -{ - DN_F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); - return result; -} -#endif // !defined(DN_NO_V4) - -#if !defined(DN_NO_M4) -// NOTE: [$MAT4] DN_M4 //////////////////////////////////////////////////////////////////////////// -DN_API DN_M4 DN_M4_Identity() -{ - DN_M4 result = - {{ - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_ScaleF(DN_F32 x, DN_F32 y, DN_F32 z) -{ - DN_M4 result = - {{ - {x, 0, 0, 0}, - {0, y, 0, 0}, - {0, 0, z, 0}, - {0, 0, 0, 1}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Scale(DN_V3F32 xyz) -{ - DN_M4 result = - {{ - {xyz.x, 0, 0, 0}, - {0, xyz.y, 0, 0}, - {0, 0, xyz.z, 0}, - {0, 0, 0, 1}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_TranslateF(DN_F32 x, DN_F32 y, DN_F32 z) -{ - DN_M4 result = - {{ - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {x, y, z, 1}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Translate(DN_V3F32 xyz) -{ - DN_M4 result = - {{ - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {xyz.x, xyz.y, xyz.z, 1}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Transpose(DN_M4 mat) -{ - DN_M4 result = {}; - for (int col = 0; col < 4; col++) - for (int row = 0; row < 4; row++) - result.columns[col][row] = mat.columns[row][col]; - return result; -} - -DN_API DN_M4 DN_M4_Rotate(DN_V3F32 axis01, DN_F32 radians) -{ - DN_ASSERTF(DN_ABS(DN_V3_Length(axis01) - 1.f) <= 0.01f, - "Rotation axis must be normalised, length = %f", - DN_V3_Length(axis01)); - - DN_F32 sin = DN_SINF(radians); - DN_F32 cos = DN_COSF(radians); - DN_F32 one_minus_cos = 1.f - cos; - - DN_F32 x = axis01.x; - DN_F32 y = axis01.y; - DN_F32 z = axis01.z; - DN_F32 x2 = DN_SQUARED(x); - DN_F32 y2 = DN_SQUARED(y); - DN_F32 z2 = DN_SQUARED(z); - - DN_M4 result = - {{ - {cos + x2*one_minus_cos, y*x*one_minus_cos + z*sin, z*x*one_minus_cos - y*sin, 0}, // Col 1 - {x*y*one_minus_cos - z*sin, cos + y2*one_minus_cos, z*y*one_minus_cos + x*sin, 0}, // Col 2 - {x*z*one_minus_cos + y*sin, y*z*one_minus_cos - x*sin, cos + z2*one_minus_cos, 0}, // Col 3 - {0, 0, 0, 1}, // Col 4 - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Orthographic(DN_F32 left, DN_F32 right, DN_F32 bottom, DN_F32 top, DN_F32 z_near, DN_F32 z_far) -{ - // NOTE: Here is the matrix in column major for readability. Below it's - // transposed due to how you have to declare column major matrices in C/C++. - // - // m = [2/r-l, 0, 0, -1*(r+l)/(r-l)] - // [0, 2/t-b, 0, 1*(t+b)/(t-b)] - // [0, 0, -2/f-n, -1*(f+n)/(f-n)] - // [0, 0, 0, 1 ] - - DN_M4 result = - {{ - {2.f / (right - left), 0.f, 0.f, 0.f}, - {0.f, 2.f / (top - bottom), 0.f, 0.f}, - {0.f, 0.f, -2.f / (z_far - z_near), 0.f}, - {(-1.f * (right + left)) / (right - left), (-1.f * (top + bottom)) / (top - bottom), (-1.f * (z_far + z_near)) / (z_far - z_near), 1.f}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Perspective(DN_F32 fov /*radians*/, DN_F32 aspect, DN_F32 z_near, DN_F32 z_far) -{ - DN_F32 tan_fov = DN_TANF(fov / 2.f); - DN_M4 result = - {{ - {1.f / (aspect * tan_fov), 0.f, 0.f, 0.f}, - {0, 1.f / tan_fov, 0.f, 0.f}, - {0.f, 0.f, (z_near + z_far) / (z_near - z_far), -1.f}, - {0.f, 0.f, (2.f * z_near * z_far)/(z_near - z_far), 0.f}, - }}; - - return result; -} - -DN_API DN_M4 DN_M4_Add(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] + rhs.columns[col][it]; - } - return result; -} - -DN_API DN_M4 DN_M4_Sub(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] - rhs.columns[col][it]; - } - return result; -} - -DN_API DN_M4 DN_M4_Mul(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int row = 0; row < 4; row++) - { - DN_F32 sum = 0; - for (int f32_it = 0; f32_it < 4; f32_it++) - sum += lhs.columns[f32_it][row] * rhs.columns[col][f32_it]; - - result.columns[col][row] = sum; - } - } - return result; -} - -DN_API DN_M4 DN_M4_Div(DN_M4 lhs, DN_M4 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] / rhs.columns[col][it]; - } - return result; -} - -DN_API DN_M4 DN_M4_AddF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] + rhs; - } - return result; -} - -DN_API DN_M4 DN_M4_SubF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] - rhs; - } - return result; -} - -DN_API DN_M4 DN_M4_MulF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] * rhs; - } - return result; -} - -DN_API DN_M4 DN_M4_DivF(DN_M4 lhs, DN_F32 rhs) -{ - DN_M4 result; - for (int col = 0; col < 4; col++) - { - for (int it = 0; it < 4; it++) - result.columns[col][it] = lhs.columns[col][it] / rhs; - } - return result; -} - -#if !defined(DN_NO_FSTR8) -DN_API DN_FStr8<256> DN_M4_ColumnMajorString(DN_M4 mat) -{ - DN_FStr8<256> result = {}; - for (int row = 0; row < 4; row++) { - for (int it = 0; it < 4; it++) { - if (it == 0) DN_FStr8_Add(&result, DN_STR8("|")); - DN_FStr8_AddF(&result, "%.5f", mat.columns[it][row]); - if (it != 3) DN_FStr8_Add(&result, DN_STR8(", ")); - else DN_FStr8_Add(&result, DN_STR8("|\n")); - } - } - - return result; -} -#endif -#endif // !defined(DN_M4) - -// NOTE: [$M2x3] DN_M2x3 ////////////////////////////////////////////////////////////////////////// -DN_API bool operator==(DN_M2x3 const &lhs, DN_M2x3 const &rhs) -{ - bool result = DN_MEMCMP(lhs.e, rhs.e, sizeof(lhs.e[0]) * DN_ARRAY_UCOUNT(lhs.e)) == 0; - return result; -} - -DN_API bool operator!=(DN_M2x3 const &lhs, DN_M2x3 const &rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -DN_API DN_M2x3 DN_M2x3_Identity() -{ - DN_M2x3 result = {{ - 1, 0, 0, - 0, 1, 0, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3_Translate(DN_V2F32 offset) -{ - DN_M2x3 result = {{ - 1, 0, offset.x, - 0, 1, offset.y, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3_Scale(DN_V2F32 scale) -{ - DN_M2x3 result = {{ - scale.x, 0, 0, - 0, scale.y, 0, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3_Rotate(DN_F32 radians) -{ - DN_M2x3 result = {{ - DN_COSF(radians), DN_SINF(radians), 0, - -DN_SINF(radians), DN_COSF(radians), 0, - }}; - return result; -} - -DN_API DN_M2x3 DN_M2x3_Mul(DN_M2x3 m1, DN_M2x3 m2) -{ - // NOTE: Ordinarily you can't multiply M2x3 with M2x3 because column count - // (3) != row count (2). We pretend we have two 3x3 matrices with the last - // row set to [0 0 1] and perform a 3x3 matrix multiply. - // - // | (0)a (1)b (2)c | | (0)g (1)h (2)i | - // | (3)d (4)e (5)f | x | (3)j (4)k (5)l | - // | (6)0 (7)0 (8)1 | | (6)0 (7)0 (8)1 | - - DN_M2x3 result = {{ - m1.e[0]*m2.e[0] + m1.e[1]*m2.e[3], // a*g + b*j + c*0[omitted], - m1.e[0]*m2.e[1] + m1.e[1]*m2.e[4], // a*h + b*k + c*0[omitted], - m1.e[0]*m2.e[2] + m1.e[1]*m2.e[5] + m1.e[2], // a*i + b*l + c*1, - - m1.e[3]*m2.e[0] + m1.e[4]*m2.e[3], // d*g + e*j + f*0[omitted], - m1.e[3]*m2.e[1] + m1.e[4]*m2.e[4], // d*h + e*k + f*0[omitted], - m1.e[3]*m2.e[2] + m1.e[4]*m2.e[5] + m1.e[5], // d*i + e*l + f*1, - }}; - - return result; -} - -DN_API DN_V2F32 DN_M2x3_Mul2F32(DN_M2x3 m1, DN_F32 x, DN_F32 y) -{ - // NOTE: Ordinarily you can't multiply M2x3 with V2 because column count (3) - // != row count (2). We pretend we have a V3 with `z` set to `1`. - // - // | (0)a (1)b (2)c | | x | - // | (3)d (4)e (5)f | x | y | - // | 1 | - - DN_V2F32 result = {{ - m1.e[0]*x + m1.e[1]*y + m1.e[2], // a*x + b*y + c*1 - m1.e[3]*x + m1.e[4]*y + m1.e[5], // d*x + e*y + f*1 - }}; - return result; -} - -DN_API DN_V2F32 DN_M2x3_MulV2(DN_M2x3 m1, DN_V2F32 v2) -{ - DN_V2F32 result = DN_M2x3_Mul2F32(m1, v2.x, v2.y); - return result; -} - -#if !defined(DN_NO_RECT) -// NOTE: [$RECT] DN_Rect ////////////////////////////////////////////////////////////////////////// -DN_API bool operator==(const DN_Rect& lhs, const DN_Rect& rhs) -{ - bool result = (lhs.pos == rhs.pos) && (lhs.size == rhs.size); - return result; -} - -DN_API DN_V2F32 DN_Rect_Center(DN_Rect rect) -{ - DN_V2F32 result = rect.pos + (rect.size * .5f); - return result; -} - -DN_API bool DN_Rect_ContainsPoint(DN_Rect rect, DN_V2F32 p) -{ - DN_V2F32 min = rect.pos; - DN_V2F32 max = rect.pos + rect.size; - bool result = (p.x >= min.x && p.x <= max.x && p.y >= min.y && p.y <= max.y); - return result; -} - -DN_API bool DN_Rect_ContainsRect(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - bool result = (b_min >= a_min && b_max <= a_max); - return result; -} - -DN_API DN_Rect DN_Rect_Expand(DN_Rect a, DN_F32 amount) -{ - DN_Rect result = a; - result.pos -= amount; - result.size += (amount * 2.f); - return result; -} - -DN_API DN_Rect DN_Rect_ExpandV2(DN_Rect a, DN_V2F32 amount) -{ - DN_Rect result = a; - result.pos -= amount; - result.size += (amount * 2.f); - return result; -} - -DN_API bool DN_Rect_Intersects(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - bool result = (a_min.x <= b_max.x && a_max.x >= b_min.x) && - (a_min.y <= b_max.y && a_max.y >= b_min.y); - return result; -} - -DN_API DN_Rect DN_Rect_Intersection(DN_Rect a, DN_Rect b) -{ - DN_Rect result = DN_Rect_Init2V2(a.pos, DN_V2F32_Init1N(0)); - if (DN_Rect_Intersects(a, b)) { - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - - DN_V2F32 min = {}; - DN_V2F32 max = {}; - min.x = DN_MAX(a_min.x, b_min.x); - min.y = DN_MAX(a_min.y, b_min.y); - max.x = DN_MIN(a_max.x, b_max.x); - max.y = DN_MIN(a_max.y, b_max.y); - result = DN_Rect_Init2V2(min, max - min); - } - return result; -} - -DN_API DN_Rect DN_Rect_Union(DN_Rect a, DN_Rect b) -{ - DN_V2F32 a_min = a.pos; - DN_V2F32 a_max = a.pos + a.size; - DN_V2F32 b_min = b.pos; - DN_V2F32 b_max = b.pos + b.size; - - DN_V2F32 min, max; - min.x = DN_MIN(a_min.x, b_min.x); - min.y = DN_MIN(a_min.y, b_min.y); - max.x = DN_MAX(a_max.x, b_max.x); - max.y = DN_MAX(a_max.y, b_max.y); - DN_Rect result = DN_Rect_Init2V2(min, max - min); - return result; -} - -DN_API DN_RectMinMax DN_Rect_MinMax(DN_Rect a) -{ - DN_RectMinMax result = {}; - result.min = a.pos; - result.max = a.pos + a.size; - return result; -} - -DN_API DN_F32 DN_Rect_Area(DN_Rect a) -{ - DN_F32 result = a.size.w * a.size.h; - return result; -} - -DN_API DN_Rect DN_Rect_CutLeftClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_x = rect->pos.x; - DN_F32 max_x = rect->pos.x + rect->size.w; - DN_F32 result_max_x = min_x + amount; - if (clip) - result_max_x = DN_MIN(result_max_x, max_x); - DN_Rect result = DN_Rect_Init4N(min_x, rect->pos.y, result_max_x - min_x, rect->size.h); - rect->pos.x = result_max_x; - rect->size.w = max_x - result_max_x; - return result; -} - -DN_API DN_Rect DN_Rect_CutRightClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_x = rect->pos.x; - DN_F32 max_x = rect->pos.x + rect->size.w; - DN_F32 result_min_x = max_x - amount; - if (clip) - result_min_x = DN_MAX(result_min_x, 0); - DN_Rect result = DN_Rect_Init4N(result_min_x, rect->pos.y, max_x - result_min_x, rect->size.h); - rect->size.w = result_min_x - min_x; - return result; -} - -DN_API DN_Rect DN_Rect_CutTopClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_y = rect->pos.y; - DN_F32 max_y = rect->pos.y + rect->size.h; - DN_F32 result_max_y = min_y + amount; - if (clip) - result_max_y = DN_MIN(result_max_y, max_y); - DN_Rect result = DN_Rect_Init4N(rect->pos.x, min_y, rect->size.w, result_max_y - min_y); - rect->pos.y = result_max_y; - rect->size.h = max_y - result_max_y; - return result; -} - -DN_API DN_Rect DN_Rect_CutBottomClip(DN_Rect *rect, DN_F32 amount, DN_RectCutClip clip) -{ - DN_F32 min_y = rect->pos.y; - DN_F32 max_y = rect->pos.y + rect->size.h; - DN_F32 result_min_y = max_y - amount; - if (clip) - result_min_y = DN_MAX(result_min_y, 0); - DN_Rect result = DN_Rect_Init4N(rect->pos.x, result_min_y, rect->size.w, max_y - result_min_y); - rect->size.h = result_min_y - min_y; - return result; -} - -DN_API DN_Rect DN_RectCut_Cut(DN_RectCut rect_cut, DN_V2F32 size, DN_RectCutClip clip) -{ - DN_Rect result = {}; - if (rect_cut.rect) { - switch (rect_cut.side) { - case DN_RectCutSide_Left: result = DN_Rect_CutLeftClip(rect_cut.rect, size.w, clip); break; - case DN_RectCutSide_Right: result = DN_Rect_CutRightClip(rect_cut.rect, size.w, clip); break; - case DN_RectCutSide_Top: result = DN_Rect_CutTopClip(rect_cut.rect, size.h, clip); break; - case DN_RectCutSide_Bottom: result = DN_Rect_CutBottomClip(rect_cut.rect, size.h, clip); break; - } - } - return result; -} - -DN_API DN_V2F32 DN_Rect_InterpolatedPoint(DN_Rect rect, DN_V2F32 t01) -{ - DN_V2F32 result = DN_V2F32_Init2N(rect.pos.w + (rect.size.w * t01.x), - rect.pos.h + (rect.size.h * t01.y)); - return result; -} - -DN_API DN_V2F32 DN_Rect_TopLeft(DN_Rect rect) -{ - DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(0, 0)); - return result; -} - -DN_API DN_V2F32 DN_Rect_TopRight(DN_Rect rect) -{ - DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(1, 0)); - return result; -} - -DN_API DN_V2F32 DN_Rect_BottomLeft(DN_Rect rect) -{ - DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(0, 1)); - return result; -} - -DN_API DN_V2F32 DN_Rect_BottomRight(DN_Rect rect) -{ - DN_V2F32 result = DN_Rect_InterpolatedPoint(rect, DN_V2F32_Init2N(1, 1)); - return result; -} -#endif // !defined(DN_NO_RECT) - -// NOTE: [$MATH] Raycast /////////////////////////////////////////////////////////////////////////// - -DN_API DN_RaycastLineIntersectV2Result DN_Raycast_LineIntersectV2(DN_V2F32 origin_a, DN_V2F32 dir_a, DN_V2F32 origin_b, DN_V2F32 dir_b) -{ - // NOTE: Parametric equation of a line - // - // p = o + (t*d) - // - // - o is the starting 2d point - // - d is the direction of the line - // - t is a scalar that scales along the direction of the point - // - // To determine if a ray intersections a ray, we want to solve - // - // (o_a + (t_a * d_a)) = (o_b + (t_b * d_b)) - // - // Where '_a' and '_b' represent the 1st and 2nd point's origin, direction - // and 't' components respectively. This is 2 equations with 2 unknowns - // (`t_a` and `t_b`) which we can solve for by expressing the equation in - // terms of `t_a` and `t_b`. - // - // Working that math out produces the formula below for 't'. - - DN_RaycastLineIntersectV2Result result = {}; - DN_F32 denominator = ((dir_b.y * dir_a.x) - (dir_b.x * dir_a.y)); - if (denominator != 0.0f) { - result.t_a = (((origin_a.y - origin_b.y) * dir_b.x) + ((origin_b.x - origin_a.x) * dir_b.y)) / denominator; - result.t_b = (((origin_a.y - origin_b.y) * dir_a.x) + ((origin_b.x - origin_a.x) * dir_a.y)) / denominator; - result.hit = true; - } - return result; -} - -// NOTE: [$MATH] Other ///////////////////////////////////////////////////////////////////////////// -DN_API DN_V2F32 DN_Lerp_V2F32(DN_V2F32 a, DN_F32 t, DN_V2F32 b) -{ - DN_V2F32 result = {}; - result.x = a.x + ((b.x - a.x) * t); - result.y = a.y + ((b.y - a.y) * t); - return result; -} - -DN_API DN_F32 DN_Lerp_F32(DN_F32 a, DN_F32 t, DN_F32 b) -{ - DN_F32 result = a + ((b - a) * t); - return result; -} diff --git a/dqn_os.cpp b/dqn_os.cpp deleted file mode 100644 index 4d48712..0000000 --- a/dqn_os.cpp +++ /dev/null @@ -1,544 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ -// $$ / $$ |$$ / \__| -// $$ | $$ |\$$$$$$\ -// $$ | $$ | \____$$\ -// $$ | $$ |$$\ $$ | -// $$$$$$ |\$$$$$$ | -// \______/ \______/ -// -// dqn_os.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// -DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8(DN_OSDateTime time, char date_separator, char hms_separator) -{ - DN_OSDateTimeStr8 result = {}; - result.hms_size = DN_CAST(uint8_t) DN_SNPRINTF(result.hms, - DN_ARRAY_ICOUNT(result.hms), - "%02hhu%c%02hhu%c%02hhu", - time.hour, - hms_separator, - time.minutes, - hms_separator, - time.seconds); - - result.date_size = DN_CAST(uint8_t) DN_SNPRINTF(result.date, - DN_ARRAY_ICOUNT(result.date), - "%hu%c%02hhu%c%02hhu", - time.year, - date_separator, - time.month, - date_separator, - time.day); - - DN_ASSERT(result.hms_size < DN_ARRAY_UCOUNT(result.hms)); - DN_ASSERT(result.date_size < DN_ARRAY_UCOUNT(result.date)); - return result; -} - -DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator, char hms_separator) -{ - DN_OSDateTime time = DN_OS_DateLocalTimeNow(); - DN_OSDateTimeStr8 result = DN_OS_DateLocalTimeStr8(time, date_separator, hms_separator); - return result; -} - -DN_API uint64_t DN_OS_DateUnixTimeS() -{ - uint64_t result = DN_OS_DateUnixTimeNs() / (1'000 /*us*/ * 1'000 /*ms*/ * 1'000 /*s*/); - return result; -} - -DN_API bool DN_OS_DateIsValid(DN_OSDateTime date) -{ - if (date.year < 1970) - return false; - if (date.month <= 0 || date.month >= 13) - return false; - if (date.day <= 0 || date.day >= 32) - return false; - if (date.hour >= 24) - return false; - if (date.minutes >= 60) - return false; - if (date.seconds >= 60) - return false; - return true; -} - -// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) -{ - DN_Str8 result = {}; - if (!arena) - return result; - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8 exe_path = DN_OS_EXEPath(tmem.arena); - DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; - DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(exe_path, separators, DN_ARRAY_UCOUNT(separators)); - result = DN_Str8_Copy(arena, split.lhs); - return result; -} - -// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// -DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = ticks / DN_CAST(DN_F64)frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterMs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000) / DN_CAST(DN_F64)frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterUs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000'000) / DN_CAST(DN_F64)frequency; - return result; -} - -DN_API DN_F64 DN_OS_PerfCounterNs(uint64_t begin, uint64_t end) -{ - uint64_t frequency = DN_OS_PerfCounterFrequency(); - uint64_t ticks = end - begin; - DN_F64 result = (ticks * 1'000'000'000) / DN_CAST(DN_F64)frequency; - return result; -} - - -DN_API DN_OSTimer DN_OS_TimerBegin() -{ - DN_OSTimer result = {}; - result.start = DN_OS_PerfCounterNow(); - return result; -} - -DN_API void DN_OS_TimerEnd(DN_OSTimer *timer) -{ - timer->end = DN_OS_PerfCounterNow(); -} - -DN_API DN_F64 DN_OS_TimerS(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterS(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerMs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterMs(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerUs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterUs(timer.start, timer.end); - return result; -} - -DN_API DN_F64 DN_OS_TimerNs(DN_OSTimer timer) -{ - DN_F64 result = DN_OS_PerfCounterNs(timer.start, timer.end); - return result; -} - -DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency) -{ - uint64_t os_frequency = DN_OS_PerfCounterFrequency(); - uint64_t os_target_elapsed = duration_ms_to_gauge_tsc_frequency * os_frequency / 1000ULL; - uint64_t tsc_begin = DN_CPU_TSC(); - uint64_t result = 0; - if (tsc_begin) { - uint64_t os_elapsed = 0; - for (uint64_t os_begin = DN_OS_PerfCounterNow(); os_elapsed < os_target_elapsed; ) - os_elapsed = DN_OS_PerfCounterNow() - os_begin; - uint64_t tsc_end = DN_CPU_TSC(); - uint64_t tsc_elapsed = tsc_end - tsc_begin; - result = tsc_elapsed / os_elapsed * os_frequency; - } - return result; -} - -#if !defined(DN_NO_OS_FILE_API) -// NOTE: [$FILE] DN_OSPathInfo/File /////////////////////////////////////////////////////////////// -DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against) -{ - DN_OSPathInfo file_info = DN_OS_PathInfo(file); - DN_OSPathInfo check_against_info = DN_OS_PathInfo(check_against); - bool result = !file_info.exists || file_info.last_write_time_in_s < check_against_info.last_write_time_in_s; - return result; -} - -DN_API bool DN_OS_FileWrite(DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *error) -{ - bool result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); - return result; -} - -DN_API bool DN_OS_FileWriteFV(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - bool result = false; - if (!file || !fmt) - return result; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); - result = DN_OS_FileWritePtr(file, buffer.data, buffer.size, error); - return result; -} - -DN_API bool DN_OS_FileWriteF(DN_OSFile *file, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_FileWriteFV(file, error, fmt, args); - va_end(args); - return result; -} - -// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_OS_ReadAll(DN_Arena *arena, DN_Str8 path, DN_ErrSink *error) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - // NOTE: Query file size + allocate buffer ///////////////////////////////////////////////////// - DN_OSPathInfo path_info = DN_OS_PathInfo(path); - if (!path_info.exists) { - DN_ErrSink_AppendF(error, 1, "File does not exist/could not be queried for reading '%.*s'", DN_STR_FMT(path)); - return result; - } - - DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); - result = DN_Str8_Alloc(arena, path_info.size, DN_ZeroMem_No); - if (!DN_Str8_HasData(result)) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer_size_str8 = DN_U64ToByteSizeStr8(tmem.arena, path_info.size, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF(error, 1 /*error_code*/, "Failed to allocate %.*s for reading file '%.*s'", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(path)); - DN_Arena_TempMemEnd(temp_mem); - result = {}; - return result; - } - - // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// - DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_Read, error); - bool read_failed = !DN_OS_FileRead(&file, result.data, result.size, error); - if (file.error || read_failed) { - DN_Arena_TempMemEnd(temp_mem); - result = {}; - } - DN_OS_FileClose(&file); - - return result; -} -DN_API bool DN_OS_WriteAll(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) -{ - DN_OSFile file = DN_OS_FileOpen(path, DN_OSFileOpen_CreateAlways, DN_OSFileAccess_Write, error); - bool result = DN_OS_FileWrite(&file, buffer, error); - DN_OS_FileClose(&file); - return result; -} - -DN_API bool DN_OS_WriteAllFV(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); - bool result = DN_OS_WriteAll(file_path, buffer, error); - return result; -} - -DN_API bool DN_OS_WriteAllF(DN_Str8 file_path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_WriteAllFV(file_path, error, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_OS_WriteAllSafe(DN_Str8 path, DN_Str8 buffer, DN_ErrSink *error) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 tmp_path = DN_Str8_InitF(tmem.arena, "%.*s.tmp", DN_STR_FMT(path)); - if (!DN_OS_WriteAll(tmp_path, buffer, error)) - return false; - if (!DN_OS_CopyFile(tmp_path, path, true /*overwrite*/, error)) - return false; - if (!DN_OS_PathDelete(tmp_path)) - return false; - return true; -} - -DN_API bool DN_OS_WriteAllSafeFV(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer = DN_Str8_InitFV(tmem.arena, fmt, args); - bool result = DN_OS_WriteAllSafe(path, buffer, error); - return result; -} - -DN_API bool DN_OS_WriteAllSafeF(DN_Str8 path, DN_ErrSink *error, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_OS_WriteAllSafeFV(path, error, fmt, args); - return result; -} -#endif // !defined(DN_NO_OS_FILE_API) - -// NOTE: [$PATH] DN_OSPath //////////////////////////////////////////////////////////////////////// -DN_API bool DN_OS_PathAddRef(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) -{ - if (!arena || !fs_path || !DN_Str8_HasData(path)) - return false; - - if (path.size <= 0) - return true; - - DN_Str8 const delimiter_array[] = { - DN_STR8("\\"), - DN_STR8("/") - }; - - if (fs_path->links_size == 0) { - fs_path->has_prefix_path_separator = (path.data[0] == '/'); - } - - for (;;) { - DN_Str8BinarySplitResult delimiter = DN_Str8_BinarySplitArray(path, delimiter_array, DN_ARRAY_UCOUNT(delimiter_array)); - for (; delimiter.lhs.data; delimiter = DN_Str8_BinarySplitArray(delimiter.rhs, delimiter_array, DN_ARRAY_UCOUNT(delimiter_array))) { - if (delimiter.lhs.size <= 0) - continue; - - DN_OSPathLink *link = DN_Arena_New(arena, DN_OSPathLink, DN_ZeroMem_Yes); - if (!link) - return false; - - link->string = delimiter.lhs; - link->prev = fs_path->tail; - if (fs_path->tail) { - fs_path->tail->next = link; - } else { - fs_path->head = link; - } - fs_path->tail = link; - fs_path->links_size += 1; - fs_path->string_size += delimiter.lhs.size; - } - - if (!delimiter.lhs.data) - break; - } - - return true; -} - -DN_API bool DN_OS_PathAdd(DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path) -{ - DN_Str8 copy = DN_Str8_Copy(arena, path); - bool result = DN_Str8_HasData(copy) ? true : DN_OS_PathAddRef(arena, fs_path, copy); - return result; -} - -DN_API bool DN_OS_PathAddF(DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8_InitFV(arena, fmt, args); - va_end(args); - bool result = DN_OS_PathAddRef(arena, fs_path, path); - return result; -} - -DN_API bool DN_OS_PathPop(DN_OSPath *fs_path) -{ - if (!fs_path) - return false; - - if (fs_path->tail) { - DN_ASSERT(fs_path->head); - fs_path->links_size -= 1; - fs_path->string_size -= fs_path->tail->string.size; - fs_path->tail = fs_path->tail->prev; - if (fs_path->tail) { - fs_path->tail->next = nullptr; - } else { - fs_path->head = nullptr; - } - } else { - DN_ASSERT(!fs_path->head); - } - - return true; -} - -DN_API DN_Str8 DN_OS_PathTo(DN_Arena *arena, DN_Str8 path, DN_Str8 path_separator) -{ - DN_OSPath fs_path = {}; - DN_OS_PathAddRef(arena, &fs_path, path); - DN_Str8 result = DN_OS_PathBuildWithSeparator(arena, &fs_path, path_separator); - return result; -} - -DN_API DN_Str8 DN_OS_PathToF(DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...) -{ - DN_TLSTMem tmem = DN_TLS_TMem(arena); - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); - va_end(args); - DN_Str8 result = DN_OS_PathTo(arena, path, path_separator); - return result; -} - -DN_API DN_Str8 DN_OS_Path(DN_Arena *arena, DN_Str8 path) -{ - DN_Str8 result = DN_OS_PathTo(arena, path, DN_OSPathSeperatorString); - return result; -} - -DN_API DN_Str8 DN_OS_PathF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) -{ - DN_TLSTMem tmem = DN_TLS_TMem(arena); - va_list args; - va_start(args, fmt); - DN_Str8 path = DN_Str8_InitFV(tmem.arena, fmt, args); - va_end(args); - DN_Str8 result = DN_OS_Path(arena, path); - return result; -} - -DN_API DN_Str8 DN_OS_PathBuildWithSeparator(DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator) -{ - DN_Str8 result = {}; - if (!fs_path || fs_path->links_size <= 0) - return result; - - // NOTE: Each link except the last one needs the path separator appended to it, '/' or '\\' - DN_USize string_size = (fs_path->has_prefix_path_separator ? path_separator.size : 0) + fs_path->string_size + ((fs_path->links_size - 1) * path_separator.size); - result = DN_Str8_Alloc(arena, string_size, DN_ZeroMem_No); - if (result.data) { - char *dest = result.data; - if (fs_path->has_prefix_path_separator) { - DN_MEMCPY(dest, path_separator.data, path_separator.size); - dest += path_separator.size; - } - - for (DN_OSPathLink *link = fs_path->head; link; link = link->next) { - DN_Str8 string = link->string; - DN_MEMCPY(dest, string.data, string.size); - dest += string.size; - - if (link != fs_path->tail) { - DN_MEMCPY(dest, path_separator.data, path_separator.size); - dest += path_separator.size; - } - } - } - - result.data[string_size] = 0; - return result; -} - - -// NOTE: [$EXEC] DN_OSExec //////////////////////////////////////////////////////////////////////// -DN_API DN_OSExecResult DN_OS_Exec(DN_Slice cmd_line, - DN_OSExecArgs *args, - DN_Arena *arena, - DN_ErrSink *error) -{ - DN_OSExecAsyncHandle async_handle = DN_OS_ExecAsync(cmd_line, args, error); - DN_OSExecResult result = DN_OS_ExecWait(async_handle, arena, error); - return result; -} - -DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena) -{ - DN_ErrSink *error = DN_ErrSink_Begin(DN_ErrSinkMode_Nil); - DN_OSExecResult result = DN_OS_Exec(cmd_line, args, arena, error); - if (result.os_error_code) { - DN_ErrSink_EndAndExitIfErrorF( - error, - result.os_error_code, - "OS failed to execute the requested command returning the error code %u", - result.os_error_code); - } - - if (result.exit_code) { - DN_ErrSink_EndAndExitIfErrorF( - error, - result.exit_code, - "OS executed command and returned non-zero exit code %u", - result.exit_code); - } - - DN_ErrSink_EndAndIgnore(error); - return result; -} - -// NOTE: [$THRD] DN_OSThread ////////////////////////////////////////////////////////////////////// -DN_THREAD_LOCAL DN_TLS *g_dn_os_thread_tls; - -static void DN_OS_ThreadExecute_(void *user_context) -{ - DN_OSThread *thread = DN_CAST(DN_OSThread *)user_context; - DN_TLS_Init(&thread->tls); - DN_OS_ThreadSetTLS(&thread->tls); - DN_OS_SemaphoreWait(&thread->init_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); - thread->func(thread); -} - -DN_API void DN_OS_ThreadSetTLS(DN_TLS *tls) -{ - g_dn_os_thread_tls = tls; -} - -DN_API void DN_OS_ThreadSetName(DN_Str8 name) -{ - DN_TLS *tls = DN_TLS_Get(); - tls->name_size = DN_CAST(uint8_t)DN_MIN(name.size, sizeof(tls->name) - 1); - DN_MEMCPY(tls->name, name.data, tls->name_size); - tls->name[tls->name_size] = 0; - - #if defined(DN_OS_WIN32) - DN_Win_ThreadSetName(name); - #else - DN_Posix_ThreadSetName(name); - #endif -} - -// NOTE: [$HTTP] DN_OSHttp //////////////////////////////////////////////////////////////////////// -DN_API void DN_OS_HttpRequestWait(DN_OSHttpResponse *response) -{ - if (response && DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) - DN_OS_SemaphoreWait(&response->on_complete_semaphore, DN_OS_SEMAPHORE_INFINITE_TIMEOUT); -} - -DN_API DN_OSHttpResponse DN_OS_HttpRequest(DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers) -{ - // TODO(doyle): Revise the memory allocation and its lifetime - DN_OSHttpResponse result = {}; - DN_TLSTMem tmem = DN_TLS_TMem(arena); - result.tmem_arena = tmem.arena; - - DN_OS_HttpRequestAsync(&result, arena, host, path, secure, method, body, headers); - DN_OS_HttpRequestWait(&result); - return result; -} diff --git a/dqn_os.h b/dqn_os.h deleted file mode 100644 index 6ab5420..0000000 --- a/dqn_os.h +++ /dev/null @@ -1,445 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ -// $$ / $$ |$$ / \__| -// $$ | $$ |\$$$$$$\ -// $$ | $$ | \____$$\ -// $$ | $$ |$$\ $$ | -// $$$$$$ |\$$$$$$ | -// \______/ \______/ -// -// dqn_os.h -- Common APIs/services provided by the operating system/platform layer -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// [$OMEM] DN_OSMem -- -- Memory allocation (typically virtual memory if supported) -// [$DATE] DN_OSDate -- -- Date time APIs -// [$FILE] DN_OSPathInfo/File -- -- File path info/reading/writing -// [$PATH] DN_OSPath -- -- Construct native OS paths helpers -// [$EXEC] DN_OSExec -- -- Execute programs programatically -// [$SEMA] DN_OSSemaphore -- DN_SEMAPHORE -- -// [$MUTX] DN_OSMutex -- -- -// [$THRD] DN_OSThread -- DN_THREAD -- -// [$HTTP] DN_OSHttp -- -- -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$OMEM] DN_OSMem ////////////////////////////////////////////////////////////////////////// -enum DN_OSMemCommit -{ - DN_OSMemCommit_No, - DN_OSMemCommit_Yes, -}; - -enum DN_OSMemPage -{ - // Exception on read/write with a page. This flag overrides the read/write - // access. - DN_OSMemPage_NoAccess = 1 << 0, - - DN_OSMemPage_Read = 1 << 1, // Only read permitted on the page. - - // Only write permitted on the page. On Windows this is not supported and - // will be promoted to read+write permissions. - DN_OSMemPage_Write = 1 << 2, - - DN_OSMemPage_ReadWrite = DN_OSMemPage_Read | DN_OSMemPage_Write, - - // Modifier used in conjunction with previous flags. Raises exception on - // first access to the page, then, the underlying protection flags are - // active. This is supported on Windows, on other OS's using this flag will - // set the OS equivalent of DN_OSMemPage_NoAccess. - // This flag must only be used in DN_OSMem_Protect - DN_OSMemPage_Guard = 1 << 3, - - // If leak tracing is enabled, this flag will allow the allocation recorded - // from the reserve call to be leaked, e.g. not printed when leaks are - // dumped to the console. - DN_OSMemPage_AllocRecordLeakPermitted = 1 << 4, - - // If leak tracing is enabled this flag will prevent any allocation record - // from being created in the allocation table at all. If this flag is - // enabled, 'OSMemPage_AllocRecordLeakPermitted' has no effect since the - // record will never be created. - DN_OSMemPage_NoAllocRecordEntry = 1 << 5, - - // [INTERNAL] Do not use. All flags together do not constitute a correct - // configuration of pages. - DN_OSMemPage_All = DN_OSMemPage_NoAccess | - DN_OSMemPage_ReadWrite | - DN_OSMemPage_Guard | - DN_OSMemPage_AllocRecordLeakPermitted | - DN_OSMemPage_NoAllocRecordEntry, -}; - -// NOTE: [$DATE] DN_OSDate //////////////////////////////////////////////////////////////////////// -struct DN_OSDateTimeStr8 -{ - char date[DN_ARRAY_UCOUNT("YYYY-MM-SS")]; - uint8_t date_size; - char hms[DN_ARRAY_UCOUNT("HH:MM:SS")]; - uint8_t hms_size; -}; - -struct DN_OSDateTime -{ - uint8_t day; - uint8_t month; - uint16_t year; - uint8_t hour; - uint8_t minutes; - uint8_t seconds; -}; - -struct DN_OSTimer /// Record time between two time-points using the OS's performance counter. -{ - uint64_t start; - uint64_t end; -}; - -#if !defined(DN_NO_OS_FILE_API) -// NOTE: [$FSYS] DN_OSFile //////////////////////////////////////////////////////////////////////// -enum DN_OSPathInfoType -{ - DN_OSPathInfoType_Unknown, - DN_OSPathInfoType_Directory, - DN_OSPathInfoType_File, -}; - -struct DN_OSPathInfo -{ - bool exists; - DN_OSPathInfoType type; - uint64_t create_time_in_s; - uint64_t last_write_time_in_s; - uint64_t last_access_time_in_s; - uint64_t size; -}; - -struct DN_OSDirIterator -{ - void *handle; - DN_Str8 file_name; - char buffer[512]; -}; - -// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -struct DN_OSFile -{ - bool error; - void *handle; -}; - -enum DN_OSFileOpen -{ - DN_OSFileOpen_CreateAlways, // Create file if it does not exist, otherwise, zero out the file and open - DN_OSFileOpen_OpenIfExist, // Open file at path only if it exists - DN_OSFileOpen_OpenAlways, // Open file at path, create file if it does not exist -}; - -typedef uint32_t DN_OSFileAccess; -enum DN_OSFileAccess_ -{ - DN_OSFileAccess_Read = 1 << 0, - DN_OSFileAccess_Write = 1 << 1, - DN_OSFileAccess_Execute = 1 << 2, - DN_OSFileAccess_AppendOnly = 1 << 3, // This flag cannot be combined with any other access mode - DN_OSFileAccess_ReadWrite = DN_OSFileAccess_Read | DN_OSFileAccess_Write, - DN_OSFileAccess_All = DN_OSFileAccess_ReadWrite | DN_OSFileAccess_Execute | DN_OSFileAccess_AppendOnly, -}; -#endif // DN_NO_OS_FILE_API - -// NOTE: DN_OSPath //////////////////////////////////////////////////////////////////////////////// -#if !defined(DN_OSPathSeperator) - #if defined(DN_OS_WIN32) - #define DN_OSPathSeperator "\\" - #else - #define DN_OSPathSeperator "/" - #endif - #define DN_OSPathSeperatorString DN_STR8(DN_OSPathSeperator) -#endif - -struct DN_OSPathLink -{ - DN_Str8 string; - DN_OSPathLink *next; - DN_OSPathLink *prev; -}; - -struct DN_OSPath -{ - bool has_prefix_path_separator; - DN_OSPathLink *head; - DN_OSPathLink *tail; - DN_USize string_size; - uint16_t links_size; -}; - -// NOTE: [$EXEC] DN_OSExec //////////////////////////////////////////////////////////////////////// -typedef uint32_t DN_OSExecFlags; -enum DN_OSExecFlags_ -{ - DN_OSExecFlags_Nil = 0, - DN_OSExecFlags_SaveStdout = 1 << 0, - DN_OSExecFlags_SaveStderr = 1 << 1, - DN_OSExecFlags_SaveOutput = DN_OSExecFlags_SaveStdout | DN_OSExecFlags_SaveStderr, - DN_OSExecFlags_MergeStderrToStdout = 1 << 2 | DN_OSExecFlags_SaveOutput, -}; - -struct DN_OSExecAsyncHandle -{ - DN_OSExecFlags exec_flags; - uint32_t os_error_code; - uint32_t exit_code; - void *process; - void *stdout_read; - void *stdout_write; - void *stderr_read; - void *stderr_write; -}; - -struct DN_OSExecResult -{ - bool finished; - DN_Str8 stdout_text; - DN_Str8 stderr_text; - uint32_t os_error_code; - uint32_t exit_code; -}; - -struct DN_OSExecArgs -{ - DN_OSExecFlags flags; - DN_Str8 working_dir; - DN_Slice environment; -}; - -#if !defined(DN_NO_SEMAPHORE) -// NOTE: [$SEMA] DN_OSSemaphore /////////////////////////////////////////////////////////////////// -uint32_t const DN_OS_SEMAPHORE_INFINITE_TIMEOUT = UINT32_MAX; - -struct DN_OSSemaphore -{ - #if defined(DN_OS_WIN32) && !defined(DN_OS_WIN32_USE_PTHREADS) - void *win32_handle; - #else - sem_t posix_handle; - bool posix_init; - #endif -}; - -enum DN_OSSemaphoreWaitResult -{ - DN_OSSemaphoreWaitResult_Failed, - DN_OSSemaphoreWaitResult_Success, - DN_OSSemaphoreWaitResult_Timeout, -}; -#endif // !defined(DN_NO_SEMAPHORE) - -// NOTE: [$THRD] DN_OSThread ///////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) -typedef int32_t (DN_OSThreadFunc)(struct DN_OSThread*); - -struct DN_OSThread -{ - DN_FStr8<64> name; - DN_TLS tls; - void *handle; - uint64_t thread_id; - void *user_context; - DN_OSThreadFunc *func; - DN_OSSemaphore init_semaphore; -}; -#endif // !defined(DN_NO_THREAD) - -// NOTE: [$HTTP] DN_OSHttp //////////////////////////////////////////////////////////////////////// -enum DN_OSHttpRequestSecure -{ - DN_OSHttpRequestSecure_No, - DN_OSHttpRequestSecure_Yes, -}; - -struct DN_OSHttpResponse -{ - // NOTE: Response data - uint32_t error_code; - DN_Str8 error_msg; - uint16_t http_status; - DN_Str8 body; - DN_B32 done; - - // NOTE: Book-keeping - DN_Arena *arena; // Allocates memory for the response - - // NOTE: Async book-keeping - // Synchronous HTTP response uses the TLS scratch arena whereas async - // calls use their own dedicated arena. - DN_Arena tmp_arena; - DN_Arena *tmem_arena; - DN_Str8Builder builder; - DN_OSSemaphore on_complete_semaphore; - - #if defined(DN_PLATFORM_EMSCRIPTEN) - emscripten_fetch_t *em_handle; - #elif defined(DN_OS_WIN32) - HINTERNET win32_request_session; - HINTERNET win32_request_connection; - HINTERNET win32_request_handle; - #endif -}; - -DN_API void DN_OS_Init(); - -// NOTE: [$OMEM] Memory ////////////////////////////////////////////////////////////////////////// -DN_API void * DN_OS_MemReserve (DN_USize size, DN_OSMemCommit commit, uint32_t page_flags); -DN_API bool DN_OS_MemCommit (void *ptr, DN_USize size, uint32_t page_flags); -DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size); -DN_API void DN_OS_MemRelease (void *ptr, DN_USize size); -DN_API int DN_OS_MemProtect (void *ptr, DN_USize size, uint32_t page_flags); - -// NOTE: Heap -DN_API void *DN_OS_MemAlloc (DN_USize size, DN_ZeroMem zero_mem); -DN_API void DN_OS_MemDealloc (void *ptr); - -// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// -DN_API DN_OSDateTime DN_OS_DateLocalTimeNow (); -DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8Now(char date_separator = '-', char hms_separator = ':'); -DN_API DN_OSDateTimeStr8 DN_OS_DateLocalTimeStr8 (DN_OSDateTime time, char date_separator = '-', char hms_separator = ':'); -DN_API uint64_t DN_OS_DateUnixTimeNs (); -DN_API uint64_t DN_OS_DateUnixTimeS (); -DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate (uint64_t time); -DN_API uint64_t DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date); -DN_API uint64_t DN_OS_DateToUnixTimeS (DN_OSDateTime date); -DN_API bool DN_OS_DateIsValid (DN_OSDateTime date); - -// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// -DN_API bool DN_OS_SecureRNGBytes (void *buffer, uint32_t size); -DN_API bool DN_OS_SetEnvVar (DN_Str8 name, DN_Str8 value); -DN_API DN_Str8 DN_OS_EXEPath (DN_Arena *arena); -DN_API DN_Str8 DN_OS_EXEDir (DN_Arena *arena); -#define DN_OS_EXEDir_TLS() DN_OS_EXEDir(DN_TLS_TopArena()) -DN_API void DN_OS_SleepMs (DN_UInt milliseconds); - -// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// -DN_API uint64_t DN_OS_PerfCounterNow (); -DN_API uint64_t DN_OS_PerfCounterFrequency(); -DN_API DN_F64 DN_OS_PerfCounterS (uint64_t begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterMs (uint64_t begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterUs (uint64_t begin, uint64_t end); -DN_API DN_F64 DN_OS_PerfCounterNs (uint64_t begin, uint64_t end); -DN_API DN_OSTimer DN_OS_TimerBegin (); -DN_API void DN_OS_TimerEnd (DN_OSTimer *timer); -DN_API DN_F64 DN_OS_TimerS (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerMs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerUs (DN_OSTimer timer); -DN_API DN_F64 DN_OS_TimerNs (DN_OSTimer timer); -DN_API uint64_t DN_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_frequency); -#if !defined(DN_NO_OS_FILE_API) -// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// -DN_API DN_OSPathInfo DN_OS_PathInfo (DN_Str8 path); -DN_API bool DN_OS_FileIsOlderThan(DN_Str8 file, DN_Str8 check_against); -DN_API bool DN_OS_PathDelete (DN_Str8 path); -DN_API bool DN_OS_FileExists (DN_Str8 path); -DN_API bool DN_OS_CopyFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); -DN_API bool DN_OS_MoveFile (DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err); -DN_API bool DN_OS_MakeDir (DN_Str8 path); -DN_API bool DN_OS_DirExists (DN_Str8 path); -DN_API bool DN_OS_DirIterate (DN_Str8 path, DN_OSDirIterator *it); - -// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DN_API DN_OSFile DN_OS_FileOpen (DN_Str8 path, DN_OSFileOpen open_mode, DN_OSFileAccess access, DN_ErrSink *err); -DN_API bool DN_OS_FileRead (DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *data, DN_USize size, DN_ErrSink *err); -DN_API bool DN_OS_FileWrite (DN_OSFile *file, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_FileWriteFV (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_FileWriteF (DN_OSFile *file, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_FileFlush (DN_OSFile *file, DN_ErrSink *err); -DN_API void DN_OS_FileClose (DN_OSFile *file); - -// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_OS_ReadAll (DN_Arena *arena, DN_Str8 path, DN_ErrSink *err); -#define DN_OS_ReadAll_TLS(...) DN_OS_ReadAll(DN_TLS_TopArena(), ##__VA_ARGS__) -DN_API bool DN_OS_WriteAll (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_WriteAllFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_WriteAllF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -DN_API bool DN_OS_WriteAllSafe (DN_Str8 path, DN_Str8 buffer, DN_ErrSink *err); -DN_API bool DN_OS_WriteAllSafeFV (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, va_list args); -DN_API bool DN_OS_WriteAllSafeF (DN_Str8 path, DN_ErrSink *err, DN_FMT_ATTRIB char const *fmt, ...); -#endif // !defined(DN_NO_OS_FILE_API) - -// NOTE: File system paths ///////////////////////////////////////////////////////////////////////// -DN_API bool DN_OS_PathAddRef (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -#define DN_OS_PathAddRef_TLS(...) DN_OS_PathAddRef(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathAddRef_Frame(...) DN_OS_PathAddRef(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API bool DN_OS_PathAdd (DN_Arena *arena, DN_OSPath *fs_path, DN_Str8 path); -#define DN_OS_PathAdd_TLS(...) DN_OS_PathAdd(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathAdd_Frame(...) DN_OS_PathAdd(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API bool DN_OS_PathAddF (DN_Arena *arena, DN_OSPath *fs_path, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_OS_PathAddF_TLS(...) DN_OS_PathAddF(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathAddF_Frame(...) DN_OS_PathAddF(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API bool DN_OS_PathPop (DN_OSPath *fs_path); -DN_API DN_Str8 DN_OS_PathBuildWithSeparator (DN_Arena *arena, DN_OSPath const *fs_path, DN_Str8 path_separator); -#define DN_OS_PathBuildWithSeperator_TLS(...) DN_OS_PathBuildWithSeperator(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathBuildWithSeperator_Frame(...) DN_OS_PathBuildWithSeperator(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API DN_Str8 DN_OS_PathTo (DN_Arena *arena, DN_Str8 path, DN_Str8 path_separtor); -#define DN_OS_PathTo_TLS(...) DN_OS_PathTo(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathTo_Frame(...) DN_OS_PathTo(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API DN_Str8 DN_OS_PathToF (DN_Arena *arena, DN_Str8 path_separator, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_OS_PathToF_TLS(...) DN_OS_PathToF(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathToF_Frame(...) DN_OS_PathToF(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API DN_Str8 DN_OS_Path (DN_Arena *arena, DN_Str8 path); -#define DN_OS_Path_TLS(...) DN_OS_Path(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_Path_Frame(...) DN_OS_Path(DN_TLS_FrameArena(), ##__VA_ARGS__) -DN_API DN_Str8 DN_OS_PathF (DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...); -#define DN_OS_PathF_TLS(...) DN_OS_PathF(DN_TLS_TopArena(), ##__VA_ARGS__) -#define DN_OS_PathF_Frame(...) DN_OS_PathF(DN_TLS_FrameArena(), ##__VA_ARGS__) - -#define DN_OS_PathBuildFwdSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("/")) -#define DN_OS_PathBuildBackSlash(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_STR8("\\")) -#define DN_OS_PathBuild(allocator, fs_path) DN_OS_PathBuildWithSeparator(allocator, fs_path, DN_OSPathSeparatorString) - -// NOTE: [$EXEC] DN_OSExec //////////////////////////////////////////////////////////////////////// -DN_API void DN_OS_Exit (int32_t exit_code); -DN_API DN_OSExecResult DN_OS_ExecPump (DN_OSExecAsyncHandle handle, char *stdout_buffer, size_t *stdout_size, char *stderr_buffer, size_t *stderr_size, uint32_t timeout_ms, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecWait (DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync (DN_Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_Exec (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena, DN_ErrSink *err); -DN_API DN_OSExecResult DN_OS_ExecOrAbort (DN_Slice cmd_line, DN_OSExecArgs *args, DN_Arena *arena); -#define DN_OS_ExecOrAbort_TLS(...) DN_OS_ExecOrAbort(__VA_ARGS__, DN_TLS_TopArena()) - -// NOTE: [$SEMA] DN_OSSemaphore /////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_SEMAPHORE) -DN_API DN_OSSemaphore DN_OS_SemaphoreInit (uint32_t initial_count); -DN_API bool DN_OS_SemaphoreIsValid (DN_OSSemaphore *semaphore); -DN_API void DN_OS_SemaphoreDeinit (DN_OSSemaphore *semaphore); -DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount); -DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait (DN_OSSemaphore *semaphore, uint32_t timeout_ms); -#endif // !defined(DN_NO_SEMAPHORE) - -// NOTE: [$MUTX] DN_OSMutex /////////////////////////////////////////////////////////////////////// -DN_API DN_OSMutex DN_OS_MutexInit (); -DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex); -DN_API void DN_OS_MutexLock (DN_OSMutex *mutex); -DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex); -#define DN_OS_Mutex(mutex) DN_DEFER_LOOP(DN_OS_MutexLock(mutex), DN_OS_MutexUnlock(mutex)) - -// NOTE: [$THRD] DN_OSThread ///////////////////////////////////////////////////////////////////// -#if !defined(DN_NO_THREAD) && !defined(DN_NO_SEMAPHORE) -DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context); -DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread); -DN_API uint32_t DN_OS_ThreadID (); -DN_API void DN_OS_ThreadSetTLS(DN_TLS *tls); -DN_API void DN_OS_ThreadSetName(DN_Str8 name); -#endif // !defined(DN_NO_THREAD) - -// NOTE: [$HTTP] DN_OSHttp //////////////////////////////////////////////////////////////////////// -DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); -DN_API void DN_OS_HttpRequestWait (DN_OSHttpResponse *response); -DN_API void DN_OS_HttpRequestFree (DN_OSHttpResponse *response); -DN_API DN_OSHttpResponse DN_OS_HttpRequest (DN_Arena *arena, DN_Str8 host, DN_Str8 path, DN_OSHttpRequestSecure secure, DN_Str8 method, DN_Str8 body, DN_Str8 headers); diff --git a/dqn_os_posix.cpp b/dqn_os_posix.cpp deleted file mode 100644 index e5ebdd9..0000000 --- a/dqn_os_posix.cpp +++ /dev/null @@ -1,1336 +0,0 @@ -#pragma once -#include "dqn.h" - -#include // readdir, opendir, closedir - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ -// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ \_$$ _|$$ | $$ | -// $$ / $$ |$$ / \__| $$ | $$ |$$ / $$ |$$ / \__| $$ | \$$\ $$ | -// $$ | $$ |\$$$$$$\ $$$$$$$ |$$ | $$ |\$$$$$$\ $$ | \$$$$ / -// $$ | $$ | \____$$\ $$ ____/ $$ | $$ | \____$$\ $$ | $$ $$< -// $$ | $$ |$$\ $$ | $$ | $$ | $$ |$$\ $$ | $$ | $$ /\$$\ -// $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ | -// \______/ \______/ \__| \______/ \______/ \______|\__| \__| -// -// dqn_os_posix.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$VMEM] DN_OSMem -// ////////////////////////////////////////////////////////////////////////// -static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) -{ - DN_ASSERT((protect & ~DN_OSMemPage_All) == 0); - DN_ASSERT(protect != 0); - uint32_t result = 0; - - if (protect & (DN_OSMemPage_NoAccess | DN_OSMemPage_Guard)) { - result = PROT_NONE; - } else { - if (protect & DN_OSMemPage_Read) - result = PROT_READ; - if (protect & DN_OSMemPage_Write) - result = PROT_WRITE; - } - return result; -} - -DN_API void *DN_OS_MemReserve(DN_usize size, DN_OSMemCommit commit, uint32_t page_flags) -{ - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - - if (commit == DN_OSMemCommit_Yes) - os_page_flags |= (PROT_READ | PROT_WRITE); - - void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_total, 1); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_frame, 1); - if (result == MAP_FAILED) - result = nullptr; - return result; -} - -DN_API bool DN_OS_MemCommit(void *ptr, DN_usize size, uint32_t page_flags) -{ - bool result = false; - if (!ptr || size == 0) - return false; - - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - result = mprotect(ptr, size, os_page_flags) == 0; - DN_Atomic_AddU64(&g_dn_core->mem_allocs_total, 1); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_frame, 1); - return result; -} - -DN_API void DN_OS_MemDecommit(void *ptr, DN_usize size) -{ - mprotect(ptr, size, PROT_NONE); - madvise(ptr, size, MADV_FREE); -} - -DN_API void DN_OS_MemRelease(void *ptr, DN_usize size) -{ - munmap(ptr, size); -} - -DN_API int DN_OS_MemProtect(void *ptr, DN_usize size, uint32_t page_flags) -{ - if (!ptr || size == 0) - return 0; - - static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_STR8( - "Page protection requires pointers to be page aligned because we " - "can only guard memory at a multiple of the page boundary."); - DN_ASSERTF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t) ptr, g_dn_core->os_page_size), - "%s", - ALIGNMENT_ERROR_MSG.data); - DN_ASSERTF( - DN_IsPowerOfTwoAligned(size, g_dn_core->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); - - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - int result = mprotect(ptr, size, os_page_flags); - DN_ASSERTF(result == 0, "mprotect failed (%d)", errno); - return result; -} - -DN_API void *DN_OS_MemAlloc(DN_usize size, DN_ZeroMem zero_mem) -{ - void *result = zero_mem == DN_ZeroMem_Yes ? calloc(1, size) : malloc(size); - return result; -} - -DN_API void DN_OS_MemDealloc(void *ptr) -{ - free(ptr); -} - -// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// -DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() -{ - DN_OSDateTime result = {}; - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - - // NOTE: localtime_r is used because it is thread safe - // See: https://linux.die.net/man/3/localtime - // According to POSIX.1-2004, localtime() is required to behave as though - // tzset(3) was called, while localtime_r() does not have this requirement. - // For portable code tzset(3) should be called before localtime_r(). - for (static bool once = true; once; once = false) - tzset(); - - struct tm time = {}; - localtime_r(&ts.tv_sec, &time); - - result.hour = time.tm_hour; - result.minutes = time.tm_min; - result.seconds = time.tm_sec; - - result.day = DN_CAST(uint8_t) time.tm_mday; - result.month = DN_CAST(uint8_t) time.tm_mon + 1; - result.year = 1900 + DN_CAST(int16_t) time.tm_year; - return result; -} - -DN_API uint64_t DN_OS_DateUnixTimeNs() -{ - struct timespec ts = {}; - clock_gettime(CLOCK_REALTIME, &ts); - uint64_t result = (ts.tv_sec * 1000 /*ms*/ * 1000 /*us*/ * 1000 /*ns*/) + ts.tv_nsec; - return result; -} - -DN_API uint64_t DN_OS_DateLocalToUnixTimeS(DN_OSDateTime) -{ - DN_ASSERT_ONCE(!"Unimplemented"); - uint64_t result = 0; - return result; -} - -DN_API uint64_t DN_OS_DateToUnixTimeS(DN_OSDateTime date) -{ - DN_ASSERT(DN_OS_DateIsValid(date)); - struct tm timeinfo = {}; - timeinfo.tm_year = date.year - 1900; - timeinfo.tm_mon = date.month - 1; - timeinfo.tm_mday = date.day; - timeinfo.tm_hour = date.hour; - timeinfo.tm_min = date.minutes; - timeinfo.tm_sec = date.seconds; - uint64_t result = mktime(&timeinfo); - return result; -} - -DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(uint64_t time) -{ - time_t posix_time = DN_CAST(time_t) time; - struct tm posix_date = *gmtime(&posix_time); - DN_OSDateTime result = {}; - result.year = posix_date.tm_year + 1900; - result.month = posix_date.tm_mon + 1; - result.day = posix_date.tm_mday; - result.hour = posix_date.tm_hour; - result.minutes = posix_date.tm_min; - result.seconds = posix_date.tm_sec; - return result; -} - -DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) -{ -#if defined(DN_PLATFORM_EMSCRIPTEN) - (void)buffer; - (void)size; - return false; -#else - if (!buffer || size < 0) - return false; - - if (size == 0) - return true; - - DN_ASSERTF(size <= 32, - "We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix " - "guarantees 32 " - "bytes can always be fulfilled by this system at a time"); - // TODO(doyle): - // https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c - // TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html - uint32_t read_bytes = 0; - do { - read_bytes = - getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes - } while (read_bytes != size || errno == EAGAIN); - return true; -#endif -} - -DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) -{ - DN_ASSERTF_ONCE(false, "Unimplemented"); - (void)name; - (void)value; - bool result = false; - return result; -} - - -DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) -{ - DN_Str8 result = {}; - if (!arena) - return result; - - int required_size_wo_null_terminator = 0; - for (int try_size = 128;; try_size *= 2) { - auto scoped_arena = DN_ArenaTempMemScope(arena); - char *try_buf = DN_Arena_NewArray(arena, char, try_size, DN_ZeroMem_No); - int bytes_written = readlink("/proc/self/exe", try_buf, try_size); - if (bytes_written == -1) { - // Failed, we're unable to determine the executable directory - break; - } else if (bytes_written == try_size) { - // Try again, if returned size was equal- we may of prematurely - // truncated according to the man pages - continue; - } else { - // readlink will give us the path to the executable. Once we - // determine the correct buffer size required to get the full file - // path, we do some post-processing on said string and extract just - // the directory. - - // TODO(dn): It'd be nice if there's some way of keeping this - // try_buf around, memcopy the byte and trash the try_buf from the - // arena. Instead we just get the size and redo the call one last - // time after this "calculate" step. - DN_ASSERTF(bytes_written < try_size, - "bytes_written can never be greater than the try size, function writes at " - "most try_size"); - required_size_wo_null_terminator = bytes_written; - break; - } - } - - if (required_size_wo_null_terminator) { - DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(arena); - char *exe_path = - DN_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, DN_ZeroMem_No); - exe_path[required_size_wo_null_terminator] = 0; - - int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator); - if (bytes_written == -1) { - // Note that if read-link fails again can be because there's - // a potential race condition here, our exe or directory could have - // been deleted since the last call, so we need to be careful. - DN_Arena_TempMemEnd(temp_mem); - } else { - result = DN_Str8_Init(exe_path, required_size_wo_null_terminator); - } - } - return result; -} - -DN_API void DN_OS_SleepMs(DN_uint milliseconds) -{ - struct timespec ts; - ts.tv_sec = milliseconds / 1000; - ts.tv_nsec = (milliseconds % 1000) * 1000000; // Convert remaining milliseconds to nanoseconds - // nanosleep can fail if interrupted by a signal, so we loop until the full sleep time has passed - while (nanosleep(&ts, &ts) == -1 && errno == EINTR); -} - -DN_API uint64_t DN_OS_PerfCounterFrequency() -{ - // NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which - // increments at nanosecond granularity. - uint64_t result = 1'000'000'000; - return result; -} - -DN_API uint64_t DN_OS_PerfCounterNow() -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - uint64_t result = DN_CAST(uint64_t) ts.tv_sec * 1'000'000'000 + DN_CAST(uint64_t) ts.tv_nsec; - return result; -} - -#if !defined(DN_NO_OS_FILE_API) -DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) -{ - DN_OSPathInfo result = {}; - if (!DN_Str8_HasData(path)) - return result; - - struct stat file_stat; - if (lstat(path.data, &file_stat) != -1) { - result.exists = true; - result.size = file_stat.st_size; - result.last_access_time_in_s = file_stat.st_atime; - result.last_write_time_in_s = file_stat.st_mtime; - // TODO(dn): Seems linux does not support creation time via stat. We - // shoddily deal with this. - result.create_time_in_s = - DN_MIN(result.last_access_time_in_s, result.last_write_time_in_s); - - if (S_ISDIR(file_stat.st_mode)) { - result.type = DN_OSPathInfoType_Directory; - } else if (S_ISREG(file_stat.st_mode)) { - result.type = DN_OSPathInfoType_File; - } - } - return result; -} - -DN_API bool DN_OS_PathDelete(DN_Str8 path) -{ - bool result = false; - if (DN_Str8_HasData(path)) - result = remove(path.data) == 0; - return result; -} - -DN_API bool DN_OS_FileExists(DN_Str8 path) -{ - bool result = false; - if (!DN_Str8_HasData(path)) - return result; - - struct stat stat_result; - if (lstat(path.data, &stat_result) != -1) - result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode); - return result; -} - -DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *error) -{ - bool result = false; - #if defined(DN_PLATFORM_EMSCRIPTEN) - DN_ErrSink_AppendF(error, 1, "Unsupported on Emscripten because of their VFS model"); - #else - int src_fd = open(src.data, O_RDONLY); - if (src_fd == -1) { - int error_code = errno; - DN_ErrSink_AppendF(error, - error_code, - "Failed to open file '%.*s' for copying: (%d) %s", - DN_STR_FMT(src), - error_code, - strerror(error_code)); - return result; - } - DN_DEFER - { - close(src_fd); - }; - - int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); - if (dest_fd == -1) { - int error_code = errno; - DN_ErrSink_AppendF(error, - error_code, - "Failed to open file destination '%.*s' for copying to: (%d) %s", - DN_STR_FMT(src), - error_code, - strerror(error_code)); - return result; - } - DN_DEFER - { - close(dest_fd); - }; - - struct stat stat_existing; - int fstat_result = fstat(src_fd, &stat_existing); - if (fstat_result == -1) { - int error_code = errno; - DN_ErrSink_AppendF(error, - error_code, - "Failed to query file size of '%.*s' for copying: (%d) %s", - DN_STR_FMT(src), - error_code, - strerror(error_code)); - return result; - } - - ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); - result = (bytes_written == stat_existing.st_size); - if (!result) { - int error_code = errno; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 file_size_str8 = - DN_U64ToByteSizeStr8(tmem.arena, stat_existing.st_size, DN_U64ByteSizeType_Auto); - DN_Str8 bytes_written_str8 = - DN_U64ToByteSizeStr8(tmem.arena, bytes_written, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF(error, - error_code, - "Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file " - "size is %.*s: (%d) %s", - DN_STR_FMT(src), - DN_STR_FMT(dest), - DN_STR_FMT(bytes_written_str8), - DN_STR_FMT(file_size_str8), - error_code, - strerror(error_code)); - } - - #endif - return result; -} - -DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *error) -{ - // See: https://github.com/gingerBill/gb/blob/master/gb.h - bool result = false; - bool file_moved = true; - if (link(src.data, dest.data) == -1) { - // NOTE: Link can fail if we're trying to link across different volumes - // so we fall back to a binary directory. - file_moved |= DN_OS_CopyFile(src, dest, overwrite, error); - } - - if (file_moved) { - result = true; - int unlink_result = unlink(src.data); - if (unlink_result == -1) { - int error_code = errno; - DN_ErrSink_AppendF( - error, - error_code, - "File '%.*s' was moved but failed to be unlinked from old location: (%d) %s", - DN_STR_FMT(src), - error_code, - strerror(error_code)); - } - } - return result; -} - -DN_API bool DN_OS_MakeDir(DN_Str8 path) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - bool result = true; - - // TODO(doyle): Implement this without using the path indexes, it's not - // necessary. See Windows implementation. - DN_usize path_indexes_size = 0; - uint16_t path_indexes[64] = {}; - - DN_Str8 copy = DN_Str8_Copy(tmem.arena, path); - for (DN_usize index = copy.size - 1; index < copy.size; index--) { - bool first_char = index == (copy.size - 1); - char ch = copy.data[index]; - if (ch == '/' || first_char) { - char temp = copy.data[index]; - - if (!first_char) - copy.data[index] = 0; // Temporarily null terminate it - - bool is_file = DN_OS_FileExists(copy); - - if (!first_char) - copy.data[index] = temp; // Undo null termination - - if (is_file) { - // NOTE: There's something that exists in at this path, but - // it's not a directory. This request to make a directory is - // invalid. - return false; - } else if (DN_OS_DirExists(copy)) { - // NOTE: We found a directory, we can stop here and start - // building up all the directories that didn't exist up to - // this point. - break; - } else { - // NOTE: There's nothing that exists at this path, we can - // create a directory here - path_indexes[path_indexes_size++] = DN_CAST(uint16_t) index; - } - } - } - - for (DN_usize index = path_indexes_size - 1; result && index < path_indexes_size; index--) { - uint16_t path_index = path_indexes[index]; - char temp = copy.data[path_index]; - - if (index != 0) - copy.data[path_index] = 0; - result |= mkdir(copy.data, 0774) == 0; - if (index != 0) - copy.data[path_index] = temp; - } - return result; -} - -DN_API bool DN_OS_DirExists(DN_Str8 path) -{ - bool result = false; - if (!DN_Str8_HasData(path)) - return result; - - struct stat stat_result; - if (lstat(path.data, &stat_result) != -1) - result = S_ISDIR(stat_result.st_mode); - return result; -} - -DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) -{ - if (!it->handle) { - it->handle = opendir(path.data); - if (!it->handle) - return false; - } - - struct dirent *entry; - for (;;) { - entry = readdir(DN_CAST(DIR*)it->handle); - if (entry == NULL) - break; - - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - DN_usize name_size = DN_CStr8_Size(entry->d_name); - DN_usize clamped_size = DN_MIN(sizeof(it->buffer) - 1, name_size); - DN_ASSERTF(name_size == clamped_size, "name: %s, name_size: %zu, clamped_size: %zu", entry->d_name, name_size, clamped_size); - DN_MEMCPY(it->buffer, entry->d_name, clamped_size); - it->buffer[clamped_size] = 0; - it->file_name = DN_Str8_Init(it->buffer, clamped_size); - return true; - } - - closedir(DN_CAST(DIR*)it->handle); - it->handle = NULL; - it->file_name = {}; - it->buffer[0] = 0; - return false; -} - -// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, - DN_OSFileOpen open_mode, - DN_OSFileAccess access, - DN_ErrSink *error) -{ - DN_OSFile result = {}; - if (!DN_Str8_HasData(path) || path.size <= 0) - return result; - - if ((access & ~(DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0))) { - DN_INVALID_CODE_PATH; - return result; - } - - if (access & DN_OSFileAccess_Execute) { - result.error = true; - DN_ErrSink_AppendF( - error, - 1, - "Failed to open file '%.*s': File access flag 'execute' is not supported", - DN_STR_FMT(path)); - DN_INVALID_CODE_PATH; // TODO: Not supported via fopen - return result; - } - - // NOTE: fopen interface is not as expressive as the Win32 - // We will fopen the file beforehand to setup the state/check for validity - // before closing and reopening it with the correct request access - // permissions. - { - FILE *handle = nullptr; - switch (open_mode) { - case DN_OSFileOpen_CreateAlways: handle = fopen(path.data, "w"); break; - case DN_OSFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break; - case DN_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; - default: DN_INVALID_CODE_PATH; break; - } - - if (!handle) { // TODO(doyle): FileOpen flag to string - result.error = true; - DN_ErrSink_AppendF(error, - 1, - "Failed to open file '%.*s': File could not be opened in requested " - "mode 'DN_OSFileOpen' flag %d", - DN_STR_FMT(path), - open_mode); - return result; - } - fclose(handle); - } - - char const *fopen_mode = nullptr; - if (access & DN_OSFileAccess_AppendOnly) - fopen_mode = "a+"; - else if (access & DN_OSFileAccess_Write) - fopen_mode = "w+"; - else if (access & DN_OSFileAccess_Read) - fopen_mode = "r+"; - - FILE *handle = fopen(path.data, fopen_mode); - if (!handle) { - result.error = true; - DN_ErrSink_AppendF(error, - 1, - "Failed to open file '%.*s': File could not be opened with requested " - "access mode 'DN_OSFileAccess' %d", - DN_STR_FMT(path), - fopen_mode); - return result; - } - result.handle = handle; - return result; -} - -DN_API bool DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_usize size, DN_ErrSink *error) -{ - if (!file || !file->handle || file->error || !buffer || size <= 0) - return false; - - if (fread(buffer, size, 1, DN_CAST(FILE *) file->handle) != 1) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer_size_str8 = - DN_U64ToByteSizeStr8(tmem.arena, size, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF( - error, 1, "Failed to read %.*s from file", DN_STR_FMT(buffer_size_str8)); - return false; - } - - return true; -} - -DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_usize size, DN_ErrSink *err) -{ - if (!file || !file->handle || file->error || !buffer || size <= 0) - return false; - bool result = - fwrite(buffer, DN_CAST(DN_usize) size, 1 /*count*/, DN_CAST(FILE *) file->handle) == - 1 /*count*/; - if (!result) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 buffer_size_str8 = - DN_U64ToByteSizeStr8(tmem.arena, size, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF( - err, 1, "Failed to write buffer (%s) to file handle", DN_STR_FMT(buffer_size_str8)); - } - return result; -} - -DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_ErrSink *err) -{ - // TODO: errno is not thread safe - int fd = fileno(DN_CAST(FILE *)file->handle); - if (fd == -1) { - DN_ErrSink_AppendF(err, errno, "Failed to flush file buffer to disk, file handle could not be converted to descriptor (%d): %s", fd, strerror(errno)); - return false; - } - - int fsync_result = fsync(fd); - if (fsync_result == -1) { - DN_ErrSink_AppendF(err, errno, "Failed to flush file buffer to disk (%d): %s", fsync_result, strerror(errno)); - return false; - } - return true; -} - -DN_API void DN_OS_FileClose(DN_OSFile *file) -{ - if (!file || !file->handle || file->error) - return; - fclose(DN_CAST(FILE *) file->handle); - *file = {}; -} -#endif // !defined(DN_NO_OS_FILE_API) - -// NOTE: [$EXEC] DN_OSExec //////////////////////////////////////////////////////////////////////// -DN_API void DN_OS_Exit(int32_t exit_code) -{ - exit(DN_CAST(int) exit_code); -} - -enum DN_OSPipeType_ { - DN_OSPipeType__Read, - DN_OSPipeType__Write, - DN_OSPipeType__Count, -}; - -DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, - DN_Arena *arena, - DN_ErrSink *error) -{ - DN_OSExecResult result = {}; - if (!handle.process || handle.os_error_code || handle.exit_code) { - if (handle.os_error_code) - result.os_error_code = handle.os_error_code; - else - result.exit_code = handle.exit_code; - - DN_ASSERT(!handle.stdout_read); - DN_ASSERT(!handle.stdout_write); - DN_ASSERT(!handle.stderr_read); - DN_ASSERT(!handle.stderr_write); - return result; - } - -#if defined(DN_PLATFORM_EMSCRIPTEN) - DN_INVALID_CODE_PATHF("Unsupported operation"); -#endif - - static_assert(sizeof(pid_t) <= sizeof(handle.process), - "We store the PID opaquely in a register sized pointer"); - pid_t process = {}; - DN_MEMCPY(&process, &handle.process, sizeof(process)); - for (;;) { - int status = 0; - if (waitpid(process, &status, 0) < 0) { - result.os_error_code = errno; - break; - } - - if (WIFEXITED(status)) { - result.exit_code = WEXITSTATUS(status); - break; - } - - if (WIFSIGNALED(status)) { - result.os_error_code = WTERMSIG(status); - break; - } - } - - int stdout_pipe[DN_OSPipeType__Count] = {}; - int stderr_pipe[DN_OSPipeType__Count] = {}; - DN_MEMCPY(&stdout_pipe[DN_OSPipeType__Read], - &handle.stdout_read, - sizeof(stdout_pipe[DN_OSPipeType__Read])); - DN_MEMCPY(&stdout_pipe[DN_OSPipeType__Write], - &handle.stdout_write, - sizeof(stdout_pipe[DN_OSPipeType__Write])); - DN_MEMCPY(&stderr_pipe[DN_OSPipeType__Read], - &handle.stderr_read, - sizeof(stderr_pipe[DN_OSPipeType__Read])); - DN_MEMCPY(&stderr_pipe[DN_OSPipeType__Write], - &handle.stderr_write, - sizeof(stderr_pipe[DN_OSPipeType__Write])); - - // NOTE: Process has finished, stop the write end of the pipe - close(stdout_pipe[DN_OSPipeType__Write]); - close(stderr_pipe[DN_OSPipeType__Write]); - - // NOTE: Read the data from the read end of the pipe - if (result.os_error_code == 0) { - DN_TLSTMem tmem = DN_TLS_TMem(arena); - if (arena && handle.stdout_read) { - char buffer[4096]; - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - for (;;) { - ssize_t bytes_read = - read(stdout_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); - if (bytes_read <= 0) - break; - DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); - } - - result.stdout_text = DN_Str8Builder_Build(&builder, arena); - } - - if (arena && handle.stderr_read) { - char buffer[4096]; - DN_Str8Builder builder = DN_Str8Builder_Init(tmem.arena); - for (;;) { - ssize_t bytes_read = - read(stderr_pipe[DN_OSPipeType__Read], buffer, sizeof(buffer)); - if (bytes_read <= 0) - break; - DN_Str8Builder_AppendF(&builder, "%.*s", bytes_read, buffer); - } - - result.stderr_text = DN_Str8Builder_Build(&builder, arena); - } - } - - close(stdout_pipe[DN_OSPipeType__Read]); - close(stderr_pipe[DN_OSPipeType__Read]); - return result; -} - -DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, - DN_OSExecArgs *args, - DN_ErrSink *error) -{ -#if defined(DN_PLATFORM_EMSCRIPTEN) - DN_INVALID_CODE_PATHF("Unsupported operation"); -#endif - DN_ASSERTF_ONCE(args->environment.size == 0, "Unimplemented in POSIX"); - - DN_OSExecAsyncHandle result = {}; - if (cmd_line.size == 0) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); - int stdout_pipe[DN_OSPipeType__Count] = {}; - int stderr_pipe[DN_OSPipeType__Count] = {}; - - // NOTE: Open stdout pipe ////////////////////////////////////////////////////////////////////// - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { - if (pipe(stdout_pipe) == -1) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to create stdout pipe to redirect the output of the command '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - DN_ASSERT(stdout_pipe[DN_OSPipeType__Read] != 0); - DN_ASSERT(stdout_pipe[DN_OSPipeType__Write] != 0); - } - - DN_DEFER - { - if (result.os_error_code == 0 && result.exit_code == 0) - return; - close(stdout_pipe[DN_OSPipeType__Read]); - close(stdout_pipe[DN_OSPipeType__Write]); - }; - - // NOTE: Open stderr pipe ////////////////////////////////////////////////////////////////////// - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { - stderr_pipe[DN_OSPipeType__Read] = stdout_pipe[DN_OSPipeType__Read]; - stderr_pipe[DN_OSPipeType__Write] = stdout_pipe[DN_OSPipeType__Write]; - } else if (pipe(stderr_pipe) == -1) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to create stderr pipe to redirect the output of the command '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - DN_ASSERT(stderr_pipe[DN_OSPipeType__Read] != 0); - DN_ASSERT(stderr_pipe[DN_OSPipeType__Write] != 0); - } - - DN_DEFER { - if (result.os_error_code == 0 && result.exit_code == 0) - return; - close(stderr_pipe[DN_OSPipeType__Read]); - close(stderr_pipe[DN_OSPipeType__Write]); - }; - - pid_t child_pid = fork(); - if (child_pid < 0) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to fork process to execute the command '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - - if (child_pid == 0) { // Child process - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout) && - (dup2(stdout_pipe[DN_OSPipeType__Write], STDOUT_FILENO) == -1)) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to redirect stdout 'write' pipe for output of command '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && - (dup2(stderr_pipe[DN_OSPipeType__Write], STDERR_FILENO) == -1)) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to redirect stderr 'read' pipe for output of command '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - - // NOTE: Convert the command into something suitable for execvp - char **argv = - DN_Arena_NewArray(tmem.arena, char *, cmd_line.size + 1 /*null*/, DN_ZeroMem_Yes); - if (!argv) { - result.exit_code = -1; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to create argument values from command line '%.*s': Out of memory", - DN_STR_FMT(cmd_rendered)); - return result; - } - - for (DN_usize arg_index = 0; arg_index < cmd_line.size; arg_index++) { - DN_Str8 arg = cmd_line.data[arg_index]; - argv[arg_index] = DN_Str8_Copy(tmem.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated - } - - // NOTE: Change the working directory if there is one - char *prev_working_dir = nullptr; - DN_DEFER { - if (!prev_working_dir) - return; - if (result.os_error_code == 0) { - int chdir_result = chdir(prev_working_dir); - (void)chdir_result; - } - free(prev_working_dir); - }; - - if (args->working_dir.size) { - prev_working_dir = get_current_dir_name(); - DN_Str8 working_dir = DN_Str8_Copy(tmem.arena, args->working_dir); - if (chdir(working_dir.data) == -1) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to create argument values from command line '%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - } - - // NOTE: Execute the command. We reuse argv because the first arg, the - // binary to execute is guaranteed to be null-terminated. - if (execvp(argv[0], argv) < 0) { - result.os_error_code = errno; - DN_ErrSink_AppendF( - error, - result.os_error_code, - "Failed to execute command'%.*s': %s", - DN_STR_FMT(cmd_rendered), - strerror(result.os_error_code)); - return result; - } - } - - DN_ASSERT(result.os_error_code == 0); - DN_MEMCPY(&result.stdout_read, - &stdout_pipe[DN_OSPipeType__Read], - sizeof(stdout_pipe[DN_OSPipeType__Read])); - DN_MEMCPY(&result.stdout_write, - &stdout_pipe[DN_OSPipeType__Write], - sizeof(stdout_pipe[DN_OSPipeType__Write])); - - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { - DN_MEMCPY(&result.stderr_read, - &stderr_pipe[DN_OSPipeType__Read], - sizeof(stderr_pipe[DN_OSPipeType__Read])); - DN_MEMCPY(&result.stderr_write, - &stderr_pipe[DN_OSPipeType__Write], - sizeof(stderr_pipe[DN_OSPipeType__Write])); - } - result.exec_flags = args->flags; - DN_MEMCPY(&result.process, &child_pid, sizeof(child_pid)); - return result; -} - -#if !defined(DN_NO_SEMAPHORE) -// NOTE: [$SEMA] DN_OSSemaphore /////////////////////////////////////////////////////////////////// -DN_API DN_OSSemaphore DN_OS_SemaphoreInit(uint32_t initial_count) -{ - DN_OSSemaphore result = {}; - int pshared = 0; // Share the semaphore across all threads in the process - if (sem_init(&result.posix_handle, pshared, initial_count) == 0) - result.posix_init = true; - return result; -} - -DN_API bool DN_OS_SemaphoreIsValid(DN_OSSemaphore *semaphore) -{ - bool result = false; - if (semaphore) - result = semaphore->posix_init; - return result; -} - -DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) -{ - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - // TODO(doyle): Error handling? - if (semaphore->posix_init) - sem_destroy(&semaphore->posix_handle); - *semaphore = {}; -} - -// NOTE: These functions don't need semaphore to be passed by pointer, **BUT** -// the POSIX implementation disallows copies of sem_t. In particular: -// -// Source: The Open Group Base Specifications Issue 7, 2018 edition -// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_09 -// -// 2.9.9 Synchronization Object Copies and Alternative Mappings -// -// For barriers, condition variables, mutexes, and read-write locks, [TSH] -// [Option Start] if the process-shared attribute is set to -// PTHREAD_PROCESS_PRIVATE, [Option End] only the synchronization object at the -// address used to initialize it can be used for performing synchronization. The -// effect of referring to another mapping of the same object when locking, -// unlocking, or destroying the object is undefined. [...] The effect of -// referring to a copy of the object when locking, unlocking, or destroying it -// is undefined. - -DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount) -{ - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - - #if defined(DN_OS_WIN32) - sem_post_multiple(&semaphore->posix_handle, amount); // mingw extension - #else - DN_FOR_UINDEX(index, amount) - sem_post(&semaphore->posix_handle); - #endif // !defined(DN_OS_WIN32) -} - -DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, - uint32_t timeout_ms) -{ - DN_OSSemaphoreWaitResult result = {}; - if (!DN_OS_SemaphoreIsValid(semaphore)) - return result; - - if (timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT) { - int wait_result = 0; - do { - wait_result = sem_wait(&semaphore->posix_handle); - } while (wait_result == -1 && errno == EINTR); - - if (wait_result == 0) - result = DN_OSSemaphoreWaitResult_Success; - } else { - struct timespec abs_timeout = {}; - abs_timeout.tv_sec = timeout_ms / 1000; - abs_timeout.tv_nsec = (timeout_ms % 1000) * 1'000'000; - if (sem_timedwait(&semaphore->posix_handle, &abs_timeout) == 0) - result = DN_OSSemaphoreWaitResult_Success; - else if (errno == ETIMEDOUT) - result = DN_OSSemaphoreWaitResult_Timeout; - } - return result; -} -#endif // !defined(DN_NO_SEMAPHORE) - -#if !defined(DN_NO_THREAD) -// NOTE: [$MUTX] DN_OSMutex /////////////////////////////////////////////////////////////////////// -DN_API DN_OSMutex DN_OS_MutexInit() -{ - DN_OSMutex result = {}; - if (pthread_mutexattr_init(&result.posix_attribs) != 0) - return result; - if (pthread_mutex_init(&result.posix_handle, &result.posix_attribs) != 0) - return result; - return result; -} - -DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) -{ - if (!mutex) - return; - pthread_mutexattr_destroy(&mutex->posix_attribs); - pthread_mutex_destroy(&mutex->posix_handle); -} - -DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) -{ - if (!mutex) - return; - pthread_mutex_lock(&mutex->posix_handle); -} - -DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) -{ - if (!mutex) - return; - pthread_mutex_unlock(&mutex->posix_handle); -} - -// NOTE: [$THRD] DN_OSThread ///////////////////////////////////////////////////////////////////// -static void *DN_OS_ThreadFunc_(void *user_context) -{ - DN_OS_ThreadExecute_(user_context); - return nullptr; -} - -DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) -{ - bool result = false; - if (!thread) - return result; - - thread->func = func; - thread->user_context = user_context; - thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - - // TODO(doyle): Check if semaphore is valid - // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and - // the ID are different things. For pthreads then we just duplicate the - // thread ID to both variables - pthread_t p_thread = {}; - static_assert(sizeof(p_thread) <= sizeof(thread->handle), - "We store the thread handle opaquely in our abstraction, " - "there must be enough bytes to store pthread's structure"); - static_assert(sizeof(p_thread) <= sizeof(thread->thread_id), - "We store the thread handle opaquely in our abstraction, " - "there must be enough bytes to store pthread's structure"); - - pthread_attr_t attribs = {}; - pthread_attr_init(&attribs); - result = pthread_create(&p_thread, &attribs, DN_OS_ThreadFunc_, thread) == 0; - pthread_attr_destroy(&attribs); - - if (result) { - DN_MEMCPY(&thread->handle, &p_thread, sizeof(p_thread)); - DN_MEMCPY(&thread->thread_id, &p_thread, sizeof(p_thread)); - } - - if (result) { - DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); - } else { - DN_OS_SemaphoreDeinit(&thread->init_semaphore); - *thread = {}; - } - - return result; -} - -DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) -{ - if (!thread || !thread->handle) - return; - - pthread_t thread_id = {}; - DN_MEMCPY(&thread_id, &thread->thread_id, sizeof(thread_id)); - - void *return_val = nullptr; - pthread_join(thread_id, &return_val); - thread->handle = {}; - thread->thread_id = {}; -} - -DN_API uint32_t DN_OS_ThreadID() -{ - pid_t result = gettid(); - DN_ASSERT(gettid() >= 0); - return DN_CAST(uint32_t) result; -} - -DN_API void DN_Posix_ThreadSetName(DN_Str8 name) -{ - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); - pthread_t thread = pthread_self(); - pthread_setname_np(thread, (char *)copy.data); -} -#endif // !defined(DN_NO_THREAD) - -// NOTE: [$HTTP] DN_OSHttp //////////////////////////////////////////////////////////////////////// -#if 0 // TODO(doyle): Implement websockets for Windows and Emscripten -static EM_BOOL EMWebSocketOnOpenCallback(int type, const EmscriptenWebSocketOpenEvent *event, void *user_context) -{ - (void)user_context; - (void)type; - (void)event; - // EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(event->socket, R"({"jsonrpc":"2.0","id":1,"method": "eth_subscribe","params":["newHeads"]})"); - // if (result) - // DN_Log_InfoF("Failed to emscripten_websocket_send_utf8_text(): %d\n", result); - return EM_TRUE; -} - -static EM_BOOL EMWebSocketOnMsgCallback(int type, const EmscriptenWebSocketMessageEvent *event __attribute__((nonnull)), void *user_context) -{ - (void)type; - (void)user_context; - (void)event; - if (event->isText) { - DN_Log_InfoF("Received: %.*s", event->numBytes, event->data); - } else { - DN_Log_InfoF("Received: %d bytes", event->numBytes); - } - return EM_TRUE; -} - -static EM_BOOL EMWebSocketOnErrorCallback(int type, const EmscriptenWebSocketErrorEvent *event, void *user_context) -{ - (void)user_context; - (void)type; - (void)event; - return EM_TRUE; -} - -static EM_BOOL EMWebSocketOnCloseCallback(int type, const EmscriptenWebSocketCloseEvent *event, void *user_context) -{ - (void)user_context; - (void)type; - (void)event; - return EM_TRUE; -} -#endif - -#if defined(DN_PLATFORM_EMSCRIPTEN) -static void DN_OS_HttpRequestEMFetchOnSuccessCallback(emscripten_fetch_t *fetch) -{ - DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; - if (!DN_CHECK(response)) - return; - - response->http_status = DN_CAST(uint32_t) fetch->status; - response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); - if (response->body.data) - DN_MEMCPY(response->body.data, fetch->data, fetch->numBytes); - - DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); - DN_Atomic_AddU32(&response->done, 1); -} - -static void DN_OS_HttpRequestEMFetchOnErrorCallback(emscripten_fetch_t *fetch) -{ - DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *) fetch->userData; - if (!DN_CHECK(response)) - return; - - response->http_status = DN_CAST(uint32_t) fetch->status; - response->body = DN_Str8_Alloc(response->arena, fetch->numBytes, DN_ZeroMem_No); - if (response->body.size) - DN_MEMCPY(response->body.data, fetch->data, fetch->numBytes); - - DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); - DN_Atomic_AddU32(&response->done, 1); -} -#endif - -DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, - DN_Arena *arena, - DN_Str8 host, - DN_Str8 path, - DN_OSHttpRequestSecure secure, - DN_Str8 method, - DN_Str8 body, - DN_Str8 headers) -{ - if (!response || !arena) - return; - - response->arena = arena; - response->builder.arena = - response->tmem_arena ? response->tmem_arena : &response->tmp_arena; - - DN_Arena *tmem = response->tmem_arena; - DN_TLSTMem tmem_ = DN_TLS_TMem(arena); - if (!tmem) - tmem = tmem_.arena; - -#if defined(DN_PLATFORM_EMSCRIPTEN) - emscripten_fetch_attr_t fetch_attribs = {}; - emscripten_fetch_attr_init(&fetch_attribs); - - if (method.size >= sizeof(fetch_attribs.requestMethod)) { - response->error_msg = - DN_Str8_InitF(arena, - "Request method in EM has a size limit of 31 characters, method was " - "'%.*s' which is %zu characters long", - DN_STR_FMT(method), - method.size); - DN_CHECKF(method.size < sizeof(fetch_attribs.requestMethod), - "%.*s", - DN_STR_FMT(response->error_msg)); - response->error_code = DN_CAST(uint32_t) - 1; - DN_Atomic_AddU32(&response->done, 1); - return; - } - - DN_MEMCPY(fetch_attribs.requestMethod, method.data, method.size); - - fetch_attribs.requestData = body.data; - fetch_attribs.requestDataSize = body.size; - fetch_attribs.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; - fetch_attribs.onsuccess = DN_OS_HttpRequestEMFetchOnSuccessCallback; - fetch_attribs.onerror = DN_OS_HttpRequestEMFetchOnErrorCallback; - fetch_attribs.userData = response; - - DN_Str8 url = DN_Str8_InitF(scratch_arena, "%.*s%.*s", DN_STR_FMT(host), DN_STR_FMT(path)); - DN_Log_InfoF("Initiating HTTP '%s' request to '%.*s' with payload '%.*s'", - fetch_attribs.requestMethod, - DN_STR_FMT(url), - DN_STR_FMT(body)); - response->on_complete_semaphore = DN_OS_SemaphoreInit(0); - response->em_handle = emscripten_fetch(&fetch_attribs, url.data); -#else // #elif defined(DN_OS_WIN32) - DN_INVALID_CODE_PATHF("Unimplemented function"); -#endif -} - -DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) -{ -// NOTE: Cleanup -#if defined(DN_PLATFORM_EMSCRIPTEN) - if (response->em_handle) { - emscripten_fetch_close(response->em_handle); - response->em_handle = nullptr; - } -#endif // #elif defined(DN_OS_WIN32) - - DN_Arena_Deinit(&response->tmp_arena); - if (DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) - DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); - *response = {}; -} diff --git a/dqn_os_win32.cpp b/dqn_os_win32.cpp deleted file mode 100644 index 58b6ba7..0000000 --- a/dqn_os_win32.cpp +++ /dev/null @@ -1,1728 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ -// $$ / $$ |$$ / \__| $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | -// $$ | $$ |\$$$$$$\ $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | -// $$ | $$ | \____$$\ $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ -// $$ | $$ |$$\ $$ | $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | -// $$$$$$ |\$$$$$$ | $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ -// \______/ \______/ \__/ \__|\______|\__| \__| \______/ \________| -// -// dqn_os_win32.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$VMEM] DN_OSMem ///////////////////////////////////////////////////////////////////////// -static uint32_t DN_OS_MemConvertPageToOSFlags_(uint32_t protect) -{ - DN_ASSERT((protect & ~DN_OSMemPage_All) == 0); - DN_ASSERT(protect != 0); - uint32_t result = 0; - - if (protect & DN_OSMemPage_NoAccess) { - result = PAGE_NOACCESS; - } else { - if (protect & DN_OSMemPage_ReadWrite) { - result = PAGE_READWRITE; - } else if (protect & DN_OSMemPage_Read) { - result = PAGE_READONLY; - } else if (protect & DN_OSMemPage_Write) { - DN_Log_WarningF("Windows does not support write-only pages, granting read+write access"); - result = PAGE_READWRITE; - } - } - - if (protect & DN_OSMemPage_Guard) - result |= PAGE_GUARD; - - DN_ASSERTF(result != PAGE_GUARD, "Page guard is a modifier, you must also specify a page permission like read or/and write"); - return result; -} - -DN_API void *DN_OS_MemReserve(DN_USize size, DN_OSMemCommit commit, uint32_t page_flags) -{ - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - unsigned long flags = MEM_RESERVE | (commit == DN_OSMemCommit_Yes ? MEM_COMMIT : 0); - void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_total, DN_CAST(bool)(flags & MEM_COMMIT)); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_frame, DN_CAST(bool)(flags & MEM_COMMIT)); - return result; -} - -DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, uint32_t page_flags) -{ - bool result = false; - if (!ptr || size == 0) - return false; - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; - DN_Atomic_AddU64(&g_dn_core->mem_allocs_total, 1); - DN_Atomic_AddU64(&g_dn_core->mem_allocs_frame, 1); - return result; -} - -DN_API void DN_OS_MemDecommit(void *ptr, DN_USize size) -{ - // NOTE: This is a decommit call, which is explicitly saying to free the - // pages but not the address space, you would use OS_MemRelease to release - // everything. - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6250) // Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs). This causes address space leaks. - VirtualFree(ptr, size, MEM_DECOMMIT); - DN_MSVC_WARNING_POP -} - -DN_API void DN_OS_MemRelease(void *ptr, DN_USize size) -{ - (void)size; - VirtualFree(ptr, 0, MEM_RELEASE); -} - -DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, uint32_t page_flags) -{ - if (!ptr || size == 0) - return 0; - - static DN_Str8 const ALIGNMENT_ERROR_MSG = - DN_STR8("Page protection requires pointers to be page aligned because we " - "can only guard memory at a multiple of the page boundary."); - DN_ASSERTF(DN_IsPowerOfTwoAligned(DN_CAST(uintptr_t)ptr, g_dn_core->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); - DN_ASSERTF(DN_IsPowerOfTwoAligned(size, g_dn_core->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data); - - unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); - unsigned long prev_flags = 0; - int result = VirtualProtect(ptr, size, os_page_flags, &prev_flags); - - (void)prev_flags; - if (result == 0) - DN_ASSERTF(result, "VirtualProtect failed"); - return result; -} - -DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZeroMem zero_mem) -{ - uint32_t flags = zero_mem == DN_ZeroMem_Yes ? HEAP_ZERO_MEMORY : 0; - DN_ASSERT(size <= DN_CAST(DWORD)(-1)); - void *result = HeapAlloc(GetProcessHeap(), flags, DN_CAST(DWORD) size); - return result; -} - -DN_API void DN_OS_MemDealloc(void *ptr) -{ - HeapFree(GetProcessHeap(), 0, ptr); -} - -// NOTE: [$DATE] Date ////////////////////////////////////////////////////////////////////////////// -DN_API DN_OSDateTime DN_OS_DateLocalTimeNow() -{ - SYSTEMTIME sys_time; - GetLocalTime(&sys_time); - - DN_OSDateTime result = {}; - result.hour = DN_CAST(uint8_t) sys_time.wHour; - result.minutes = DN_CAST(uint8_t) sys_time.wMinute; - result.seconds = DN_CAST(uint8_t) sys_time.wSecond; - result.day = DN_CAST(uint8_t) sys_time.wDay; - result.month = DN_CAST(uint8_t) sys_time.wMonth; - result.year = DN_CAST(int16_t) sys_time.wYear; - return result; -} - -const uint64_t DN_OS_WIN32_UNIX_TIME_START = 0x019DB1DED53E8000; // January 1, 1970 (start of Unix epoch) in "ticks" -const uint64_t DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds - -DN_API uint64_t DN_OS_DateUnixTimeNs() -{ - FILETIME file_time; - GetSystemTimeAsFileTime(&file_time); - - // NOTE: Filetime returned is in intervals of 100 nanoeseconds so we - // multiply by 100 to get nanoseconds. - LARGE_INTEGER date_time; - date_time.u.LowPart = file_time.dwLowDateTime; - date_time.u.HighPart = file_time.dwHighDateTime; - uint64_t result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) * 100; - return result; -} - -static SYSTEMTIME DN_OS_DateToSystemTime_(DN_OSDateTime date) -{ - SYSTEMTIME result = {}; - result.wYear = date.year; - result.wMonth = date.month; - result.wDay = date.day; - result.wHour = date.hour; - result.wMinute = date.minutes; - result.wSecond = date.seconds; - return result; -} - -static uint64_t DN_OS_SystemTimeToUnixTimeS_(SYSTEMTIME *sys_time) -{ - FILETIME file_time = {}; - SystemTimeToFileTime(sys_time, &file_time); - - LARGE_INTEGER date_time; - date_time.u.LowPart = file_time.dwLowDateTime; - date_time.u.HighPart = file_time.dwHighDateTime; - uint64_t result = (date_time.QuadPart - DN_OS_WIN32_UNIX_TIME_START) / DN_OS_WIN32_FILE_TIME_TICKS_PER_SECOND; - return result; -} - -DN_API uint64_t DN_OS_DateLocalToUnixTimeS(DN_OSDateTime date) -{ - SYSTEMTIME local_time = DN_OS_DateToSystemTime_(date); - SYSTEMTIME sys_time = {}; - TzSpecificLocalTimeToSystemTime(nullptr, &local_time, &sys_time); - uint64_t result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); - return result; -} - -DN_API uint64_t DN_OS_DateToUnixTimeS(DN_OSDateTime date) -{ - DN_ASSERT(DN_OS_DateIsValid(date)); - - SYSTEMTIME sys_time = DN_OS_DateToSystemTime_(date); - uint64_t result = DN_OS_SystemTimeToUnixTimeS_(&sys_time); - return result; -} - -DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(uint64_t time) -{ - // NOTE: Windows epoch time starts from Jan 1, 1601 and counts in - // 100-nanoseconds intervals. - // - // See: https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913 - - uint64_t win32_time = 116'444'736'000'000'000 + (time * 10'000'000); - SYSTEMTIME sys_time = {}; - FILETIME file_time = {}; - file_time.dwLowDateTime = (DWORD)win32_time; - file_time.dwHighDateTime = win32_time >> 32; - FileTimeToSystemTime(&file_time, &sys_time); - - DN_OSDateTime result = {}; - result.year = DN_CAST(uint16_t)sys_time.wYear; - result.month = DN_CAST(uint8_t)sys_time.wMonth; - result.day = DN_CAST(uint8_t)sys_time.wDay; - result.hour = DN_CAST(uint8_t)sys_time.wHour; - result.minutes = DN_CAST(uint8_t)sys_time.wMinute; - result.seconds = DN_CAST(uint8_t)sys_time.wSecond; - return result; -} - -DN_API bool DN_OS_SecureRNGBytes(void *buffer, uint32_t size) -{ - if (!buffer || size < 0) - return false; - - if (size == 0) - return true; - - bool init = true; - DN_TicketMutex_Begin(&g_dn_core->win32_bcrypt_rng_mutex); - if (!g_dn_core->win32_bcrypt_rng_handle) - { - wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&g_dn_core->win32_bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (!g_dn_core->win32_bcrypt_rng_handle || init_status != 0) - { - DN_Log_ErrorF("Failed to initialise random number generator, error: %d", init_status); - init = false; - } - } - DN_TicketMutex_End(&g_dn_core->win32_bcrypt_rng_mutex); - - if (!init) - return false; - - long gen_status = BCryptGenRandom(g_dn_core->win32_bcrypt_rng_handle, DN_CAST(unsigned char *)buffer, size, 0 /*flags*/); - if (gen_status != 0) - { - DN_Log_ErrorF("Failed to generate random bytes: %d", gen_status); - return false; - } - - return true; -} - -DN_API bool DN_OS_SetEnvVar(DN_Str8 name, DN_Str8 value) -{ - DN_TLSTMem tmem = DN_TLS_PushTMem(nullptr); - DN_Str16 name16 = DN_Win_Str8ToStr16(tmem.arena, name); - DN_Str16 value16 = DN_Win_Str8ToStr16(tmem.arena, value); - bool result = SetEnvironmentVariableW(name16.data, value16.data) != 0; - return result; -} - -DN_API DN_Str8 DN_OS_EXEPath(DN_Arena *arena) -{ - DN_Str8 result = {}; - if (!arena) - return result; - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str16 exe_dir16 = DN_Win_EXEPathW(tmem.arena); - result = DN_Win_Str16ToStr8(arena, exe_dir16); - return result; -} - -DN_API void DN_OS_SleepMs(DN_UInt milliseconds) -{ - Sleep(milliseconds); -} - -DN_API uint64_t DN_OS_PerfCounterFrequency() -{ - uint64_t result = g_dn_core->win32_qpc_frequency.QuadPart; - DN_ASSERTF(result, "Initialise the library with DN_Library_Init() to get a valid QPC frequency value"); - return result; -} - -DN_API uint64_t DN_OS_PerfCounterNow() -{ - LARGE_INTEGER integer = {}; - QueryPerformanceCounter(&integer); - uint64_t result = integer.QuadPart; - return result; -} - -#if !defined(DN_NO_OS_FILE_API) -static uint64_t DN_Win_FileTimeToSeconds_(FILETIME const *time) -{ - ULARGE_INTEGER time_large_int = {}; - time_large_int.u.LowPart = time->dwLowDateTime; - time_large_int.u.HighPart = time->dwHighDateTime; - uint64_t result = (time_large_int.QuadPart / 10000000ULL) - 11644473600ULL; - return result; -} - -DN_API DN_OSPathInfo DN_OS_PathInfo(DN_Str8 path) -{ - DN_OSPathInfo result = {}; - if (!DN_Str8_HasData(path)) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (!GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) - return result; - - result.exists = true; - result.create_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftCreationTime); - result.last_access_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftLastAccessTime); - result.last_write_time_in_s = DN_Win_FileTimeToSeconds_(&attrib_data.ftLastWriteTime); - - LARGE_INTEGER large_int = {}; - large_int.u.HighPart = DN_CAST(int32_t)attrib_data.nFileSizeHigh; - large_int.u.LowPart = attrib_data.nFileSizeLow; - result.size = (uint64_t)large_int.QuadPart; - - if (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) { - if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - result.type = DN_OSPathInfoType_Directory; - else - result.type = DN_OSPathInfoType_File; - } - - return result; -} - -DN_API bool DN_OS_PathDelete(DN_Str8 path) -{ - bool result = false; - if (!DN_Str8_HasData(path)) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - if (path16.size) { - result = DeleteFileW(path16.data); - if (!result) - result = RemoveDirectoryW(path16.data); - } - return result; -} - -DN_API bool DN_OS_FileExists(DN_Str8 path) -{ - bool result = false; - if (!DN_Str8_HasData(path)) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - if (path16.size) { - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { - result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && - !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - } - return result; -} - -DN_API bool DN_OS_CopyFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err) -{ - bool result = false; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 src16 = DN_Win_Str8ToStr16(tmem.arena, src); - DN_Str16 dest16 = DN_Win_Str8ToStr16(tmem.arena, dest); - - int fail_if_exists = overwrite == false; - result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0; - - if (!result) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_ErrSink_AppendF(err, - win_error.code, - "Failed to copy file '%.*s' to '%.*s': (%u) %.*s", - DN_STR_FMT(src), - DN_STR_FMT(dest), - win_error.code, - DN_STR_FMT(win_error.msg)); - } - return result; -} - -DN_API bool DN_OS_MoveFile(DN_Str8 src, DN_Str8 dest, bool overwrite, DN_ErrSink *err) -{ - bool result = false; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 src16 = DN_Win_Str8ToStr16(tmem.arena, src); - DN_Str16 dest16 = DN_Win_Str8ToStr16(tmem.arena, dest); - - unsigned long flags = MOVEFILE_COPY_ALLOWED; - if (overwrite) { - flags |= MOVEFILE_REPLACE_EXISTING; - } - - result = MoveFileExW(src16.data, dest16.data, flags) != 0; - if (!result) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_ErrSink_AppendF(err, - win_error.code, - "Failed to move file '%.*s' to '%.*s': (%u) %.*s", - DN_STR_FMT(src), - DN_STR_FMT(dest), - win_error.code, - DN_STR_FMT(win_error.msg)); - } - return result; -} - -DN_API bool DN_OS_MakeDir(DN_Str8 path) -{ - bool result = true; - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - - // NOTE: Go back from the end of the string to all the directories in the - // string, and try to create them. Since Win32 API cannot create - // intermediate directories that don't exist in a path we need to go back - // and record all the directories until we encounter one that exists. - // - // From that point onwards go forwards and make all the directories - // inbetween by null-terminating the string temporarily, creating the - // directory and so forth until we reach the end. - // - // If we find a file at some point in the path we fail out because the - // series of directories can not be made if a file exists with the same - // name. - for (DN_USize index = 0; index < path16.size; index++) { - bool first_char = index == (path16.size - 1); - wchar_t ch = path16.data[index]; - if (ch == '/' || ch == '\\' || first_char) { - wchar_t temp = path16.data[index]; - if (!first_char) - path16.data[index] = 0; // Temporarily null terminate it - - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - bool successful = GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data); // Check - - if (successful) { - if (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - // NOTE: The directory exists, continue iterating the path - } else { - // NOTE: There's some kind of file that exists at the path - // but it's not a directory. This request to make a - // directory is invalid. - return false; - } - } else { - // NOTE: There's nothing that exists at this path, we can create - // a directory here - result |= (CreateDirectoryW(path16.data, nullptr) == 0); - } - - if (!first_char) - path16.data[index] = temp; // Undo null termination - } - } - return result; -} - - -DN_API bool DN_OS_DirExists(DN_Str8 path) -{ - bool result = false; - if (!DN_Str8_HasData(path)) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - if (path16.size) { - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { - result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && - (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - } - - return result; -} - -DN_API bool DN_OS_DirIterate(DN_Str8 path, DN_OSDirIterator *it) -{ - if (!DN_Str8_HasData(path) || !it || path.size <= 0) - return false; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Win_FolderIteratorW wide_it = {}; - DN_Str16 path16 = {}; - if (it->handle) { - wide_it.handle = it->handle; - } else { - bool needs_asterisks = DN_Str8_EndsWith(path, DN_STR8("\\")) || - DN_Str8_EndsWith(path, DN_STR8("/")); - bool has_glob = DN_Str8_EndsWith(path, DN_STR8("\\*")) || - DN_Str8_EndsWith(path, DN_STR8("/*")); - - DN_Str8 adjusted_path = path; - if (!has_glob) { - // NOTE: We are missing the glob for enumerating the files, we will - // add those characters in this branch, so overwrite the null - // character, add the glob and re-null terminate the buffer. - if (needs_asterisks) - adjusted_path = DN_OS_PathF(tmem.arena, "%.*s*", DN_STR_FMT(path)); - else - adjusted_path = DN_OS_PathF(tmem.arena, "%.*s/*", DN_STR_FMT(path)); - } - - path16 = DN_Win_Str8ToStr16(tmem.arena, adjusted_path); - if (path16.size <= 0) // Conversion error - return false; - } - - bool result = DN_Win_DirWIterate(path16, &wide_it); - it->handle = wide_it.handle; - if (result) { - int size = DN_Win_Str16ToStr8Buffer(wide_it.file_name, it->buffer, DN_ARRAY_UCOUNT(it->buffer)); - it->file_name = DN_Str8_Init(it->buffer, size); - } - - return result; -} - -// NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DN_API DN_OSFile DN_OS_FileOpen(DN_Str8 path, DN_OSFileOpen open_mode, uint32_t access, DN_ErrSink *err) -{ - DN_OSFile result = {}; - if (!DN_Str8_HasData(path) || path.size <= 0) - return result; - - if ((access & ~DN_OSFileAccess_All) || ((access & DN_OSFileAccess_All) == 0)) { - DN_INVALID_CODE_PATH; - return result; - } - - unsigned long create_flag = 0; - switch (open_mode) { - case DN_OSFileOpen_CreateAlways: create_flag = CREATE_ALWAYS; break; - case DN_OSFileOpen_OpenIfExist: create_flag = OPEN_EXISTING; break; - case DN_OSFileOpen_OpenAlways: create_flag = OPEN_ALWAYS; break; - default: DN_INVALID_CODE_PATH; return result; - } - - unsigned long access_mode = 0; - if (access & DN_OSFileAccess_AppendOnly) { - DN_ASSERTF((access & ~DN_OSFileAccess_AppendOnly) == 0, - "Append can only be applied exclusively to the file, other access modes not permitted"); - access_mode = FILE_APPEND_DATA; - } else { - if (access & DN_OSFileAccess_Read) - access_mode |= GENERIC_READ; - if (access & DN_OSFileAccess_Write) - access_mode |= GENERIC_WRITE; - if (access & DN_OSFileAccess_Execute) - access_mode |= GENERIC_EXECUTE; - } - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem.arena, path); - void *handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, - /*DWORD dwDesiredAccess*/ access_mode, - /*DWORD dwShareMode*/ FILE_SHARE_READ | FILE_SHARE_WRITE, - /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, - /*DWORD dwCreationDisposition*/ create_flag, - /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL, - /*HANDLE hTemplateFile*/ nullptr); - - if (handle == INVALID_HANDLE_VALUE) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.error = true; - DN_ErrSink_AppendF(err, win_error.code, "Failed to open file at '%.*s': '%.*s'", DN_STR_FMT(path), DN_STR_FMT(win_error.msg)); - return result; - } - - result.handle = handle; - return result; -} - -DN_API bool DN_OS_FileRead(DN_OSFile *file, void *buffer, DN_USize size, DN_ErrSink *err) -{ - if (!file || !file->handle || file->error || !buffer || size <= 0) - return false; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - if (!DN_CHECK(size <= (unsigned long)-1)) { - DN_Str8 buffer_size_str8 = DN_U64ToByteSizeStr8(tmem.arena, size, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF( - err, - 1 /*error_code*/, - "Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO", - DN_STR_FMT(buffer_size_str8)); - return false; - } - - unsigned long bytes_read = 0; - unsigned long read_result = ReadFile(/*HANDLE hFile*/ file->handle, - /*LPVOID lpBuffer*/ buffer, - /*DWORD nNumberOfBytesToRead*/ DN_CAST(unsigned long)size, - /*LPDWORD lpNumberOfByesRead*/ &bytes_read, - /*LPOVERLAPPED lpOverlapped*/ nullptr); - if (read_result == 0) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_ErrSink_AppendF(err, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DN_STR_FMT(win_error.msg)); - return false; - } - - if (bytes_read != size) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_ErrSink_AppendF( - err, - win_error.code, - "Failed to read the desired number of bytes from file, we read %uB but we expected %uB: (%u) %.*s", - bytes_read, - DN_CAST(unsigned long)size, - win_error.code, - DN_STR_FMT(win_error.msg)); - return false; - } - - return true; -} - -DN_API bool DN_OS_FileWritePtr(DN_OSFile *file, void const *buffer, DN_USize size, DN_ErrSink *err) -{ - if (!file || !file->handle || file->error || !buffer || size <= 0) - return false; - - bool result = true; - char const *end = DN_CAST(char *) buffer + size; - for (char const *ptr = DN_CAST(char const *) buffer; result && ptr != end;) { - unsigned long write_size = DN_CAST(unsigned long)DN_MIN((unsigned long)-1, end - ptr); - unsigned long bytes_written = 0; - result = WriteFile(file->handle, ptr, write_size, &bytes_written, nullptr /*lpOverlapped*/) != 0; - ptr += bytes_written; - } - - if (!result) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_Str8 buffer_size_str8 = DN_U64ToByteSizeStr8(tmem.arena, size, DN_U64ByteSizeType_Auto); - DN_ErrSink_AppendF(err, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DN_STR_FMT(buffer_size_str8), DN_STR_FMT(win_error.msg)); - } - return result; -} - -DN_API bool DN_OS_FileFlush(DN_OSFile *file, DN_ErrSink *err) -{ - if (!file || !file->handle || file->error) - return false; - - BOOL result = FlushFileBuffers(DN_CAST(HANDLE)file->handle); - if (!result) { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_WinError win_error = DN_Win_LastError(tmem.arena); - DN_ErrSink_AppendF(err, win_error.code, "Failed to flush file buffer to disk: %.*s", DN_STR_FMT(win_error.msg)); - } - - return DN_CAST(bool)result; -} - -DN_API void DN_OS_FileClose(DN_OSFile *file) -{ - if (!file || !file->handle || file->error) - return; - CloseHandle(file->handle); - *file = {}; -} -#endif // !defined(DN_NO_OS_FILE_API) - -// NOTE: [$EXEC] DN_OSExec //////////////////////////////////////////////////////////////////////// -DN_API void DN_OS_Exit(int32_t exit_code) -{ - ExitProcess(DN_CAST(UINT)exit_code); -} - -DN_API DN_OSExecResult DN_OS_ExecPump(DN_OSExecAsyncHandle handle, - char *stdout_buffer, - size_t *stdout_size, - char *stderr_buffer, - size_t *stderr_size, - uint32_t timeout_ms, - DN_ErrSink *err) -{ - DN_OSExecResult result = {}; - size_t stdout_buffer_size = 0; - size_t stderr_buffer_size = 0; - if (stdout_size) { - stdout_buffer_size = *stdout_size; - *stdout_size = 0; - } - - if (stderr_size) { - stderr_buffer_size = *stderr_size; - *stderr_size = 0; - } - - if (!handle.process || handle.os_error_code || handle.exit_code) { - if (handle.os_error_code) - result.os_error_code = handle.os_error_code; - else - result.exit_code = handle.exit_code; - - DN_ASSERT(!handle.stdout_read); - DN_ASSERT(!handle.stdout_write); - DN_ASSERT(!handle.stderr_read); - DN_ASSERT(!handle.stderr_write); - DN_ASSERT(!handle.process); - return result; - } - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DWORD stdout_bytes_available = 0; - DWORD stderr_bytes_available = 0; - PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr); - PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr); - - DWORD exec_result = WAIT_TIMEOUT; - if (stdout_bytes_available == 0 && stderr_bytes_available == 0) - exec_result = WaitForSingleObject(handle.process, timeout_ms); - - if (exec_result == WAIT_FAILED) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF(err, result.os_error_code, "Executed command failed to terminate: %.*s", DN_STR_FMT(win_error.msg)); - } else { - if (DN_CHECK(exec_result == WAIT_TIMEOUT || exec_result == WAIT_OBJECT_0)) { - // NOTE: Read stdout from process ////////////////////////////////////////////////////// - // If the pipes are full, the process will block. We periodically - // flush the pipes to make sure this doesn't happen - char sink[DN_KILOBYTES(8)]; - stdout_bytes_available = 0; - if (PeekNamedPipe(handle.stdout_read, nullptr, 0, nullptr, &stdout_bytes_available, nullptr)) { - if (stdout_bytes_available) { - DWORD bytes_read = 0; - char *dest_buffer = handle.stdout_write && stdout_buffer ? stdout_buffer : sink; - size_t dest_size = handle.stdout_write && stdout_buffer ? stdout_buffer_size : DN_ARRAY_UCOUNT(sink); - BOOL success = ReadFile(handle.stdout_read, dest_buffer, DN_CAST(DWORD)dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stdout_size) - *stdout_size = bytes_read; - } - } - - // NOTE: Read stderr from process ////////////////////////////////////////////////////// - stderr_bytes_available = 0; - if (PeekNamedPipe(handle.stderr_read, nullptr, 0, nullptr, &stderr_bytes_available, nullptr)) { - if (stderr_bytes_available) { - char *dest_buffer = handle.stderr_write && stderr_buffer ? stderr_buffer : sink; - size_t dest_size = handle.stderr_write && stderr_buffer ? stderr_buffer_size : DN_ARRAY_UCOUNT(sink); - DWORD bytes_read = 0; - BOOL success = ReadFile(handle.stderr_read, dest_buffer, DN_CAST(DWORD)dest_size, &bytes_read, NULL); - (void)success; // TODO: - if (stderr_size) - *stderr_size = bytes_read; - } - } - } - } - - result.finished = exec_result == WAIT_OBJECT_0 || exec_result == WAIT_FAILED; - if (exec_result == WAIT_OBJECT_0) { - DWORD exit_status; - if (GetExitCodeProcess(handle.process, &exit_status)) { - result.exit_code = exit_status; - } else { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF(err, - result.os_error_code, - "Failed to retrieve command exit code: %.*s", - DN_STR_FMT(win_error.msg)); - } - - // NOTE: Cleanup /////////////////////////////////////////////////////////////////////////////// - CloseHandle(handle.stdout_write); - CloseHandle(handle.stderr_write); - CloseHandle(handle.stdout_read); - CloseHandle(handle.stderr_read); - CloseHandle(handle.process); - } - - result.stdout_text = DN_Str8_Init(stdout_buffer, stdout_size ? *stdout_size : 0); - result.stderr_text = DN_Str8_Init(stderr_buffer, stderr_size ? *stderr_size : 0); - return result; -} - - -DN_API DN_OSExecResult DN_OS_ExecWait(DN_OSExecAsyncHandle handle, DN_Arena *arena, DN_ErrSink *err) -{ - DN_OSExecResult result = {}; - if (!handle.process || handle.os_error_code || handle.exit_code) { - result.finished = true; - if (handle.os_error_code) - result.os_error_code = handle.os_error_code; - else - result.exit_code = handle.exit_code; - - DN_ASSERT(!handle.stdout_read); - DN_ASSERT(!handle.stdout_write); - DN_ASSERT(!handle.stderr_read); - DN_ASSERT(!handle.stderr_write); - DN_ASSERT(!handle.process); - return result; - } - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8Builder stdout_builder = {}; - DN_Str8Builder stderr_builder = {}; - if (arena) { - stdout_builder.arena = tmem.arena; - stderr_builder.arena = tmem.arena; - } - - uint32_t const SLOW_WAIT_TIME_MS = 100; - uint32_t const FAST_WAIT_TIME_MS = 20; - uint32_t wait_ms = FAST_WAIT_TIME_MS; - while (!result.finished) { - size_t stdout_size = DN_KILOBYTES(8); - size_t stderr_size = DN_KILOBYTES(8); - char *stdout_buffer = DN_Arena_NewArray(tmem.arena, char, stdout_size, DN_ZeroMem_No); - char *stderr_buffer = DN_Arena_NewArray(tmem.arena, char, stderr_size, DN_ZeroMem_No); - result = DN_OS_ExecPump(handle, stdout_buffer, &stdout_size, stderr_buffer, &stderr_size, wait_ms, err); - DN_Str8Builder_AppendCopy(&stdout_builder, result.stdout_text); - DN_Str8Builder_AppendCopy(&stderr_builder, result.stderr_text); - wait_ms = (DN_Str8_HasData(result.stdout_text) || DN_Str8_HasData(result.stderr_text)) ? FAST_WAIT_TIME_MS : SLOW_WAIT_TIME_MS; - } - - // NOTE: Get stdout/stderr. If no arena is passed this is a no-op ////////////////////////////// - result.stdout_text = DN_Str8Builder_Build(&stdout_builder, arena); - result.stderr_text = DN_Str8Builder_Build(&stderr_builder, arena); - return result; -} - -DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExecArgs *args, DN_ErrSink *err) -{ - // NOTE: Pre-amble ///////////////////////////////////////////////////////////////////////////// - DN_OSExecAsyncHandle result = {}; - if (cmd_line.size == 0) - return result; - - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 cmd_rendered = DN_Slice_Str8Render(tmem.arena, cmd_line, DN_STR8(" ")); - DN_Str16 cmd16 = DN_Win_Str8ToStr16(tmem.arena, cmd_rendered); - DN_Str16 working_dir16 = DN_Win_Str8ToStr16(tmem.arena, args->working_dir); - - DN_Str8Builder env_builder = DN_Str8Builder_Init_TLS(); - DN_Str8Builder_AppendArrayRef(&env_builder, args->environment.data, args->environment.size); - if (env_builder.string_size) - DN_Str8Builder_AppendRef(&env_builder, DN_STR8("\0")); - - DN_Str8 env_block8 = DN_Str8Builder_BuildDelimited_TLS(&env_builder, DN_STR8("\0")); - DN_Str16 env_block16 = {}; - if (env_block8.size) - env_block16 = DN_Win_Str8ToStr16(tmem.arena, env_block8); - - // NOTE: Stdout/err security attributes //////////////////////////////////////////////////////// - SECURITY_ATTRIBUTES save_std_security_attribs = {}; - save_std_security_attribs.nLength = sizeof(save_std_security_attribs); - save_std_security_attribs.bInheritHandle = true; - - // NOTE: Redirect stdout /////////////////////////////////////////////////////////////////////// - HANDLE stdout_read = {}; - HANDLE stdout_write = {}; - DN_DEFER { - if (result.os_error_code || result.exit_code) { - CloseHandle(stdout_read); - CloseHandle(stdout_write); - } - }; - - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStdout)) { - if (!CreatePipe(&stdout_read, &stdout_write, &save_std_security_attribs, /*nSize*/ 0)) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF( - err, - result.os_error_code, - "Failed to create stdout pipe to redirect the output of the command '%.*s': %.*s", - DN_STR_FMT(cmd_rendered), - DN_STR_FMT(win_error.msg)); - return result; - } - - if (!SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF(err, - result.os_error_code, - "Failed to make stdout 'read' pipe non-inheritable when trying to " - "execute command '%.*s': %.*s", - DN_STR_FMT(cmd_rendered), - DN_STR_FMT(win_error.msg)); - return result; - } - } - - // NOTE: Redirect stderr /////////////////////////////////////////////////////////////////////// - HANDLE stderr_read = {}; - HANDLE stderr_write = {}; - DN_DEFER { - if (result.os_error_code || result.exit_code) { - CloseHandle(stderr_read); - CloseHandle(stderr_write); - } - }; - - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr)) { - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { - stderr_read = stdout_read; - stderr_write = stdout_write; - } else { - if (!CreatePipe(&stderr_read, &stderr_write, &save_std_security_attribs, /*nSize*/ 0)) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF( - err, - result.os_error_code, - "Failed to create stderr pipe to redirect the output of the command '%.*s': %.*s", - DN_STR_FMT(cmd_rendered), - DN_STR_FMT(win_error.msg)); - return result; - } - - if (!SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF(err, - result.os_error_code, - "Failed to make stderr 'read' pipe non-inheritable when trying to " - "execute command '%.*s': %.*s", - DN_STR_FMT(cmd_rendered), - DN_STR_FMT(win_error.msg)); - return result; - } - } - } - - // NOTE: Execute command /////////////////////////////////////////////////////////////////////// - PROCESS_INFORMATION proc_info = {}; - STARTUPINFOW startup_info = {}; - startup_info.cb = sizeof(STARTUPINFOW); - startup_info.hStdError = stderr_write ? stderr_write : GetStdHandle(STD_ERROR_HANDLE); - startup_info.hStdOutput = stdout_write ? stdout_write : GetStdHandle(STD_OUTPUT_HANDLE); - startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - startup_info.dwFlags |= STARTF_USESTDHANDLES; - BOOL create_result = CreateProcessW(nullptr, - cmd16.data, - nullptr, - nullptr, - true, - CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, - env_block16.data, - working_dir16.data, - &startup_info, - &proc_info); - if (!create_result) { - DN_WinError win_error = DN_Win_LastError(tmem.arena); - result.os_error_code = win_error.code; - DN_ErrSink_AppendF(err, - result.os_error_code, - "Failed to execute command '%.*s': %.*s", - DN_STR_FMT(cmd_rendered), - DN_STR_FMT(win_error.msg)); - return result; - } - - // NOTE: Post-amble //////////////////////////////////////////////////////////////////////////// - CloseHandle(proc_info.hThread); - result.process = proc_info.hProcess; - result.stdout_read = stdout_read; - result.stdout_write = stdout_write; - if (DN_Bit_IsSet(args->flags, DN_OSExecFlags_SaveStderr) && DN_Bit_IsNotSet(args->flags, DN_OSExecFlags_MergeStderrToStdout)) { - result.stderr_read = stderr_read; - result.stderr_write = stderr_write; - } - result.exec_flags = args->flags; - return result; -} - -#if !defined(DN_NO_SEMAPHORE) -// NOTE: [$SEMA] DN_OSSemaphore /////////////////////////////////////////////////////////////////// -DN_API DN_OSSemaphore DN_OS_SemaphoreInit(uint32_t initial_count) -{ - DN_OSSemaphore result = {}; - SECURITY_ATTRIBUTES security_attribs = {}; - result.win32_handle = CreateSemaphoreA(&security_attribs, initial_count, INT32_MAX, nullptr /*name*/); - return result; -} - -DN_API bool DN_OS_SemaphoreIsValid(DN_OSSemaphore *semaphore) -{ - bool result = false; - if (semaphore) { - result = semaphore->win32_handle; - } - return result; -} - -DN_API void DN_OS_SemaphoreDeinit(DN_OSSemaphore *semaphore) -{ - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - CloseHandle(semaphore->win32_handle); - *semaphore = {}; -} - -DN_API void DN_OS_SemaphoreIncrement(DN_OSSemaphore *semaphore, uint32_t amount) -{ - if (!DN_OS_SemaphoreIsValid(semaphore)) - return; - LONG prev_count = 0; - ReleaseSemaphore(DN_CAST(HANDLE *)semaphore->win32_handle, amount, &prev_count); -} - -DN_API DN_OSSemaphoreWaitResult DN_OS_SemaphoreWait(DN_OSSemaphore *semaphore, uint32_t timeout_ms) -{ - DN_OSSemaphoreWaitResult result = {}; - if (!DN_OS_SemaphoreIsValid(semaphore)) - return result; - - if (!semaphore->win32_handle) - return result; - - DWORD wait_result = WaitForSingleObject(semaphore->win32_handle, timeout_ms == DN_OS_SEMAPHORE_INFINITE_TIMEOUT ? INFINITE : timeout_ms); - if (wait_result == WAIT_TIMEOUT) - result = DN_OSSemaphoreWaitResult_Timeout; - else if (wait_result == WAIT_OBJECT_0) - result = DN_OSSemaphoreWaitResult_Success; - return result; -} -#endif // !defined(DN_NO_SEMAPHORE) - -#if !defined(DN_NO_THREAD) -// NOTE: [$MUTX] DN_OSMutex /////////////////////////////////////////////////////////////////////// -DN_API DN_OSMutex DN_OS_MutexInit() -{ - DN_OSMutex result = {}; - - CRITICAL_SECTION crit_section = {}; - InitializeCriticalSection(&crit_section); - - static_assert(sizeof(CRITICAL_SECTION) <= sizeof(result.win32_handle), "Insufficient bytes to store Win32 mutex opaquely in our abstracted DN_OSMutex"); - DN_MEMCPY(result.win32_handle, &crit_section, sizeof(crit_section)); - return result; -} - -DN_API void DN_OS_MutexDeinit(DN_OSMutex *mutex) -{ - if (!mutex) - return; - CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *)mutex->win32_handle; - DeleteCriticalSection(crit_section); - DN_MEMSET(mutex->win32_handle, 0, DN_ARRAY_UCOUNT(mutex->win32_handle)); -} - -DN_API void DN_OS_MutexLock(DN_OSMutex *mutex) -{ - if (!mutex) - return; - CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *)mutex->win32_handle; - EnterCriticalSection(crit_section); -} - -DN_API void DN_OS_MutexUnlock(DN_OSMutex *mutex) -{ - if (!mutex) - return; - CRITICAL_SECTION *crit_section = DN_CAST(CRITICAL_SECTION *)mutex->win32_handle; - LeaveCriticalSection(crit_section); -} - -// NOTE: [$THRD] DN_OSThread ///////////////////////////////////////////////////////////////////// - -static DWORD __stdcall DN_OS_ThreadFunc_(void *user_context) -{ - DN_OS_ThreadExecute_(user_context); - return 0; -} - -DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, void *user_context) -{ - bool result = false; - if (!thread) - return result; - - thread->func = func; - thread->user_context = user_context; - thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - - // TODO(doyle): Check if semaphore is valid - DWORD thread_id = 0; - SECURITY_ATTRIBUTES security_attribs = {}; - thread->handle = CreateThread(&security_attribs, - 0 /*stack_size*/, - DN_OS_ThreadFunc_, - thread, - 0 /*creation_flags*/, - &thread_id); - - result = thread->handle != INVALID_HANDLE_VALUE; - if (result) { - thread->thread_id = thread_id; - } - - // NOTE: Ensure that thread_id is set before 'thread->func' is called. - if (result) { - DN_OS_SemaphoreIncrement(&thread->init_semaphore, 1); - } else { - DN_OS_SemaphoreDeinit(&thread->init_semaphore); - *thread = {}; - } - - return result; -} - -DN_API void DN_OS_ThreadDeinit(DN_OSThread *thread) -{ - if (!thread || !thread->handle) - return; - - WaitForSingleObject(thread->handle, INFINITE); - CloseHandle(thread->handle); - thread->handle = INVALID_HANDLE_VALUE; - thread->thread_id = {}; - DN_TLS_Deinit(&thread->tls); -} - -DN_API uint32_t DN_OS_ThreadID() -{ - unsigned long result = GetCurrentThreadId(); - return result; -} - -typedef HRESULT DN_WinSetThreadDescriptionFunc(HANDLE hThread, PWSTR const lpThreadDescription); -static DN_WinSetThreadDescriptionFunc *g_dn_win_set_thread_description = nullptr; - -DN_API void DN_Win_ThreadSetName(DN_Str8 name) -{ - DN_TLS *tls = DN_TLS_Get(); - DN_ArenaTempMem tmem = DN_Arena_TempMemBegin(tls->arenas + DN_TLSArena_TMem0); - - // NOTE: SetThreadDescription is only available in - // Windows Server 2016, Windows 10 LTSB 2016 and Windows 10 version 1607 - // - // See: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription - if (g_dn_win_set_thread_description) { - DN_Str16 name16 = DN_Win_Str8ToStr16(tmem.arena, name); - g_dn_win_set_thread_description(GetCurrentThread(), (WCHAR *)name16.data); - DN_Arena_TempMemEnd(tmem); - return; - } - - // NOTE: Fallback to throw-exception method to set thread name - #pragma pack(push, 8) - struct DN_WinThreadNameInfo { - uint32_t dwType; - char *szName; - uint32_t dwThreadID; - uint32_t dwFlags; - }; - #pragma pack(pop) - - DN_Str8 copy = DN_Str8_Copy(tmem.arena, name); - DN_WinThreadNameInfo info = {}; - info.dwType = 0x1000; - info.szName = (char *)copy.data; - info.dwThreadID = DN_OS_ThreadID(); - __try { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); - } __except (EXCEPTION_EXECUTE_HANDLER) { - } - - DN_Arena_TempMemEnd(tmem); -} -#endif // !defined(DN_NO_THREAD) - -// NOTE: [$HTTP] DN_OSHttp //////////////////////////////////////////////////////////////////////// -void DN_OS_HttpRequestWin32Callback(HINTERNET session, DWORD *dwContext, DWORD dwInternetStatus, VOID *lpvStatusInformation, DWORD dwStatusInformationLength) -{ - (void)session; - (void)dwStatusInformationLength; - - DN_OSHttpResponse *response = DN_CAST(DN_OSHttpResponse *)dwContext; - HINTERNET request = DN_CAST(HINTERNET)response->win32_request_handle; - DN_WinError error = {}; - DWORD const READ_BUFFER_SIZE = DN_MEGABYTES(1); - - if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESOLVING_NAME) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDING_REQUEST) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_SENT) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CREATED) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DETECTING_PROXY) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REDIRECT) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SECURE_FAILURE) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE) { - DWORD status = 0; - DWORD status_size = sizeof(status_size); - if (WinHttpQueryHeaders(request, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &status, - &status_size, - WINHTTP_NO_HEADER_INDEX)) { - response->http_status = DN_CAST(uint16_t)status; - - // NOTE: You can normally call into WinHttpQueryDataAvailable which means the kernel - // will buffer the response into a single buffer and return us the full size of the - // request. - // - // or - // - // You may call WinHttpReadData directly to write the memory into our buffer directly. - // This is advantageous to avoid a copy from the kernel buffer into our buffer. If the - // end user application knows the typical payload size then they can optimise for this - // to prevent unnecessary allocation on the user side. - void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); - if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) - error = DN_Win_LastError(&response->tmp_arena); - } else { - error = DN_Win_LastError(&response->tmp_arena); - } - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE) { - DWORD bytes_read = dwStatusInformationLength; - if (bytes_read) { - DN_Str8 prev_buffer = DN_Str8_Init(DN_CAST(char *) lpvStatusInformation, bytes_read); - DN_Str8Builder_AppendRef(&response->builder, prev_buffer); - - void *buffer = DN_Arena_Alloc(response->builder.arena, READ_BUFFER_SIZE, 1 /*align*/, DN_ZeroMem_No); - if (!WinHttpReadData(request, buffer, READ_BUFFER_SIZE, nullptr)) - error = DN_Win_LastError(&response->tmp_arena); - } - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE) { - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) { - WINHTTP_ASYNC_RESULT *async_result = DN_CAST(WINHTTP_ASYNC_RESULT *)lpvStatusInformation; - error = DN_Win_ErrorCodeToMsg(&response->tmp_arena, DN_CAST(uint32_t)async_result->dwError); - } else if (dwInternetStatus == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) { - if (!WinHttpReceiveResponse(request, 0)) - error = DN_Win_LastError(&response->tmp_arena); - } - - // NOTE: If the request handle is missing, then, the response has been freed. - // MSDN says that this callback can still be called after closing the handle - // and trigger the WINHTTP_CALLBACK_STATUS_REQUEST_ERROR. - if (request) { - bool read_complete = dwInternetStatus == WINHTTP_CALLBACK_STATUS_READ_COMPLETE && dwStatusInformationLength == 0; - if (read_complete) { - response->body = DN_Str8Builder_Build(&response->builder, response->arena); - } - - if (read_complete || dwInternetStatus == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR || error.code) { - DN_OS_SemaphoreIncrement(&response->on_complete_semaphore, 1); - DN_Atomic_AddU32(&response->done, 1); - } - - if (error.code) { - response->error_code = error.code; - response->error_msg = error.msg; - } - } -} - -DN_API void DN_OS_HttpRequestAsync(DN_OSHttpResponse *response, - DN_Arena *arena, - DN_Str8 host, - DN_Str8 path, - DN_OSHttpRequestSecure secure, - DN_Str8 method, - DN_Str8 body, - DN_Str8 headers) -{ - if (!response || !arena) - return; - - response->arena = arena; - response->builder.arena = response->tmem_arena ? response->tmem_arena : &response->tmp_arena; - - DN_Arena *tmem_arena = response->tmem_arena; - DN_TLSTMem tmem_ = DN_TLS_TMem(arena); - if (!tmem_arena) { - tmem_arena = tmem_.arena; - } - - DN_WinError error = {}; - DN_DEFER { - response->error_msg = error.msg; - response->error_code = error.code; - if (error.code) { - // NOTE: 'Wait' handles failures gracefully, skipping the wait and - // cleans up the request - DN_OS_HttpRequestWait(response); - DN_Atomic_AddU32(&response->done, 1); - } - }; - - response->win32_request_session = WinHttpOpen(nullptr /*user agent*/, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); - if (!response->win32_request_session) { - error = DN_Win_LastError(&response->tmp_arena); - return; - } - - DWORD callback_flags = WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE | - WINHTTP_CALLBACK_STATUS_READ_COMPLETE | - WINHTTP_CALLBACK_STATUS_REQUEST_ERROR | - WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE; - if (WinHttpSetStatusCallback(response->win32_request_session, - DN_CAST(WINHTTP_STATUS_CALLBACK)DN_OS_HttpRequestWin32Callback, - callback_flags, - DN_CAST(DWORD_PTR)nullptr /*dwReserved*/) == WINHTTP_INVALID_STATUS_CALLBACK) { - error = DN_Win_LastError(&response->tmp_arena); - return; - } - - DN_Str16 host16 = DN_Win_Str8ToStr16(tmem_arena, host); - response->win32_request_connection = WinHttpConnect(response->win32_request_session, host16.data, secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, 0 /*reserved*/); - if (!response->win32_request_connection) { - error = DN_Win_LastError(&response->tmp_arena); - return; - } - - DN_Str16 method16 = DN_Win_Str8ToStr16(tmem_arena, method); - DN_Str16 path16 = DN_Win_Str8ToStr16(tmem_arena, path); - response->win32_request_handle = WinHttpOpenRequest(response->win32_request_connection, - method16.data, - path16.data, - nullptr /*version*/, - nullptr /*referrer*/, - nullptr /*accept types*/, - secure ? WINHTTP_FLAG_SECURE : 0); - if (!response->win32_request_handle) { - error = DN_Win_LastError(&response->tmp_arena); - return; - } - - DN_Str16 headers16 = DN_Win_Str8ToStr16(tmem_arena, headers); - response->on_complete_semaphore = DN_OS_SemaphoreInit(0); - if (!WinHttpSendRequest(response->win32_request_handle, - headers16.data, - DN_CAST(DWORD)headers16.size, - body.data /*optional data*/, - DN_CAST(DWORD) body.size /*optional length*/, - DN_CAST(DWORD) body.size /*total content length*/, - DN_CAST(DWORD_PTR)response)) { - error = DN_Win_LastError(&response->tmp_arena); - return; - } -} - -DN_API void DN_OS_HttpRequestFree(DN_OSHttpResponse *response) -{ - // NOTE: Cleanup - // NOTE: These calls are synchronous even when the HTTP request is async. - WinHttpCloseHandle(response->win32_request_handle); - WinHttpCloseHandle(response->win32_request_connection); - WinHttpCloseHandle(response->win32_request_session); - - response->win32_request_session = nullptr; - response->win32_request_connection = nullptr; - response->win32_request_handle = nullptr; - DN_Arena_Deinit(&response->tmp_arena); - if (DN_OS_SemaphoreIsValid(&response->on_complete_semaphore)) - DN_OS_SemaphoreDeinit(&response->on_complete_semaphore); - - *response = {}; -} - -// NOTE: [$WIND] DN_Win /////////////////////////////////////////////////////////////////////////// -DN_API DN_Str16 DN_Win_ErrorCodeToMsg16Alloc(uint32_t error_code) -{ - DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; - void *module_to_get_errors_from = nullptr; - if (error_code >= 12000 && error_code <= 12175) { - flags |= FORMAT_MESSAGE_FROM_HMODULE; - module_to_get_errors_from = GetModuleHandleA("winhttp.dll"); - } - - wchar_t *result16 = nullptr; - DWORD size = FormatMessageW(/*DWORD dwFlags */ flags | FORMAT_MESSAGE_ALLOCATE_BUFFER, - /*LPCVOID lpSource */ module_to_get_errors_from, - /*DWORD dwMessageId */ error_code, - /*DWORD dwLanguageId*/ 0, - /*LPWSTR lpBuffer */ (LPWSTR)&result16, - /*DWORD nSize */ 0, - /*va_list *Arguments */ nullptr); - - DN_Str16 result = {result16, size}; - return result; -} - -DN_API DN_WinError DN_Win_ErrorCodeToMsgAlloc(uint32_t error_code) -{ - DN_WinError result = {}; - result.code = error_code; - DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc(error_code); - if (error16.size) - result.msg = DN_Win_Str16ToStr8Alloc(error16); - if (error16.data) - LocalFree(error16.data); - return result; -} - -DN_API DN_WinError DN_Win_ErrorCodeToMsg(DN_Arena *arena, uint32_t error_code) -{ - DN_WinError result = {}; - result.code = error_code; - if (arena) { - DN_Str16 error16 = DN_Win_ErrorCodeToMsg16Alloc(error_code); - if (error16.size) - result.msg = DN_Win_Str16ToStr8(arena, error16); - if (error16.data) - LocalFree(error16.data); - } - return result; -} - -DN_API DN_WinError DN_Win_LastError(DN_Arena *arena) -{ - DN_WinError result = DN_Win_ErrorCodeToMsg(arena, GetLastError()); - return result; -} - -DN_API DN_WinError DN_Win_LastErrorAlloc() -{ - DN_WinError result = DN_Win_ErrorCodeToMsgAlloc(GetLastError()); - return result; -} - -DN_API void DN_Win_MakeProcessDPIAware() -{ - typedef bool SetProcessDpiAwareProc(void); - typedef bool SetProcessDpiAwarenessProc(DPI_AWARENESS); - typedef bool SetProcessDpiAwarenessContextProc(void * /*DPI_AWARENESS_CONTEXT*/); - - // NOTE(doyle): Taken from cmuratori/refterm snippet on DPI awareness. It - // appears we can make this robust by just loading user32.dll and using - // GetProcAddress on the DPI function. If it's not there, we're on an old - // version of windows, so we can call an older version of the API. - void *lib_handle = LoadLibraryA("user32.dll"); - if (!lib_handle) - return; - - if (auto *set_process_dpi_awareness_context = DN_CAST(SetProcessDpiAwarenessContextProc *)GetProcAddress(DN_CAST(HMODULE)lib_handle, "SetProcessDpiAwarenessContext")) { - set_process_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } else if (auto *set_process_dpi_awareness = DN_CAST(SetProcessDpiAwarenessProc *)GetProcAddress(DN_CAST(HMODULE)lib_handle, "SetProcessDpiAwareness")) { - set_process_dpi_awareness(DPI_AWARENESS_PER_MONITOR_AWARE); - } else if (auto *set_process_dpi_aware = DN_CAST(SetProcessDpiAwareProc *)GetProcAddress(DN_CAST(HMODULE)lib_handle, "SetProcessDpiAware")) { - set_process_dpi_aware(); - } -} - -// NOTE: Windows UTF8 to Str16 ////////////////////////////////////////////// -DN_API DN_Str16 DN_Win_Str8ToStr16(DN_Arena *arena, DN_Str8 src) -{ - DN_Str16 result = {}; - if (!arena || !DN_Str8_HasData(src)) - return result; - - int required_size = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); - if (required_size <= 0) - return result; - - wchar_t *buffer = DN_Arena_NewArray(arena, wchar_t, required_size + 1, DN_ZeroMem_No); - if (!buffer) - return result; - - int chars_written = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int)src.size, buffer, required_size); - if (DN_CHECK(chars_written == required_size)) { - result.data = buffer; - result.size = chars_written; - result.data[result.size] = 0; - } - return result; -} - -DN_API int DN_Win_Str8ToStr16Buffer(DN_Str8 src, wchar_t *dest, int dest_size) -{ - int result = 0; - if (!DN_Str8_HasData(src)) - return result; - - result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int)src.size, nullptr /*dest*/, 0 /*dest size*/); - if (result <= 0 || result > dest_size || !dest) - return result; - - result = MultiByteToWideChar(CP_UTF8, 0 /*dwFlags*/, src.data, DN_CAST(int)src.size, dest, DN_CAST(int)dest_size); - dest[DN_MIN(result, dest_size - 1)] = 0; - return result; -} - -// NOTE: Windows Str16 To UTF8 ////////////////////////////////////////////////////////////////// -DN_API int DN_Win_Str16ToStr8Buffer(DN_Str16 src, char *dest, int dest_size) -{ - int result = 0; - if (!DN_Str16_HasData(src)) - return result; - - int src_size = DN_Safe_SaturateCastISizeToInt(src.size); - if (src_size <= 0) - return result; - - result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); - if (result <= 0 || result > dest_size || !dest) - return result; - - result = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, dest, DN_CAST(int)dest_size, nullptr, nullptr); - dest[DN_MIN(result, dest_size - 1)] = 0; - return result; -} - -DN_API DN_Str8 DN_Win_Str16ToStr8Alloc(DN_Str16 src) -{ - DN_Str8 result = {}; - if (!DN_Str16_HasData(src)) - return result; - - int src_size = DN_Safe_SaturateCastISizeToInt(src.size); - if (src_size <= 0) - return result; - - int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); - if (required_size <= 0) - return result; - - DN_Str8 buffer = {}; - buffer.data = DN_CAST(char *)DN_OS_MemAlloc(required_size + 1, DN_ZeroMem_No); - if (buffer.data) { - buffer.size = required_size; - int chars_written = WideCharToMultiByte(CP_UTF8, - 0 /*dwFlags*/, - src.data, - src_size, - buffer.data, - DN_CAST(int) buffer.size, - nullptr, - nullptr); - if (DN_CHECK(chars_written == required_size)) { - result = buffer; - result.data[result.size] = 0; - } - } - return result; -} - -DN_API DN_Str8 DN_Win_Str16ToStr8(DN_Arena *arena, DN_Str16 src) -{ - DN_Str8 result = {}; - if (!arena || !DN_Str16_HasData(src)) - return result; - - int src_size = DN_Safe_SaturateCastISizeToInt(src.size); - if (src_size <= 0) - return result; - - int required_size = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, nullptr /*dest*/, 0 /*dest size*/, nullptr, nullptr); - if (required_size <= 0) - return result; - - // NOTE: Str8 allocate ensures there's one extra byte for - // null-termination already so no-need to +1 the required size - DN_ArenaTempMemScope temp_mem = DN_ArenaTempMemScope(arena); - DN_Str8 buffer = DN_Str8_Alloc(arena, required_size, DN_ZeroMem_No); - if (!DN_Str8_HasData(buffer)) - return result; - - int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_CAST(int)buffer.size, nullptr, nullptr); - if (DN_CHECK(chars_written == required_size)) { - result = buffer; - result.data[result.size] = 0; - temp_mem.mem = {}; - } - - return result; -} - -// NOTE: Windows Executable Directory ////////////////////////////////////////// -DN_API DN_Str16 DN_Win_EXEPathW(DN_Arena *arena) -{ - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str16 result = {}; - DN_USize module_size = 0; - wchar_t *module_path = nullptr; - do { - module_size += 256; - module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); - if (!module_path) - return result; - module_size = DN_CAST(DN_USize)GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int)module_size); - } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); - - DN_USize index_of_last_slash = 0; - for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) - index_of_last_slash = module_path[index] == '\\' ? index : 0; - - result.data = DN_Arena_NewArray(arena, wchar_t, module_size + 1, DN_ZeroMem_No); - result.size = module_size; - DN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); - result.data[result.size] = 0; - return result; -} - -DN_API DN_Str16 DN_Win_EXEDirW(DN_Arena *arena) -{ - // TODO(doyle): Implement a DN_Str16_BinarySearchReverse - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str16 result = {}; - DN_USize module_size = 0; - wchar_t *module_path = nullptr; - do { - module_size += 256; - module_path = DN_Arena_NewArray(tmem.arena, wchar_t, module_size, DN_ZeroMem_No); - if (!module_path) - return result; - module_size = DN_CAST(DN_USize)GetModuleFileNameW(nullptr /*module*/, module_path, DN_CAST(int)module_size); - } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER); - - DN_USize index_of_last_slash = 0; - for (DN_USize index = module_size - 1; !index_of_last_slash && index < module_size; index--) - index_of_last_slash = module_path[index] == '\\' ? index : 0; - - result.data = DN_Arena_NewArray(arena, wchar_t, index_of_last_slash + 1, DN_ZeroMem_No); - result.size = index_of_last_slash; - DN_MEMCPY(result.data, module_path, sizeof(wchar_t) * result.size); - result.data[result.size] = 0; - return result; -} - -DN_API DN_Str8 DN_Win_WorkingDir(DN_Arena *arena, DN_Str8 suffix) -{ - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str16 suffix16 = DN_Win_Str8ToStr16(tmem.arena, suffix); - DN_Str16 dir16 = DN_Win_WorkingDirW(tmem.arena, suffix16); - DN_Str8 result = DN_Win_Str16ToStr8(arena, dir16); - return result; -} - -DN_API DN_Str16 DN_Win_WorkingDirW(DN_Arena *arena, DN_Str16 suffix) -{ - DN_ASSERT(suffix.size >= 0); - DN_Str16 result = {}; - - // NOTE: required_size is the size required *including* the null-terminator - DN_TLSTMem tmem = DN_TLS_TMem(arena); - unsigned long required_size = GetCurrentDirectoryW(0, nullptr); - unsigned long desired_size = required_size + DN_CAST(unsigned long) suffix.size; - - wchar_t *tmem_w_path = DN_Arena_NewArray(tmem.arena, wchar_t, desired_size, DN_ZeroMem_No); - if (!tmem_w_path) - return result; - - unsigned long bytes_written_wo_null_terminator = GetCurrentDirectoryW(desired_size, tmem_w_path); - if ((bytes_written_wo_null_terminator + 1) != required_size) { - // TODO(dn): Error - return result; - } - - wchar_t *w_path = DN_Arena_NewArray(arena, wchar_t, desired_size, DN_ZeroMem_No); - if (!w_path) - return result; - - if (suffix.size) { - DN_MEMCPY(w_path, tmem_w_path, sizeof(*tmem_w_path) * bytes_written_wo_null_terminator); - DN_MEMCPY(w_path + bytes_written_wo_null_terminator, suffix.data, sizeof(suffix.data[0]) * suffix.size); - w_path[desired_size] = 0; - } - - result = DN_Str16{w_path, DN_CAST(DN_USize)(desired_size - 1)}; - return result; -} - -DN_API bool DN_Win_DirWIterate(DN_Str16 path, DN_Win_FolderIteratorW *it) -{ - WIN32_FIND_DATAW find_data = {}; - if (it->handle) { - if (FindNextFileW(it->handle, &find_data) == 0) { - FindClose(it->handle); - return false; - } - } else { - it->handle = FindFirstFileExW(path.data, /*LPCWSTR lpFileName,*/ - FindExInfoStandard, /*FINDEX_INFO_LEVELS fInfoLevelId,*/ - &find_data, /*LPVOID lpFindFileData,*/ - FindExSearchNameMatch, /*FINDEX_SEARCH_OPS fSearchOp,*/ - nullptr, /*LPVOID lpSearchFilter,*/ - FIND_FIRST_EX_LARGE_FETCH /*unsigned long dwAdditionalFlags)*/); - - if (it->handle == INVALID_HANDLE_VALUE) - return false; - } - - it->file_name_buf[0] = 0; - it->file_name = DN_Str16{it->file_name_buf, 0}; - - do { - if (find_data.cFileName[0] == '.' || (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '.')) - continue; - - it->file_name.size = DN_CStr16_Size(find_data.cFileName); - DN_ASSERT(it->file_name.size < (DN_ARRAY_UCOUNT(it->file_name_buf) - 1)); - DN_MEMCPY(it->file_name.data, find_data.cFileName, it->file_name.size * sizeof(wchar_t)); - it->file_name_buf[it->file_name.size] = 0; - break; - } while (FindNextFileW(it->handle, &find_data) != 0); - - bool result = it->file_name.size > 0; - if (!result) - FindClose(it->handle); - return result; -} diff --git a/dqn_string.cpp b/dqn_string.cpp deleted file mode 100644 index 794f28c..0000000 --- a/dqn_string.cpp +++ /dev/null @@ -1,1291 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$\ $$$$$$$$\ $$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\ -// $$ __$$\\__$$ __|$$ __$$\ \_$$ _|$$$\ $$ |$$ __$$\ -// $$ / \__| $$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__| -// \$$$$$$\ $$ | $$$$$$$ | $$ | $$ $$\$$ |$$ |$$$$\ -// \____$$\ $$ | $$ __$$< $$ | $$ \$$$$ |$$ |\_$$ | -// $$\ $$ | $$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ | -// \$$$$$$ | $$ | $$ | $$ |$$$$$$\ $$ | \$$ |\$$$$$$ | -// \______/ \__| \__| \__|\______|\__| \__| \______/ -// -// dqn_string.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$CSTR] DN_CStr8 ///////////////////////////////////////////////////////////////////////// -DN_API DN_USize DN_CStr8_FSize(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_USize result = DN_VSNPRINTF(nullptr, 0, fmt, args); - va_end(args); - return result; -} - -DN_API DN_USize DN_CStr8_FVSize(DN_FMT_ATTRIB char const *fmt, va_list args) -{ - va_list args_copy; - va_copy(args_copy, args); - DN_USize result = DN_VSNPRINTF(nullptr, 0, fmt, args_copy); - va_end(args_copy); - return result; -} - -DN_API DN_USize DN_CStr8_Size(char const *src) -{ - DN_USize result = 0; - while (src && src[0] != 0) { - src++; - result++; - } - return result; -} - -DN_API DN_USize DN_CStr16_Size(wchar_t const *src) -{ - DN_USize result = 0; - while (src && src[0] != 0) { - src++; - result++; - } - - return result; -} - -// NOTE: [$STR6] DN_Str16 ///////////////////////////////////////////////////////////////////////// -DN_API bool operator==(DN_Str16 const &lhs, DN_Str16 const &rhs) -{ - bool result = false; - if (lhs.size == rhs.size) - result = DN_MEMCMP(lhs.data, rhs.data, lhs.size * sizeof(*lhs.data)) == 0; - return result; -} - -DN_API bool operator!=(DN_Str16 const &lhs, DN_Str16 const &rhs) -{ - bool result = !(lhs == rhs); - return result; -} - -// NOTE: [$STR8] DN_Str8 ////////////////////////////////////////////////////////////////////////// -DN_API DN_Str8 DN_Str8_InitCStr8(char const *src) -{ - DN_USize size = DN_CStr8_Size(src); - DN_Str8 result = DN_Str8_Init(src, size); - return result; -} - -DN_API bool DN_Str8_IsAll(DN_Str8 string, DN_Str8IsAll is_all) -{ - bool result = DN_Str8_HasData(string); - if (!result) - return result; - - switch (is_all) { - case DN_Str8IsAll_Digits: { - for (DN_USize index = 0; result && index < string.size; index++) - result = string.data[index] >= '0' && string.data[index] <= '9'; - } break; - - case DN_Str8IsAll_Hex: { - DN_Str8 trimmed = DN_Str8_TrimPrefix(string, DN_STR8("0x"), DN_Str8EqCase_Insensitive); - for (DN_USize index = 0; result && index < trimmed.size; index++) { - char ch = trimmed.data[index]; - result = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); - } - } break; - } - - return result; -} - -DN_API char *DN_Str8_End(DN_Str8 string) -{ - char *result = string.data + string.size; - return result; -} - -DN_API DN_Str8 DN_Str8_Slice(DN_Str8 string, DN_USize offset, DN_USize size) -{ - DN_Str8 result = DN_Str8_Init(string.data, 0); - if (!DN_Str8_HasData(string)) - return result; - - DN_USize capped_offset = DN_MIN(offset, string.size); - DN_USize max_size = string.size - capped_offset; - DN_USize capped_size = DN_MIN(size, max_size); - result = DN_Str8_Init(string.data + capped_offset, capped_size); - return result; -} - -DN_API DN_Str8 DN_Str8_Advance(DN_Str8 string, DN_USize amount) -{ - DN_Str8 result = DN_Str8_Slice(string, amount, DN_USIZE_MAX); - return result; -} - -DN_API DN_Str8 DN_Str8_NextLine(DN_Str8 string) -{ - DN_Str8 result = DN_Str8_BinarySplit(string, DN_STR8("\n")).rhs; - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) -{ - DN_Str8BinarySplitResult result = {}; - if (!DN_Str8_HasData(string) || !find || find_size == 0) - return result; - - result.lhs = string; - for (size_t index = 0; !result.rhs.data && index < string.size; index++) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); - if (DN_Str8_Eq(string_slice, find_item)) { - result.lhs.size = index; - result.rhs.data = string_slice.data + find_item.size; - result.rhs.size = string.size - (index + find_item.size); - break; - } - } - } - - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplit(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BinarySplitResult result = DN_Str8_BinarySplitArray(string, &find, 1); - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLastArray(DN_Str8 string, DN_Str8 const *find, DN_USize find_size) -{ - DN_Str8BinarySplitResult result = {}; - if (!DN_Str8_HasData(string) || !find || find_size == 0) - return result; - - result.lhs = string; - for (size_t index = string.size - 1; !result.rhs.data && index < string.size; index--) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); - if (DN_Str8_Eq(string_slice, find_item)) { - result.lhs.size = index; - result.rhs.data = string_slice.data + find_item.size; - result.rhs.size = string.size - (index + find_item.size); - break; - } - } - } - - return result; -} - -DN_API DN_Str8BinarySplitResult DN_Str8_BinarySplitLast(DN_Str8 string, DN_Str8 find) -{ - DN_Str8BinarySplitResult result = DN_Str8_BinarySplitLastArray(string, &find, 1); - return result; -} - -DN_API DN_USize DN_Str8_Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, DN_USize splits_count, DN_Str8SplitIncludeEmptyStrings mode) -{ - DN_USize result = 0; // The number of splits in the actual string. - if (!DN_Str8_HasData(string) || !DN_Str8_HasData(delimiter) || delimiter.size <= 0) - return result; - - DN_Str8BinarySplitResult split = {}; - DN_Str8 first = string; - do { - split = DN_Str8_BinarySplit(first, delimiter); - if (split.lhs.size || mode == DN_Str8SplitIncludeEmptyStrings_Yes) { - if (splits && result < splits_count) - splits[result] = split.lhs; - result++; - } - first = split.rhs; - } while (first.size); - - return result; -} - -DN_API DN_Slice DN_Str8_SplitAlloc(DN_Arena *arena, DN_Str8 string, DN_Str8 delimiter, DN_Str8SplitIncludeEmptyStrings mode) -{ - DN_Slice result = {}; - DN_USize splits_required = DN_Str8_Split(string, delimiter, /*splits*/ nullptr, /*count*/ 0, mode); - result.data = DN_Arena_NewArray(arena, DN_Str8, splits_required, DN_ZeroMem_No); - if (result.data) { - result.size = DN_Str8_Split(string, delimiter, result.data, splits_required, mode); - DN_ASSERT(splits_required == result.size); - } - return result; -} - -DN_API DN_Str8FindResult DN_Str8_FindStr8Array(DN_Str8 string, DN_Str8 const *find, DN_USize find_size, DN_Str8EqCase eq_case) -{ - DN_Str8FindResult result = {}; - if (!DN_Str8_HasData(string) || !find || find_size == 0) - return result; - - for (DN_USize index = 0; !result.found && index < string.size; index++) { - for (DN_USize find_index = 0; find_index < find_size; find_index++) { - DN_Str8 find_item = find[find_index]; - DN_Str8 string_slice = DN_Str8_Slice(string, index, find_item.size); - if (DN_Str8_Eq(string_slice, find_item, eq_case)) { - result.found = true; - result.index = index; - result.start_to_before_match = DN_Str8_Init(string.data, index); - result.match = DN_Str8_Init(string.data + index, find_item.size); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, find_item.size); - break; - } - } - } - return result; -} - -DN_API DN_Str8FindResult DN_Str8_FindStr8(DN_Str8 string, DN_Str8 find, DN_Str8EqCase eq_case) -{ - DN_Str8FindResult result = DN_Str8_FindStr8Array(string, &find, 1, eq_case); - return result; -} - -DN_API DN_Str8FindResult DN_Str8_Find(DN_Str8 string, uint32_t flags) -{ - DN_Str8FindResult result = {}; - for (size_t index = 0; !result.found && index < string.size; index++) { - result.found |= ((flags & DN_Str8FindFlag_Digit) && DN_Char_IsDigit(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Alphabet) && DN_Char_IsAlphabet(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Whitespace) && DN_Char_IsWhitespace(string.data[index])); - result.found |= ((flags & DN_Str8FindFlag_Plus) && string.data[index] == '+'); - result.found |= ((flags & DN_Str8FindFlag_Minus) && string.data[index] == '-'); - if (result.found) { - result.index = index; - result.match = DN_Str8_Init(string.data + index, 1); - result.match_to_end_of_buffer = DN_Str8_Init(result.match.data, string.size - index); - result.after_match_to_end_of_buffer = DN_Str8_Advance(result.match_to_end_of_buffer, 1); - } - } - return result; -} - -DN_API DN_Str8 DN_Str8_Segment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) -{ - if (!segment_size || !DN_Str8_HasData(src)) { - DN_Str8 result = DN_Str8_Copy(arena, src); - return result; - } - - DN_USize segments = src.size / segment_size; - if (src.size % segment_size == 0) - segments--; - - DN_USize segment_counter = 0; - DN_Str8 result = DN_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); - DN_USize write_index = 0; - DN_FOR_UINDEX(src_index, src.size) { - result.data[write_index++] = src.data[src_index]; - if ((src_index + 1) % segment_size == 0 && segment_counter < segments) { - result.data[write_index++] = segment_char; - segment_counter++; - } - DN_ASSERTF(write_index <= result.size, "result.size=%zu, write_index=%zu", result.size, write_index); - } - - DN_ASSERTF(write_index == result.size, "result.size=%zu, write_index=%zu", result.size, write_index); - return result; -} - -DN_API DN_Str8 DN_Str8_ReverseSegment(DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char) -{ - if (!segment_size || !DN_Str8_HasData(src)) { - DN_Str8 result = DN_Str8_Copy(arena, src); - return result; - } - - DN_USize segments = src.size / segment_size; - if (src.size % segment_size == 0) - segments--; - - DN_USize write_counter = 0; - DN_USize segment_counter = 0; - DN_Str8 result = DN_Str8_Alloc(arena, src.size + segments, DN_ZeroMem_Yes); - DN_USize write_index = result.size - 1; - - - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop - for (size_t src_index = src.size - 1; src_index < src.size; src_index--) { - DN_MSVC_WARNING_POP - result.data[write_index--] = src.data[src_index]; - if (++write_counter % segment_size == 0 && segment_counter < segments) { - result.data[write_index--] = segment_char; - segment_counter++; - } - } - - DN_ASSERT(write_index == SIZE_MAX); - return result; -} - - -DN_API bool DN_Str8_Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) -{ - if (lhs.size != rhs.size) - return false; - - if (lhs.size == 0) - return true; - - if (!lhs.data || !rhs.data) - return false; - - bool result = true; - switch (eq_case) { - case DN_Str8EqCase_Sensitive: { - result = (DN_MEMCMP(lhs.data, rhs.data, lhs.size) == 0); - } break; - - case DN_Str8EqCase_Insensitive: { - for (DN_USize index = 0; index < lhs.size && result; index++) - result = (DN_Char_ToLower(lhs.data[index]) == DN_Char_ToLower(rhs.data[index])); - } break; - } - return result; -} - -DN_API bool DN_Str8_EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) -{ - bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8_StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) -{ - DN_Str8 substring = {string.data, DN_MIN(prefix.size, string.size)}; - bool result = DN_Str8_Eq(substring, prefix, eq_case); - return result; -} - -DN_API bool DN_Str8_StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) -{ - bool result = DN_Str8_StartsWith(string, prefix, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8_EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) -{ - DN_Str8 substring = {string.data + string.size - suffix.size, DN_MIN(string.size, suffix.size)}; - bool result = DN_Str8_Eq(substring, suffix, eq_case); - return result; -} - -DN_API bool DN_Str8_EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) -{ - bool result = DN_Str8_EndsWith(string, suffix, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API bool DN_Str8_HasChar(DN_Str8 string, char ch) -{ - bool result = false; - for (DN_USize index = 0; !result && index < string.size; index++) - result = string.data[index] == ch; - return result; -} - -DN_API DN_Str8 DN_Str8_TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case) -{ - DN_Str8 result = string; - if (DN_Str8_StartsWith(string, prefix, eq_case)) { - result.data += prefix.size; - result.size -= prefix.size; - } - return result; -} - -DN_API DN_Str8 DN_Str8_TrimHexPrefix(DN_Str8 string) -{ - DN_Str8 result = DN_Str8_TrimPrefix(string, DN_STR8("0x"), DN_Str8EqCase_Insensitive); - return result; -} - -DN_API DN_Str8 DN_Str8_TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case) -{ - DN_Str8 result = string; - if (DN_Str8_EndsWith(string, suffix, eq_case)) - result.size -= suffix.size; - return result; -} - -DN_API DN_Str8 DN_Str8_TrimAround(DN_Str8 string, DN_Str8 trim_string) -{ - DN_Str8 result = DN_Str8_TrimPrefix(string, trim_string); - result = DN_Str8_TrimSuffix(result, trim_string); - return result; -} - -DN_API DN_Str8 DN_Str8_TrimWhitespaceAround(DN_Str8 string) -{ - DN_Str8 result = string; - if (!DN_Str8_HasData(string)) - return result; - - char const *start = string.data; - char const *end = string.data + string.size; - while (start < end && DN_Char_IsWhitespace(start[0])) - start++; - - while (end > start && DN_Char_IsWhitespace(end[-1])) - end--; - - result = DN_Str8_Init(start, end - start); - return result; -} - -DN_API DN_Str8 DN_Str8_TrimByteOrderMark(DN_Str8 string) -{ - DN_Str8 result = string; - if (!DN_Str8_HasData(result)) - return result; - - // TODO(dn): This is little endian - DN_Str8 UTF8_BOM = DN_STR8("\xEF\xBB\xBF"); - DN_Str8 UTF16_BOM_BE = DN_STR8("\xEF\xFF"); - DN_Str8 UTF16_BOM_LE = DN_STR8("\xFF\xEF"); - DN_Str8 UTF32_BOM_BE = DN_STR8("\x00\x00\xFE\xFF"); - DN_Str8 UTF32_BOM_LE = DN_STR8("\xFF\xFE\x00\x00"); - - result = DN_Str8_TrimPrefix(result, UTF8_BOM, DN_Str8EqCase_Sensitive); - result = DN_Str8_TrimPrefix(result, UTF16_BOM_BE, DN_Str8EqCase_Sensitive); - result = DN_Str8_TrimPrefix(result, UTF16_BOM_LE, DN_Str8EqCase_Sensitive); - result = DN_Str8_TrimPrefix(result, UTF32_BOM_BE, DN_Str8EqCase_Sensitive); - result = DN_Str8_TrimPrefix(result, UTF32_BOM_LE, DN_Str8EqCase_Sensitive); - return result; -} - -DN_API DN_Str8 DN_Str8_FileNameFromPath(DN_Str8 path) -{ - DN_Str8 separators[] = {DN_STR8("/"), DN_STR8("\\")}; - DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLastArray(path, separators, DN_ARRAY_UCOUNT(separators)); - DN_Str8 result = DN_Str8_HasData(split.rhs) ? split.rhs : split.lhs; - return result; -} - -DN_API DN_Str8 DN_Str8_FileNameNoExtension(DN_Str8 path) -{ - DN_Str8 file_name = DN_Str8_FileNameFromPath(path); - DN_Str8 result = DN_Str8_FilePathNoExtension(file_name); - return result; -} - -DN_API DN_Str8 DN_Str8_FilePathNoExtension(DN_Str8 path) -{ - DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); - DN_Str8 result = split.lhs; - return result; -} - -DN_API DN_Str8 DN_Str8_FileExtension(DN_Str8 path) -{ - DN_Str8BinarySplitResult split = DN_Str8_BinarySplitLast(path, DN_STR8(".")); - DN_Str8 result = split.rhs; - return result; -} - -DN_API DN_Str8ToU64Result DN_Str8_ToU64(DN_Str8 string, char separator) -{ - // NOTE: Argument check - DN_Str8ToU64Result result = {}; - if (!DN_Str8_HasData(string)) { - result.success = true; - return result; - } - - // NOTE: Sanitize input/output - DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); - if (trim_string.size == 0) { - result.success = true; - return result; - } - - // NOTE: Handle prefix '+' - DN_USize start_index = 0; - if (!DN_Char_IsDigit(trim_string.data[0])) { - if (trim_string.data[0] != '+') - return result; - start_index++; - } - - // NOTE: Convert the string number to the binary number - for (DN_USize index = start_index; index < trim_string.size; index++) { - char ch = trim_string.data[index]; - if (index) { - if (separator != 0 && ch == separator) - continue; - } - - if (!DN_Char_IsDigit(ch)) - return result; - - result.value = DN_Safe_MulU64(result.value, 10); - uint64_t digit = ch - '0'; - result.value = DN_Safe_AddU64(result.value, digit); - } - - result.success = true; - return result; -} - -DN_API DN_Str8ToI64Result DN_Str8_ToI64(DN_Str8 string, char separator) -{ - // NOTE: Argument check - DN_Str8ToI64Result result = {}; - if (!DN_Str8_HasData(string)) { - result.success = true; - return result; - } - - // NOTE: Sanitize input/output - DN_Str8 trim_string = DN_Str8_TrimWhitespaceAround(string); - if (trim_string.size == 0) { - result.success = true; - return result; - } - - bool negative = false; - DN_USize start_index = 0; - if (!DN_Char_IsDigit(trim_string.data[0])) { - negative = (trim_string.data[start_index] == '-'); - if (!negative && trim_string.data[0] != '+') - return result; - start_index++; - } - - // NOTE: Convert the string number to the binary number - for (DN_USize index = start_index; index < trim_string.size; index++) { - char ch = trim_string.data[index]; - if (index) { - if (separator != 0 && ch == separator) - continue; - } - - if (!DN_Char_IsDigit(ch)) - return result; - - result.value = DN_Safe_MulU64(result.value, 10); - uint64_t digit = ch - '0'; - result.value = DN_Safe_AddU64(result.value, digit); - } - - if (negative) - result.value *= -1; - - result.success = true; - return result; -} - -DN_API DN_Str8 DN_Str8_AppendF(DN_Arena *arena, DN_Str8 string, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 append = DN_Str8_InitFV(arena, fmt, args); - va_end(args); - - DN_Str8 result = DN_Str8_Alloc(arena, string.size + append.size, DN_ZeroMem_No); - DN_MEMCPY(result.data, string.data, string.size); - DN_MEMCPY(result.data + string.size, append.data, append.size); - return result; -} - -DN_API DN_Str8 DN_Str8_FillF(DN_Arena *arena, DN_USize count, char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_Str8 fill = DN_Str8_InitFV(arena, fmt, args); - va_end(args); - DN_Str8 result = DN_Str8_Alloc(arena, count * fill.size, DN_ZeroMem_No); - for (DN_USize index = 0; index < count; index++) { - void *dest = result.data + (index * fill.size); - DN_MEMCPY(dest, fill.data, fill.size); - } - return result; -} - -DN_API DN_Str8 DN_Str8_Replace(DN_Str8 string, - DN_Str8 find, - DN_Str8 replace, - DN_USize start_index, - DN_Arena *arena, - DN_Str8EqCase eq_case) -{ - DN_Str8 result = {}; - if (!DN_Str8_HasData(string) || !DN_Str8_HasData(find) || find.size > string.size || find.size == 0 || string.size == 0) { - result = DN_Str8_Copy(arena, string); - return result; - } - - DN_TLSTMem tmem = DN_TLS_TMem(arena); - DN_Str8Builder string_builder = DN_Str8Builder_Init(tmem.arena); - DN_USize max = string.size - find.size; - DN_USize head = start_index; - - for (DN_USize tail = head; tail <= max; tail++) { - DN_Str8 check = DN_Str8_Slice(string, tail, find.size); - if (!DN_Str8_Eq(check, find, eq_case)) - continue; - - if (start_index > 0 && string_builder.string_size == 0) { - // User provided a hint in the string to start searching from, we - // need to add the string up to the hint. We only do this if there's - // a replacement action, otherwise we have a special case for no - // replacements, where the entire string gets copied. - DN_Str8 slice = DN_Str8_Init(string.data, head); - DN_Str8Builder_AppendRef(&string_builder, slice); - } - - DN_Str8 range = DN_Str8_Slice(string, head, (tail - head)); - DN_Str8Builder_AppendRef(&string_builder, range); - DN_Str8Builder_AppendRef(&string_builder, replace); - head = tail + find.size; - tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string - } - - if (string_builder.string_size == 0) { - // NOTE: No replacement possible, so we just do a full-copy - result = DN_Str8_Copy(arena, string); - } else { - DN_Str8 remainder = DN_Str8_Init(string.data + head, string.size - head); - DN_Str8Builder_AppendRef(&string_builder, remainder); - result = DN_Str8Builder_Build(&string_builder, arena); - } - - return result; -} - -DN_API DN_Str8 DN_Str8_ReplaceInsensitive(DN_Str8 string, DN_Str8 find, DN_Str8 replace, DN_USize start_index, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8_Replace(string, find, replace, start_index, arena, DN_Str8EqCase_Insensitive); - return result; -} - -DN_API void DN_Str8_Remove(DN_Str8 *string, DN_USize offset, DN_USize size) -{ - if (!string || !DN_Str8_HasData(*string)) - return; - - char *end = string->data + string->size; - char *dest = DN_MIN(string->data + offset, end); - char *src = DN_MIN(string->data + offset + size, end); - DN_USize bytes_to_move = end - src; - DN_MEMMOVE(dest, src, bytes_to_move); - string->size -= bytes_to_move; -} - -DN_API DN_Str8DotTruncateResult DN_Str8_DotTruncateMiddle(DN_Arena *arena, DN_Str8 str8, uint32_t side_size, DN_Str8 truncator) -{ - DN_Str8DotTruncateResult result = {}; - if (str8.size <= (side_size * 2)) { - result.str8 = DN_Str8_Copy(arena, str8); - return result; - } - - DN_Str8 head = DN_Str8_Slice(str8, 0, side_size); - DN_Str8 tail = DN_Str8_Slice(str8, str8.size - side_size, side_size); - result.str8 = DN_Str8_InitF(arena, "%.*s%.*s%.*s", DN_STR_FMT(head), DN_STR_FMT(truncator), DN_STR_FMT(tail)); - result.truncated = true; - return result; -} - -DN_API DN_Str8 DN_Str8_PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) -{ - DN_TLSTMem tmem = DN_TLS_PushTMem(arena); - DN_Str8Builder builder = DN_Str8Builder_Init_TLS(); - - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(src, DN_STR8("\n")); - while (split.lhs.size) { - DN_Str8Builder_AppendRef(&builder, pad); - DN_Str8Builder_AppendRef(&builder, split.lhs); - split = DN_Str8_BinarySplit(split.rhs, DN_STR8("\n")); - if (split.lhs.size) - DN_Str8Builder_AppendRef(&builder, DN_STR8("\n")); - } - - DN_Str8 result = DN_Str8Builder_Build(&builder, arena); - return result; -} - -DN_API DN_Str8 DN_Str8_Lower(DN_Arena *arena, DN_Str8 string) -{ - DN_Str8 result = DN_Str8_Copy(arena, string); - DN_FOR_UINDEX (index, result.size) - result.data[index] = DN_Char_ToLower(result.data[index]); - return result; -} - -DN_API DN_Str8 DN_Str8_Upper(DN_Arena *arena, DN_Str8 string) -{ - DN_Str8 result = DN_Str8_Copy(arena, string); - DN_FOR_UINDEX (index, result.size) - result.data[index] = DN_Char_ToUpper(result.data[index]); - return result; -} - -#if defined(__cplusplus) -DN_API bool operator==(DN_Str8 const &lhs, DN_Str8 const &rhs) -{ - bool result = DN_Str8_Eq(lhs, rhs, DN_Str8EqCase_Sensitive); - return result; -} - -DN_API bool operator!=(DN_Str8 const &lhs, DN_Str8 const &rhs) -{ - bool result = !(lhs == rhs); - return result; -} -#endif - -DN_API DN_Str8 DN_Str8_InitF(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list va; - va_start(va, fmt); - DN_Str8 result = DN_Str8_InitFV(arena, fmt, va); - va_end(va); - return result; -} - -DN_API DN_Str8 DN_Str8_InitFV(DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 result = {}; - if (!fmt) - return result; - - DN_USize size = DN_CStr8_FVSize(fmt, args); - if (size) { - result = DN_Str8_Alloc(arena, size, DN_ZeroMem_No); - if (DN_Str8_HasData(result)) - DN_VSNPRINTF(result.data, DN_Safe_SaturateCastISizeToInt(size + 1 /*null-terminator*/), fmt, args); - } - return result; -} - -DN_API DN_Str8 DN_Str8_Alloc(DN_Arena *arena, DN_USize size, DN_ZeroMem zero_mem) -{ - DN_Str8 result = {}; - result.data = DN_Arena_NewArray(arena, char, size + 1, zero_mem); - if (result.data) - result.size = size; - result.data[result.size] = 0; - return result; -} - -DN_API DN_Str8 DN_Str8_CopyCString(DN_Arena *arena, char const *string, DN_USize size) -{ - DN_Str8 result = {}; - if (!string) - return result; - - result = DN_Str8_Alloc(arena, size, DN_ZeroMem_No); - if (DN_Str8_HasData(result)) { - DN_MEMCPY(result.data, string, size); - result.data[size] = 0; - } - return result; -} - -DN_API DN_Str8 DN_Str8_Copy(DN_Arena *arena, DN_Str8 string) -{ - DN_Str8 result = DN_Str8_CopyCString(arena, string.data, string.size); - return result; -} - -// NOTE: [$STRB] DN_Str8Builder //////////////////////////////////////////////////////////////// -DN_API DN_Str8Builder DN_Str8Builder_Init(DN_Arena *arena) -{ - DN_Str8Builder result = {}; - result.arena = arena; - return result; -} - -DN_API DN_Str8Builder DN_Str8Builder_InitArrayRef(DN_Arena *arena, - DN_Str8 const *strings, - DN_USize size) -{ - DN_Str8Builder result = DN_Str8Builder_Init(arena); - DN_Str8Builder_AppendArrayRef(&result, strings, size); - return result; -} - -DN_API DN_Str8Builder DN_Str8Builder_InitArrayCopy(DN_Arena *arena, - DN_Str8 const *strings, - DN_USize size) -{ - DN_Str8Builder result = DN_Str8Builder_Init(arena); - DN_Str8Builder_AppendArrayCopy(&result, strings, size); - return result; -} - -DN_API bool DN_Str8Builder_AddArrayRef(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) -{ - if (!builder) - return false; - - if (!strings || size <= 0) - return true; - - DN_Str8Link *links = DN_Arena_NewArray(builder->arena, DN_Str8Link, size, DN_ZeroMem_No); - if (!links) - return false; - - if (add == DN_Str8BuilderAdd_Append) { - DN_FOR_UINDEX(index, size) { - DN_Str8 string = strings[index]; - DN_Str8Link *link = links + index; - - link->string = string; - link->next = NULL; - - if (builder->head) - builder->tail->next = link; - else - builder->head = link; - - builder->tail = link; - builder->count++; - builder->string_size += string.size; - } - } else { - DN_ASSERT(add == DN_Str8BuilderAdd_Prepend); - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6293) // NOTE: Ill-defined loop - for (DN_USize index = size - 1; index < size; index--) { - DN_MSVC_WARNING_POP - DN_Str8 string = strings[index]; - DN_Str8Link *link = links + index; - link->string = string; - link->next = builder->head; - builder->head = link; - if (!builder->tail) - builder->tail = link; - builder->count++; - builder->string_size += string.size; - } - } - return true; -} - -DN_API bool DN_Str8Builder_AddArrayCopy(DN_Str8Builder *builder, DN_Str8 const *strings, DN_USize size, DN_Str8BuilderAdd add) -{ - if (!builder) - return false; - - if (!strings || size <= 0) - return true; - - DN_ArenaTempMem tmp_mem = DN_Arena_TempMemBegin(builder->arena); - bool result = true; - DN_Str8 *strings_copy = DN_Arena_NewArray(builder->arena, DN_Str8, size, DN_ZeroMem_No); - DN_FOR_UINDEX (index, size) { - strings_copy[index] = DN_Str8_Copy(builder->arena, strings[index]); - if (strings_copy[index].size != strings[index].size) { - result = false; - break; - } - } - - if (result) - result = DN_Str8Builder_AddArrayRef(builder, strings_copy, size, add); - - if (!result) - DN_Arena_TempMemEnd(tmp_mem); - - return result; -} - -DN_API bool DN_Str8Builder_AddFV(DN_Str8Builder *builder, DN_Str8BuilderAdd add, DN_FMT_ATTRIB char const *fmt, va_list args) -{ - DN_Str8 string = DN_Str8_InitFV(builder->arena, fmt, args); - DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(builder->arena); - bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, add); - if (!result) - DN_Arena_TempMemEnd(temp_mem); - return result; -} - -DN_API bool DN_Str8Builder_AppendRef(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Append); - return result; -} - -DN_API bool DN_Str8Builder_AppendCopy(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Append); - return result; -} - -DN_API bool DN_Str8Builder_AppendF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_Str8Builder_AppendFV(builder, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_Str8Builder_AppendBytesRef(DN_Str8Builder *builder, void const *ptr, DN_USize size) -{ - DN_Str8 input = DN_Str8_Init(ptr, size); - bool result = DN_Str8Builder_AppendRef(builder, input); - return result; -} - -DN_API bool DN_Str8Builder_AppendBytesCopy(DN_Str8Builder *builder, void const *ptr, DN_USize size) -{ - DN_Str8 input = DN_Str8_Init(ptr, size); - bool result = DN_Str8Builder_AppendCopy(builder, input); - return result; -} - -static bool DN_Str8Builder_AppendBuilder_(DN_Str8Builder *dest, DN_Str8Builder const *src, bool copy) -{ - if (!dest) - return false; - if (!src) - return true; - - DN_Arena_TempMemBegin(dest->arena); - DN_Str8Link *links = DN_Arena_NewArray(dest->arena, DN_Str8Link, src->count, DN_ZeroMem_No); - if (!links) - return false; - - DN_Str8Link *first = nullptr; - DN_Str8Link *last = nullptr; - DN_USize link_index = 0; - bool result = true; - for (DN_Str8Link const *it = src->head; it; it = it->next) { - DN_Str8Link *link = links + link_index++; - link->next = nullptr; - link->string = it->string; - - if (copy) { - link->string = DN_Str8_Copy(dest->arena, it->string); - if (link->string.size != it->string.size) { - result = false; - break; - } - } - - if (last) { - last->next = link; - } else { - first = link; - } - last = link; - } - - if (result) { - if (dest->head) - dest->tail->next = first; - else - dest->head = first; - dest->tail = last; - dest->count += src->count; - dest->string_size += src->string_size; - } - return true; -} - -DN_API bool DN_Str8Builder_AppendBuilderRef(DN_Str8Builder *dest, DN_Str8Builder const *src) -{ - bool result = DN_Str8Builder_AppendBuilder_(dest, src, false); - return result; -} - -DN_API bool DN_Str8Builder_AppendBuilderCopy(DN_Str8Builder *dest, DN_Str8Builder const *src) -{ - bool result = DN_Str8Builder_AppendBuilder_(dest, src, true); - return result; -} - -DN_API bool DN_Str8Builder_PrependRef(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8Builder_AddArrayRef(builder, &string, 1, DN_Str8BuilderAdd_Prepend); - return result; -} - -DN_API bool DN_Str8Builder_PrependCopy(DN_Str8Builder *builder, DN_Str8 string) -{ - bool result = DN_Str8Builder_AddArrayCopy(builder, &string, 1, DN_Str8BuilderAdd_Prepend); - return result; -} - -DN_API bool DN_Str8Builder_PrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - bool result = DN_Str8Builder_PrependFV(builder, fmt, args); - va_end(args); - return result; -} - -DN_API bool DN_Str8Builder_Erase(DN_Str8Builder *builder, DN_Str8 string) -{ - for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { - if ((*it)->string == string) { - *it = (*it)->next; - builder->string_size -= string.size; - builder->count -= 1; - return true; - } - } - return false; -} - -DN_API DN_Str8Builder DN_Str8Builder_Copy(DN_Arena *arena, DN_Str8Builder const *builder) -{ - DN_Str8Builder result = DN_Str8Builder_Init(arena); - DN_Str8Builder_AppendBuilderCopy(&result, builder); - return result; -} - -DN_API DN_Str8 DN_Str8Builder_Build(DN_Str8Builder const *builder, DN_Arena *arena) -{ - DN_Str8 result = DN_Str8Builder_BuildDelimited(builder, DN_STR8(""), arena); - return result; -} - -DN_API DN_Str8 DN_Str8Builder_BuildDelimited(DN_Str8Builder const *builder, DN_Str8 delimiter, DN_Arena *arena) -{ - DN_Str8 result = DN_ZERO_INIT; - if (!builder || builder->string_size <= 0 || builder->count <= 0) - return result; - - DN_USize size_for_delimiter = DN_Str8_HasData(delimiter) ? ((builder->count - 1) * delimiter.size) : 0; - result.data = DN_Arena_NewArray(arena, - char, - builder->string_size + size_for_delimiter + 1 /*null terminator*/, - DN_ZeroMem_No); - if (!result.data) - return result; - - for (DN_Str8Link *link = builder->head; link; link = link->next) { - DN_MEMCPY(result.data + result.size, link->string.data, link->string.size); - result.size += link->string.size; - if (link->next && DN_Str8_HasData(delimiter)) { - DN_MEMCPY(result.data + result.size, delimiter.data, delimiter.size); - result.size += delimiter.size; - } - } - - result.data[result.size] = 0; - DN_ASSERT(result.size == builder->string_size + size_for_delimiter); - return result; -} - -DN_API DN_Str8 DN_Str8Builder_BuildCRT(DN_Str8Builder const *builder) -{ - DN_Str8 result = DN_ZERO_INIT; - if (!builder || builder->string_size <= 0 || builder->count <= 0) - return result; - - result.data = DN_CAST(char *)malloc(builder->string_size + 1); - if (!result.data) - return result; - - for (DN_Str8Link *link = builder->head; link; link = link->next) { - DN_MEMCPY(result.data + result.size, link->string.data, link->string.size); - result.size += link->string.size; - } - - result.data[result.size] = 0; - DN_ASSERT(result.size == builder->string_size); - return result; -} - -DN_API DN_Slice DN_Str8Builder_BuildSlice(DN_Str8Builder const *builder, DN_Arena *arena) -{ - DN_Slice result = DN_ZERO_INIT; - if (!builder || builder->string_size <= 0 || builder->count <= 0) - return result; - - result = DN_Slice_Alloc(arena, builder->count, DN_ZeroMem_No); - if (!result.data) - return result; - - DN_USize slice_index = 0; - for (DN_Str8Link *link = builder->head; link; link = link->next) - result.data[slice_index++] = DN_Str8_Copy(arena, link->string); - - DN_ASSERT(slice_index == builder->count); - return result; -} - -DN_API void DN_Str8Builder_Print(DN_Str8Builder const *builder) -{ - for (DN_Str8Link *link = builder ? builder->head : nullptr; link; link = link->next) - DN_Print(link->string); -} - -DN_API void DN_Str8Builder_PrintLn(DN_Str8Builder const *builder) -{ - for (DN_Str8Link *link = builder ? builder->head : nullptr; link; link = link->next) { - if (link->next) { - DN_Print(link->string); - } else { - DN_Print_Ln(link->string); - } - } -} - -// NOTE: [$CHAR] DN_Char ////////////////////////////////////////////////////////////////////////// -DN_API bool DN_Char_IsAlphabet(char ch) -{ - bool result = (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); - return result; -} - -DN_API bool DN_Char_IsDigit(char ch) -{ - bool result = (ch >= '0' && ch <= '9'); - return result; -} - -DN_API bool DN_Char_IsAlphaNum(char ch) -{ - bool result = DN_Char_IsAlphabet(ch) || DN_Char_IsDigit(ch); - return result; -} - -DN_API bool DN_Char_IsWhitespace(char ch) -{ - bool result = (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'); - return result; -} - -DN_API bool DN_Char_IsHex(char ch) -{ - bool result = ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || (ch >= '0' && ch <= '9')); - return result; -} - -DN_API DN_CharHexToU8 DN_Char_HexToU8(char ch) -{ - DN_CharHexToU8 result = {}; - result.success = true; - if (ch >= 'a' && ch <= 'f') - result.value = ch - 'a' + 10; - else if (ch >= 'A' && ch <= 'F') - result.value = ch - 'A' + 10; - else if (ch >= '0' && ch <= '9') - result.value = ch - '0'; - else - result.success = false; - return result; -} - -static char constexpr DN_HEX_LUT[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; -DN_API char DN_Char_ToHex(char ch) -{ - char result = DN_CAST(char)-1; - if (ch < 16) - result = DN_HEX_LUT[DN_CAST(uint8_t)ch]; - return result; -} - -DN_API char DN_Char_ToHexUnchecked(char ch) -{ - char result = DN_HEX_LUT[DN_CAST(uint8_t)ch]; - return result; -} - -DN_API char DN_Char_ToLower(char ch) -{ - char result = ch; - if (result >= 'A' && result <= 'Z') - result += 'a' - 'A'; - return result; -} - -DN_API char DN_Char_ToUpper(char ch) -{ - char result = ch; - if (result >= 'a' && result <= 'z') - result -= 'a' - 'A'; - return result; -} - - -// NOTE: [$UTFX] DN_UTF /////////////////////////////////////////////////////////////////////////// -DN_API int DN_UTF8_EncodeCodepoint(uint8_t utf8[4], uint32_t codepoint) -{ - // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ - // ----------------------------------------+----------------------------+--------------------+ - // UTF-8 (binary) | Code point (binary) | Range | - // ----------------------------------------+----------------------------+--------------------+ - // 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F | - // 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF | - // 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF | - // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | - // ----------------------------------------+----------------------------+--------------------+ - - if (codepoint <= 0b0111'1111) { - utf8[0] = DN_CAST(uint8_t) codepoint; - return 1; - } - - if (codepoint <= 0b0111'1111'1111) { - utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y - return 2; - } - - if (codepoint <= 0b1111'1111'1111'1111) { - utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z - return 3; - } - - if (codepoint <= 0b1'1111'1111'1111'1111'1111) { - utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x - utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y - utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z - utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w - return 4; - } - - return 0; -} - -DN_API int DN_UTF16_EncodeCodepoint(uint16_t utf16[2], uint32_t codepoint) -{ - // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ - // ----------------------------------------+------------------------------------+------------------+ - // UTF-16 (binary) | Code point (binary) | Range | - // ----------------------------------------+------------------------------------+------------------+ - // xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000???U+FFFF | - // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000???U+10FFFF | - // ----------------------------------------+------------------------------------+------------------+ - - if (codepoint <= 0b1111'1111'1111'1111) { - utf16[0] = DN_CAST(uint16_t) codepoint; - return 1; - } - - if (codepoint <= 0b1111'1111'1111'1111'1111) { - uint32_t surrogate_codepoint = codepoint + 0x10000; - utf16[0] = 0b1101'1000'0000'0000 | ((surrogate_codepoint >> 10) & 0b11'1111'1111); // x - utf16[1] = 0b1101'1100'0000'0000 | ((surrogate_codepoint >> 0) & 0b11'1111'1111); // y - return 2; - } - - return 0; -} diff --git a/dqn_tls.cpp b/dqn_tls.cpp deleted file mode 100644 index e261488..0000000 --- a/dqn_tls.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$$\ $$\ $$$$$$\ -// \__$$ __|$$ | $$ __$$\ -// $$ | $$ | $$ / \__| -// $$ | $$ | \$$$$$$\ -// $$ | $$ | \____$$\ -// $$ | $$ | $$\ $$ | -// $$ | $$$$$$$$\\$$$$$$ | -// \__| \________|\______/ -// -// dqn_tls.cpp -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -// NOTE: [$TCTX] DN_TLS ///////////////////////////////////////////////////////////////// -DN_TLSTMem::DN_TLSTMem(DN_TLS *tls, uint8_t arena_index, DN_TLSPushTMem push_tmem) -{ - DN_ASSERT(arena_index == DN_TLSArena_TMem0 || arena_index == DN_TLSArena_TMem1); - arena = tls->arenas + arena_index; - temp_mem = DN_Arena_TempMemBegin(arena); - destructed = false; - push_arena = push_tmem; - if (push_arena) - DN_TLS_PushArena(arena); -} - -DN_TLSTMem::~DN_TLSTMem() -{ - DN_ASSERT(destructed == false); - DN_Arena_TempMemEnd(temp_mem); - destructed = true; - if (push_arena) - DN_TLS_PopArena(); -} - -DN_API void DN_TLS_Init(DN_TLS *tls) -{ - DN_CHECK(tls); - if (tls->init) - return; - - DN_FOR_UINDEX (index, DN_TLSArena_Count) { - DN_Arena *arena = tls->arenas + index; - switch (DN_CAST(DN_TLSArena)index) { - default: *arena = DN_Arena_InitSize(DN_MEGABYTES(4), DN_KILOBYTES(4), DN_ArenaFlags_AllocCanLeak); break; - case DN_TLSArena_ErrorSink: *arena = DN_Arena_InitSize(DN_KILOBYTES(64), DN_KILOBYTES(4), DN_ArenaFlags_AllocCanLeak); break; - case DN_TLSArena_Count: DN_INVALID_CODE_PATH; break; - } - } - - tls->thread_id = DN_OS_ThreadID(); - tls->err_sink.arena = tls->arenas + DN_TLSArena_ErrorSink; - tls->init = true; -} - -DN_API void DN_TLS_Deinit(DN_TLS *tls) -{ - tls->init = false; - tls->err_sink = {}; - tls->arena_stack_index = {}; - DN_FOR_UINDEX(index, DN_TLSArena_Count) - { - DN_Arena *arena = tls->arenas + index; - DN_Arena_Deinit(arena); - } -} - -DN_API DN_TLS *DN_TLS_Get() -{ - DN_TLS *result = g_dn_os_thread_tls; - DN_ASSERT( - g_dn_core->init && - "Library context must be be initialised first by calling DN_Library_Init. This " - "initialises the main thread's TLS for you (no need to call DN_OS_ThreadSetTLS on main)"); - - DN_ASSERT(result && - "Thread must be assigned the TLS with DN_OS_ThreadSetTLS. If the library is " - "initialised, then, this thread was created without calling the set TLS function " - "for the spawned thread."); - return result; -} - -DN_API DN_Arena *DN_TLS_Arena() -{ - DN_TLS *tls = DN_TLS_Get(); - DN_Arena *result = tls->arenas + DN_TLSArena_Main; - return result; -} - -// TODO: Is there a way to handle conflict arenas without the user needing to -// manually pass it in? -DN_API DN_TLSTMem DN_TLS_GetTMem(void const *conflict_arena, DN_TLSPushTMem push_tmem) -{ - DN_TLS *tls = DN_TLS_Get(); - uint8_t tls_index = (uint8_t)-1; - for (uint8_t index = DN_TLSArena_TMem0; index <= DN_TLSArena_TMem1; index++) { - DN_Arena *arena = tls->arenas + index; - if (!conflict_arena || arena != conflict_arena) { - tls_index = index; - break; - } - } - - DN_ASSERT(tls_index != (uint8_t)-1); - return DN_TLSTMem(tls, tls_index, push_tmem); -} - -DN_API void DN_TLS_PushArena(DN_Arena *arena) -{ - DN_ASSERT(arena); - DN_TLS *tls = DN_TLS_Get(); - DN_ASSERT(tls->arena_stack_index < DN_ARRAY_UCOUNT(tls->arena_stack)); - tls->arena_stack[tls->arena_stack_index++] = arena; -} - -DN_API void DN_TLS_PopArena() -{ - DN_TLS *tls = DN_TLS_Get(); - DN_ASSERT(tls->arena_stack_index > 0); - tls->arena_stack_index--; -} - -DN_API DN_Arena *DN_TLS_TopArena() -{ - DN_TLS *tls = DN_TLS_Get(); - DN_Arena *result = nullptr; - if (tls->arena_stack_index) - result = tls->arena_stack[tls->arena_stack_index - 1]; - return result; -} - -DN_API void DN_TLS_BeginFrame(DN_Arena *frame_arena) -{ - DN_TLS *tls = DN_TLS_Get(); - tls->frame_arena = frame_arena; -} - -DN_API DN_Arena *DN_TLS_FrameArena() -{ - DN_TLS *tls = DN_TLS_Get(); - DN_Arena *result = tls->frame_arena; - return result; -} - diff --git a/dqn_tls.h b/dqn_tls.h deleted file mode 100644 index 194cd9e..0000000 --- a/dqn_tls.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$$\ $$\ $$\ $$$$$$$\ $$$$$$$$\ $$$$$$\ $$$$$$$\ -// \__$$ __|$$ | $$ |$$ __$$\ $$ _____|$$ __$$\ $$ __$$\ -// $$ | $$ | $$ |$$ | $$ |$$ | $$ / $$ |$$ | $$ | -// $$ | $$$$$$$$ |$$$$$$$ |$$$$$\ $$$$$$$$ |$$ | $$ | -// $$ | $$ __$$ |$$ __$$< $$ __| $$ __$$ |$$ | $$ | -// $$ | $$ | $$ |$$ | $$ |$$ | $$ | $$ |$$ | $$ | -// $$ | $$ | $$ |$$ | $$ |$$$$$$$$\ $$ | $$ |$$$$$$$ | -// \__| \__| \__|\__| \__|\________|\__| \__|\_______/ -// -// $$$$$$\ $$$$$$\ $$\ $$\ $$$$$$$$\ $$$$$$$$\ $$\ $$\ $$$$$$$$\ -// $$ __$$\ $$ __$$\ $$$\ $$ |\__$$ __|$$ _____|$$ | $$ |\__$$ __| -// $$ / \__|$$ / $$ |$$$$\ $$ | $$ | $$ | \$$\ $$ | $$ | -// $$ | $$ | $$ |$$ $$\$$ | $$ | $$$$$\ \$$$$ / $$ | -// $$ | $$ | $$ |$$ \$$$$ | $$ | $$ __| $$ $$< $$ | -// $$ | $$\ $$ | $$ |$$ |\$$$ | $$ | $$ | $$ /\$$\ $$ | -// \$$$$$$ | $$$$$$ |$$ | \$$ | $$ | $$$$$$$$\ $$ / $$ | $$ | -// \______/ \______/ \__| \__| \__| \________|\__| \__| \__| -// -// dqn_thread_context.h -- Per thread data (e.g. scratch arenas) -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -enum DN_TLSArena -{ - DN_TLSArena_Main, // NOTE: Arena for Permanent allocations - DN_TLSArena_ErrorSink, // NOTE: Arena for logging error information for this thread - - // NOTE: Per-thread scratch arenas (2 to prevent aliasing) - DN_TLSArena_TMem0, - DN_TLSArena_TMem1, - - DN_TLSArena_Count, -}; - -struct DN_TLS -{ - DN_B32 init; // Flag to track if Thread has been initialised - uint64_t thread_id; - DN_CallSite call_site; // Stores call-site information when requested by thread - DN_ErrSink err_sink; // Error handling state - DN_Arena arenas[DN_TLSArena_Count]; - - // Push and pop arenas onto the stack. Functions suffixed 'TLS' will use - // these arenas for memory allocation. - DN_Arena *arena_stack[8]; - DN_USize arena_stack_index; - - DN_Arena *frame_arena; - char name[64]; - uint8_t name_size; -}; - -// Push the temporary memory arena when retrieved, popped when the arena goes -// out of scope. Pushed arenas are used automatically as the allocator in TLS -// suffixed function. -enum DN_TLSPushTMem -{ - DN_TLSPushTMem_No, - DN_TLSPushTMem_Yes, -}; - -struct DN_TLSTMem -{ - DN_TLSTMem(DN_TLS *context, uint8_t context_index, DN_TLSPushTMem push_scratch); - ~DN_TLSTMem(); - - DN_Arena *arena; - DN_B32 destructed; - DN_TLSPushTMem push_arena; - DN_ArenaTempMem temp_mem; -}; - -DN_API void DN_TLS_Init(DN_TLS *tls); -DN_API void DN_TLS_Deinit(DN_TLS *tls); -DN_API DN_TLS * DN_TLS_Get(); -DN_API DN_Arena * DN_TLS_Arena(); -#define DN_TLS_SaveCallSite do { DN_TLS_Get()->call_site = DN_CALL_SITE; } while (0) -DN_API DN_TLSTMem DN_TLS_GetTMem(void const *conflict_arena, DN_TLSPushTMem push_tmp_mem); -#define DN_TLS_TMem(...) DN_TLS_GetTMem(__VA_ARGS__, DN_TLSPushTMem_No) -#define DN_TLS_PushTMem(...) DN_TLS_GetTMem(__VA_ARGS__, DN_TLSPushTMem_Yes) -DN_API void DN_TLS_PushArena(DN_Arena *arena); -DN_API void DN_TLS_PopArena(); -DN_API DN_Arena * DN_TLS_TopArena(); - -DN_API void DN_TLS_BeginFrame(DN_Arena *frame_arena); -DN_API DN_Arena * DN_TLS_FrameArena(); diff --git a/dqn_unit_tests.cpp b/dqn_unit_tests.cpp deleted file mode 100644 index 76838f4..0000000 --- a/dqn_unit_tests.cpp +++ /dev/null @@ -1,2206 +0,0 @@ -#define DN_UTEST_IMPLEMENTATION -#include "Standalone/dqn_utest.h" - -#include - -// NOTE: Taken from MSDN __cpuid example implementation -// https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170 -struct DN_RefImplCPUReport { - unsigned int nIds_ = 0; - unsigned int nExIds_ = 0; - char vendor_[0x20] = {}; - int vendorSize_ = 0; - char brand_[0x40] = {}; - int brandSize_ = 0; - bool isIntel_ = false; - bool isAMD_ = false; - uint32_t f_1_ECX_ = 0; - uint32_t f_1_EDX_ = 0; - uint32_t f_7_EBX_ = 0; - uint32_t f_7_ECX_ = 0; - uint32_t f_81_ECX_ = 0; - uint32_t f_81_EDX_ = 0; - int data_[400][4] = {}; - size_t dataSize_ = 0; - int extdata_[400][4] = {}; - size_t extdataSize_ = 0; - - bool SSE3(void) const { return f_1_ECX_ & (1 << 0); } - bool PCLMULQDQ(void) const { return f_1_ECX_ & (1 << 1); } - bool MONITOR(void) const { return f_1_ECX_ & (1 << 3); } - bool SSSE3(void) const { return f_1_ECX_ & (1 << 9); } - bool FMA(void) const { return f_1_ECX_ & (1 << 12); } - bool CMPXCHG16B(void) const { return f_1_ECX_ & (1 << 13); } - bool SSE41(void) const { return f_1_ECX_ & (1 << 19); } - bool SSE42(void) const { return f_1_ECX_ & (1 << 20); } - bool MOVBE(void) const { return f_1_ECX_ & (1 << 22); } - bool POPCNT(void) const { return f_1_ECX_ & (1 << 23); } - bool AES(void) const { return f_1_ECX_ & (1 << 25); } - bool XSAVE(void) const { return f_1_ECX_ & (1 << 26); } - bool OSXSAVE(void) const { return f_1_ECX_ & (1 << 27); } - bool AVX(void) const { return f_1_ECX_ & (1 << 28); } - bool F16C(void) const { return f_1_ECX_ & (1 << 29); } - bool RDRAND(void) const { return f_1_ECX_ & (1 << 30); } - - bool MSR(void) const { return f_1_EDX_ & (1 << 5); } - bool CX8(void) const { return f_1_EDX_ & (1 << 8); } - bool SEP(void) const { return f_1_EDX_ & (1 << 11); } - bool CMOV(void) const { return f_1_EDX_ & (1 << 15); } - bool CLFSH(void) const { return f_1_EDX_ & (1 << 19); } - bool MMX(void) const { return f_1_EDX_ & (1 << 23); } - bool FXSR(void) const { return f_1_EDX_ & (1 << 24); } - bool SSE(void) const { return f_1_EDX_ & (1 << 25); } - bool SSE2(void) const { return f_1_EDX_ & (1 << 26); } - - bool FSGSBASE(void) const { return f_7_EBX_ & (1 << 0); } - bool BMI1(void) const { return f_7_EBX_ & (1 << 3); } - bool HLE(void) const { return isIntel_ && f_7_EBX_ & (1 << 4); } - bool AVX2(void) const { return f_7_EBX_ & (1 << 5); } - bool BMI2(void) const { return f_7_EBX_ & (1 << 8); } - bool ERMS(void) const { return f_7_EBX_ & (1 << 9); } - bool INVPCID(void) const { return f_7_EBX_ & (1 << 10); } - bool RTM(void) const { return isIntel_ && f_7_EBX_ & (1 << 11); } - bool AVX512F(void) const { return f_7_EBX_ & (1 << 16); } - bool RDSEED(void) const { return f_7_EBX_ & (1 << 18); } - bool ADX(void) const { return f_7_EBX_ & (1 << 19); } - bool AVX512PF(void) const { return f_7_EBX_ & (1 << 26); } - bool AVX512ER(void) const { return f_7_EBX_ & (1 << 27); } - bool AVX512CD(void) const { return f_7_EBX_ & (1 << 28); } - bool SHA(void) const { return f_7_EBX_ & (1 << 29); } - - bool PREFETCHWT1(void) const { return f_7_ECX_ & (1 << 0); } - - bool LAHF(void) const { return f_81_ECX_ & (1 << 0); } - bool LZCNT(void) const { return isIntel_ && f_81_ECX_ & (1 << 5); } - bool ABM(void) const { return isAMD_ && f_81_ECX_ & (1 << 5); } - bool SSE4a(void) const { return isAMD_ && f_81_ECX_ & (1 << 6); } - bool XOP(void) const { return isAMD_ && f_81_ECX_ & (1 << 11); } - bool TBM(void) const { return isAMD_ && f_81_ECX_ & (1 << 21); } - - bool SYSCALL(void) const { return isIntel_ && f_81_EDX_ & (1 << 11); } - bool MMXEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 22); } - bool RDTSCP(void) const { return f_81_EDX_ & (1 << 27); } - bool _3DNOWEXT(void) const { return isAMD_ && f_81_EDX_ & (1 << 30); } - bool _3DNOW(void) const { return isAMD_ && f_81_EDX_ & (1 << 31); } -}; - -DN_RefImplCPUReport DN_RefImplCPUReport_Init() -{ - DN_RefImplCPUReport result = {}; - - // int cpuInfo[4] = {-1}; - int cpui[4]; - - // Calling __cpuid with 0x0 as the function_id argument - // gets the number of the highest valid function ID. - __cpuid(cpui, 0); - result.nIds_ = cpui[0]; - - for (unsigned int i = 0; i <= result.nIds_; ++i) { - __cpuidex(cpui, i, 0); - memcpy(result.data_[result.dataSize_++], cpui, sizeof(cpui)); - } - - // Capture vendor string - *reinterpret_cast(result.vendor_) = result.data_[0][1]; - *reinterpret_cast(result.vendor_ + 4) = result.data_[0][3]; - *reinterpret_cast(result.vendor_ + 8) = result.data_[0][2]; - result.vendorSize_ = (int)strlen(result.vendor_); - - if (strcmp(result.vendor_, "GenuineIntel") == 0) - result.isIntel_ = true; - else if (strcmp(result.vendor_, "AuthenticAMD") == 0) - result.isAMD_ = true; - - // load bitset with flags for function 0x00000001 - if (result.nIds_ >= 1) { - result.f_1_ECX_ = result.data_[1][2]; - result.f_1_EDX_ = result.data_[1][3]; - } - - // load bitset with flags for function 0x00000007 - if (result.nIds_ >= 7) { - result.f_7_EBX_ = result.data_[7][1]; - result.f_7_ECX_ = result.data_[7][2]; - } - - // Calling __cpuid with 0x80000000 as the function_id argument - // gets the number of the highest valid extended ID. - __cpuid(cpui, 0x80000000); - result.nExIds_ = cpui[0]; - - for (unsigned int i = 0x80000000; i <= result.nExIds_; ++i) { - __cpuidex(cpui, i, 0); - memcpy(result.extdata_[result.extdataSize_++], cpui, sizeof(cpui)); - } - - // load bitset with flags for function 0x80000001 - if (result.nExIds_ >= 0x80000001) { - result.f_81_ECX_ = result.extdata_[1][2]; - result.f_81_EDX_ = result.extdata_[1][3]; - } - - // Interpret CPU brand string if reported - if (result.nExIds_ >= 0x80000004) { - memcpy(result.brand_, result.extdata_[2], sizeof(cpui)); - memcpy(result.brand_ + 16, result.extdata_[3], sizeof(cpui)); - memcpy(result.brand_ + 32, result.extdata_[4], sizeof(cpui)); - result.brandSize_ = (int)strlen(result.brand_); - } - - return result; -} - -#if 0 -static void DN_RefImpl_CPUReportDump() // Print out supported instruction set features -{ - auto support_message = [](std::string isa_feature, bool is_supported) { - printf("%s %s\n", isa_feature.c_str(), is_supported ? "supported" : "not supported"); - }; - - printf("%s\n", DN_RefImplCPUReport::Vendor().c_str()); - printf("%s\n", DN_RefImplCPUReport::Brand().c_str()); - - support_message("3DNOW", DN_RefImplCPUReport::_3DNOW()); - support_message("3DNOWEXT", DN_RefImplCPUReport::_3DNOWEXT()); - support_message("ABM", DN_RefImplCPUReport::ABM()); - support_message("ADX", DN_RefImplCPUReport::ADX()); - support_message("AES", DN_RefImplCPUReport::AES()); - support_message("AVX", DN_RefImplCPUReport::AVX()); - support_message("AVX2", DN_RefImplCPUReport::AVX2()); - support_message("AVX512CD", DN_RefImplCPUReport::AVX512CD()); - support_message("AVX512ER", DN_RefImplCPUReport::AVX512ER()); - support_message("AVX512F", DN_RefImplCPUReport::AVX512F()); - support_message("AVX512PF", DN_RefImplCPUReport::AVX512PF()); - support_message("BMI1", DN_RefImplCPUReport::BMI1()); - support_message("BMI2", DN_RefImplCPUReport::BMI2()); - support_message("CLFSH", DN_RefImplCPUReport::CLFSH()); - support_message("CMPXCHG16B", DN_RefImplCPUReport::CMPXCHG16B()); - support_message("CX8", DN_RefImplCPUReport::CX8()); - support_message("ERMS", DN_RefImplCPUReport::ERMS()); - support_message("F16C", DN_RefImplCPUReport::F16C()); - support_message("FMA", DN_RefImplCPUReport::FMA()); - support_message("FSGSBASE", DN_RefImplCPUReport::FSGSBASE()); - support_message("FXSR", DN_RefImplCPUReport::FXSR()); - support_message("HLE", DN_RefImplCPUReport::HLE()); - support_message("INVPCID", DN_RefImplCPUReport::INVPCID()); - support_message("LAHF", DN_RefImplCPUReport::LAHF()); - support_message("LZCNT", DN_RefImplCPUReport::LZCNT()); - support_message("MMX", DN_RefImplCPUReport::MMX()); - support_message("MMXEXT", DN_RefImplCPUReport::MMXEXT()); - support_message("MONITOR", DN_RefImplCPUReport::MONITOR()); - support_message("MOVBE", DN_RefImplCPUReport::MOVBE()); - support_message("MSR", DN_RefImplCPUReport::MSR()); - support_message("OSXSAVE", DN_RefImplCPUReport::OSXSAVE()); - support_message("PCLMULQDQ", DN_RefImplCPUReport::PCLMULQDQ()); - support_message("POPCNT", DN_RefImplCPUReport::POPCNT()); - support_message("PREFETCHWT1", DN_RefImplCPUReport::PREFETCHWT1()); - support_message("RDRAND", DN_RefImplCPUReport::RDRAND()); - support_message("RDSEED", DN_RefImplCPUReport::RDSEED()); - support_message("RDTSCP", DN_RefImplCPUReport::RDTSCP()); - support_message("RTM", DN_RefImplCPUReport::RTM()); - support_message("SEP", DN_RefImplCPUReport::SEP()); - support_message("SHA", DN_RefImplCPUReport::SHA()); - support_message("SSE", DN_RefImplCPUReport::SSE()); - support_message("SSE2", DN_RefImplCPUReport::SSE2()); - support_message("SSE3", DN_RefImplCPUReport::SSE3()); - support_message("SSE4.1", DN_RefImplCPUReport::SSE41()); - support_message("SSE4.2", DN_RefImplCPUReport::SSE42()); - support_message("SSE4a", DN_RefImplCPUReport::SSE4a()); - support_message("SSSE3", DN_RefImplCPUReport::SSSE3()); - support_message("SYSCALL", DN_RefImplCPUReport::SYSCALL()); - support_message("TBM", DN_RefImplCPUReport::TBM()); - support_message("XOP", DN_RefImplCPUReport::XOP()); - support_message("XSAVE", DN_RefImplCPUReport::XSAVE()); -}; -#endif - -static DN_UTest DN_Test_Base() -{ - DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_Base") { - DN_UTEST_TEST("Query CPUID") { - DN_CPUReport cpu_report = DN_CPU_Report(); - - // NOTE: Sanity check our report against MSDN's example //////////////////////////////////////// - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); - - // NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example ///////////// - #if 0 - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP()); - DN_UTEST_ASSERT(&test, DN_CPU_HasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE()); - #endif - - } - } - return test; -} - -static DN_UTest DN_Test_Arena() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_Arena") { - DN_UTEST_TEST("Reused memory is zeroed out") { - uint8_t alignment = 1; - DN_USize alloc_size = DN_KILOBYTES(128); - DN_Arena arena = {}; - DN_DEFER { - DN_Arena_Deinit(&arena); - }; - - // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena - uintptr_t first_ptr_address = 0; - { - DN_ArenaTempMem temp_mem = DN_Arena_TempMemBegin(&arena); - void *ptr = DN_Arena_Alloc(&arena, alloc_size, alignment, DN_ZeroMem_Yes); - first_ptr_address = DN_CAST(uintptr_t)ptr; - DN_MEMSET(ptr, 'z', alloc_size); - DN_Arena_TempMemEnd(temp_mem); - } - - // NOTE: Reallocate 128 kilobytes - char *ptr = DN_CAST(char *)DN_Arena_Alloc(&arena, alloc_size, alignment, DN_ZeroMem_Yes); - - // NOTE: Double check we got the same pointer - DN_UTEST_ASSERT(&test, first_ptr_address == DN_CAST(uintptr_t)ptr); - - // NOTE: Check that the bytes are set to 0 - for (DN_USize i = 0; i < alloc_size; i++) - DN_UTEST_ASSERT(&test, ptr[i] == 0); - } - - DN_UTEST_TEST("Test arena grows naturally, 1mb + 4mb") { - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - DN_Arena arena = DN_Arena_InitSize(DN_MEGABYTES(2), DN_MEGABYTES(2), DN_ArenaFlags_Nil); - DN_DEFER { - DN_Arena_Deinit(&arena); - }; - - char *ptr_1mb = DN_Arena_NewArray(&arena, char, DN_MEGABYTES(1), DN_ZeroMem_Yes); - char *ptr_4mb = DN_Arena_NewArray(&arena, char, DN_MEGABYTES(4), DN_ZeroMem_Yes); - DN_UTEST_ASSERT(&test, ptr_1mb); - DN_UTEST_ASSERT(&test, ptr_4mb); - - DN_ArenaBlock const *block_4mb_begin = arena.curr; - char const *block_4mb_end = DN_CAST(char *)block_4mb_begin + block_4mb_begin->reserve; - - DN_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; - DN_UTEST_ASSERTF(&test, block_1mb_begin, "New block should have been allocated"); - char const *block_1mb_end = DN_CAST(char *)block_1mb_begin + block_1mb_begin->reserve; - - DN_UTEST_ASSERTF(&test, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); - DN_UTEST_ASSERTF(&test, ptr_1mb >= DN_CAST(char *)block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); - DN_UTEST_ASSERTF(&test, ptr_4mb >= DN_CAST(char *)block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); - } - - DN_UTEST_TEST("Test arena grows naturally, 1mb, temp memory 4mb") { - DN_Arena arena = DN_Arena_InitSize(DN_MEGABYTES(2), DN_MEGABYTES(2), DN_ArenaFlags_Nil); - DN_DEFER { - DN_Arena_Deinit(&arena); - }; - - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DN_CAST(char *)DN_Arena_Alloc(&arena, DN_MEGABYTES(1), 1 /*align*/, DN_ZeroMem_Yes); - DN_UTEST_ASSERT(&test, ptr_1mb); - - DN_ArenaTempMem temp_memory = DN_Arena_TempMemBegin(&arena); - { - char *ptr_4mb = DN_Arena_NewArray(&arena, char, DN_MEGABYTES(4), DN_ZeroMem_Yes); - DN_UTEST_ASSERT(&test, ptr_4mb); - - DN_ArenaBlock const *block_4mb_begin = arena.curr; - char const *block_4mb_end = DN_CAST(char *) block_4mb_begin + block_4mb_begin->reserve; - - DN_ArenaBlock const *block_1mb_begin = block_4mb_begin->prev; - char const *block_1mb_end = DN_CAST(char *) block_1mb_begin + block_1mb_begin->reserve; - - DN_UTEST_ASSERTF(&test, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); - DN_UTEST_ASSERTF(&test, ptr_1mb >= DN_CAST(char *)block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); - DN_UTEST_ASSERTF(&test, ptr_4mb >= DN_CAST(char *)block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); - } - DN_Arena_TempMemEnd(temp_memory); - DN_UTEST_ASSERT (&test, arena.curr->prev == nullptr); - DN_UTEST_ASSERTF(&test, - arena.curr->reserve >= DN_MEGABYTES(1), - "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", - (arena.curr->reserve / 1024 / 1024), - arena.curr->reserve, - DN_MEGABYTES(1)); - } - } - return test; -} - -static DN_UTest DN_Test_Bin() -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_Bin") { - DN_UTEST_TEST("Convert 0x123") { - uint64_t result = DN_HexToU64(DN_STR8("0x123")); - DN_UTEST_ASSERTF(&test, result == 0x123, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert 0xFFFF") { - uint64_t result = DN_HexToU64(DN_STR8("0xFFFF")); - DN_UTEST_ASSERTF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert FFFF") { - uint64_t result = DN_HexToU64(DN_STR8("FFFF")); - DN_UTEST_ASSERTF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert abCD") { - uint64_t result = DN_HexToU64(DN_STR8("abCD")); - DN_UTEST_ASSERTF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert 0xabCD") { - uint64_t result = DN_HexToU64(DN_STR8("0xabCD")); - DN_UTEST_ASSERTF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert 0x") { - uint64_t result = DN_HexToU64(DN_STR8("0x")); - DN_UTEST_ASSERTF(&test, result == 0x0, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert 0X") { - uint64_t result = DN_HexToU64(DN_STR8("0X")); - DN_UTEST_ASSERTF(&test, result == 0x0, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert 3") { - uint64_t result = DN_HexToU64(DN_STR8("3")); - DN_UTEST_ASSERTF(&test, result == 3, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert f") { - uint64_t result = DN_HexToU64(DN_STR8("f")); - DN_UTEST_ASSERTF(&test, result == 0xf, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert g") { - uint64_t result = DN_HexToU64(DN_STR8("g")); - DN_UTEST_ASSERTF(&test, result == 0, "result: %" PRIu64, result); - } - - DN_UTEST_TEST("Convert -0x3") { - uint64_t result = DN_HexToU64(DN_STR8("-0x3")); - DN_UTEST_ASSERTF(&test, result == 0, "result: %" PRIu64, result); - } - - uint32_t number = 0xd095f6; - DN_UTEST_TEST("Convert %x to string", number) { - DN_Str8 number_hex = DN_BytesToHex(tmem.arena, &number, sizeof(number)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(number_hex, DN_STR8("f695d000")), "number_hex=%.*s", DN_STR_FMT(number_hex)); - } - - number = 0xf6ed00; - DN_UTEST_TEST("Convert %x to string", number) { - DN_Str8 number_hex = DN_BytesToHex(tmem.arena, &number, sizeof(number)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(number_hex, DN_STR8("00edf600")), "number_hex=%.*s", DN_STR_FMT(number_hex)); - } - - DN_Str8 hex = DN_STR8("0xf6ed00"); - DN_UTEST_TEST("Convert %.*s to bytes", DN_STR_FMT(hex)) { - DN_Str8 bytes = DN_HexToBytes(tmem.arena, hex); - DN_UTEST_ASSERTF(&test, - DN_Str8_Eq(bytes, DN_STR8("\xf6\xed\x00")), - "number_hex=%.*s", - DN_STR_FMT(DN_BytesToHex(tmem.arena, bytes.data, bytes.size))); - } - - } - return test; -} - - -static DN_UTest DN_Test_BinarySearch() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_BinarySearch") { - DN_UTEST_TEST("Search array of 1 item") { - uint32_t array[] = {1}; - DN_BinarySearchResult result = {}; - - // NOTE: Match ============================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - // NOTE: Lower bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - // NOTE: Upper bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - } - - DN_UTEST_TEST("Search array of 2 items") { - uint32_t array[] = {1}; - DN_BinarySearchResult result = {}; - - // NOTE: Match ============================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - // NOTE: Lower bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - // NOTE: Upper bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - } - - DN_UTEST_TEST("Search array of 3 items") { - uint32_t array[] = {1, 2, 3}; - DN_BinarySearchResult result = {}; - - // NOTE: Match ============================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - // NOTE: Lower bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - // NOTE: Upper bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - } - - DN_UTEST_TEST("Search array of 4 items") { - uint32_t array[] = {1, 2, 3, 4}; - DN_BinarySearchResult result = {}; - - // NOTE: Match ============================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 5U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - // NOTE: Lower bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 5U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - // NOTE: Upper bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 1); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 3); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 5U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - } - - DN_UTEST_TEST("Search array with duplicate items") { - uint32_t array[] = {1, 1, 2, 2, 3}; - DN_BinarySearchResult result = {}; - - // NOTE: Match ============================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_Match); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 5); - - // NOTE: Lower bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 4U /*find*/, DN_BinarySearchType_LowerBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 5); - - // NOTE: Upper bound ======================================================================= - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 0U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 1U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 2U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 4); - - result = DN_BinarySearch(array, DN_ARRAY_UCOUNT(array), 3U /*find*/, DN_BinarySearchType_UpperBound); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 5); - } - } - return test; -} - -static DN_UTest DN_Test_DSMap() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_DSMap") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - { - DN_Arena arena = {}; - uint32_t const MAP_SIZE = 64; - DN_DSMap map = DN_DSMap_Init(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER { DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); }; - - DN_UTEST_TEST("Find non-existent value") { - DN_DSMapResult find = DN_DSMap_FindKeyStr8(&map, DN_STR8("Foo")); - DN_UTEST_ASSERT(&test, !find.found); - DN_UTEST_ASSERT(&test, map.size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/); - } - - DN_DSMapKey key = DN_DSMap_KeyCStr8(&map, "Bar"); - DN_UTEST_TEST("Insert value and lookup") { - uint64_t desired_value = 0xF00BAA; - uint64_t *slot_value = DN_DSMap_Set(&map, key, desired_value).value; - DN_UTEST_ASSERT(&test, slot_value); - DN_UTEST_ASSERT(&test, map.size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.occupied == 2); - - uint64_t *value = DN_DSMap_Find(&map, key).value; - DN_UTEST_ASSERT(&test, value); - DN_UTEST_ASSERT(&test, *value == desired_value); - } - - DN_UTEST_TEST("Remove key") { - DN_DSMap_Erase(&map, key); - DN_UTEST_ASSERT(&test, map.size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/); - } - } - - enum DSMapTestType { DSMapTestType_Set, DSMapTestType_MakeSlot, DSMapTestType_Count }; - for (int test_type = 0; test_type < DSMapTestType_Count; test_type++) { - DN_Str8 prefix = {}; - switch (test_type) { - case DSMapTestType_Set: prefix = DN_STR8("Set"); break; - case DSMapTestType_MakeSlot: prefix = DN_STR8("Make slot"); break; - } - - DN_ArenaTempMemScope temp_mem_scope = DN_ArenaTempMemScope(tmem.arena); - DN_Arena arena = {}; - uint32_t const MAP_SIZE = 64; - DN_DSMap map = DN_DSMap_Init(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER { DN_DSMap_Deinit(&map, DN_ZeroMem_Yes); }; - - DN_UTEST_TEST("%.*s: Test growing", DN_STR_FMT(prefix)) { - uint64_t map_start_size = map.size; - uint64_t value = 0; - uint64_t grow_threshold = map_start_size * 3 / 4; - for (; map.occupied != grow_threshold; value++) { - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); - DN_UTEST_ASSERT(&test, !DN_DSMap_Find(&map, key).found); - DN_DSMapResult make_result = {}; - if (test_type == DSMapTestType_Set) { - make_result = DN_DSMap_Set(&map, key, value); - } else { - make_result = DN_DSMap_Make(&map, key); - } - DN_UTEST_ASSERT(&test, !make_result.found); - DN_UTEST_ASSERT(&test, DN_DSMap_Find(&map, key).value); - } - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.size == map_start_size); - DN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/ + value); - - { // NOTE: One more item should cause the table to grow by 2x - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); - DN_DSMapResult make_result = {}; - if (test_type == DSMapTestType_Set) { - make_result = DN_DSMap_Set(&map, key, value); - } else { - make_result = DN_DSMap_Make(&map, key); - } - - value++; - DN_UTEST_ASSERT(&test, !make_result.found); - DN_UTEST_ASSERT(&test, map.size == map_start_size * 2); - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/ + value); - } - } - - DN_UTEST_TEST("%.*s: Check the sentinel is present", DN_STR_FMT(prefix)) { - DN_DSMapSlot NIL_SLOT = {}; - DN_DSMapSlot sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT]; - DN_UTEST_ASSERT(&test, DN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0); - } - - DN_UTEST_TEST("%.*s: Recheck all the hash tables values after growing", DN_STR_FMT(prefix)) { - for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) { - DN_DSMapSlot const *slot = map.slots + index; - - // NOTE: Validate each slot value - uint64_t value_test = index - 1; - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value_test); - DN_UTEST_ASSERT(&test, DN_DSMap_KeyEquals(slot->key, key)); - if (test_type == DSMapTestType_Set) { - DN_UTEST_ASSERT(&test, slot->value == value_test); - } else { - DN_UTEST_ASSERT(&test, slot->value == 0); // NOTE: Make slot does not set the key so should be 0 - } - DN_UTEST_ASSERT(&test, slot->key.hash == DN_DSMap_Hash(&map, slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMap_Find(&map, slot->key); - DN_UTEST_ASSERT(&test, slot->value == *check.value); - } - } - - DN_UTEST_TEST("%.*s: Test shrinking", DN_STR_FMT(prefix)) { - uint64_t start_map_size = map.size; - uint64_t start_map_occupied = map.occupied; - uint64_t value = 0; - uint64_t shrink_threshold = map.size * 1 / 4; - for (; map.occupied != shrink_threshold; value++) { - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); - DN_UTEST_ASSERT(&test, DN_DSMap_Find(&map, key).found); - DN_DSMap_Erase(&map, key); - DN_UTEST_ASSERT(&test, !DN_DSMap_Find(&map, key).found); - } - DN_UTEST_ASSERT(&test, map.size == start_map_size); - DN_UTEST_ASSERT(&test, map.occupied == start_map_occupied - value); - - { // NOTE: One more item should cause the table to shrink by 2x - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); - DN_DSMap_Erase(&map, key); - value++; - - DN_UTEST_ASSERT(&test, map.size == start_map_size / 2); - DN_UTEST_ASSERT(&test, map.occupied == start_map_occupied - value); - } - - { // NOTE: Check the sentinel is present - DN_DSMapSlot NIL_SLOT = {}; - DN_DSMapSlot sentinel = map.slots[DN_DS_MAP_SENTINEL_SLOT]; - DN_UTEST_ASSERT(&test, DN_MEMCMP(&sentinel, &NIL_SLOT, sizeof(NIL_SLOT)) == 0); - } - - // NOTE: Recheck all the hash table values after shrinking - for (uint64_t index = 1 /*Sentinel*/; index < map.occupied; index++) { - - // NOTE: Generate the key - uint64_t value_test = value + (index - 1); - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value_test); - - // NOTE: Validate each slot value - DN_DSMapResult find_result = DN_DSMap_Find(&map, key); - DN_UTEST_ASSERT(&test, find_result.value); - DN_UTEST_ASSERT(&test, find_result.slot->key == key); - if (test_type == DSMapTestType_Set) { - DN_UTEST_ASSERT(&test, *find_result.value == value_test); - } else { - DN_UTEST_ASSERT(&test, *find_result.value == 0); // NOTE: Make slot does not set the key so should be 0 - } - DN_UTEST_ASSERT(&test, find_result.slot->key.hash == DN_DSMap_Hash(&map, find_result.slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMap_Find(&map, find_result.slot->key); - DN_UTEST_ASSERT(&test, *find_result.value == *check.value); - } - - for (; map.occupied != 1; value++) { // NOTE: Remove all items from the table - DN_DSMapKey key = DN_DSMap_KeyU64(&map, value); - DN_UTEST_ASSERT(&test, DN_DSMap_Find(&map, key).found); - DN_DSMap_Erase(&map, key); - DN_UTEST_ASSERT(&test, !DN_DSMap_Find(&map, key).found); - } - DN_UTEST_ASSERT(&test, map.initial_size == MAP_SIZE); - DN_UTEST_ASSERT(&test, map.size == map.initial_size); - DN_UTEST_ASSERT(&test, map.occupied == 1 /*Sentinel*/); - } - } - } - return test; -} - -static DN_UTest DN_Test_FStr8() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_FStr8") { - DN_UTEST_TEST("Append too much fails") { - DN_FStr8<4> str = {}; - DN_UTEST_ASSERT(&test, !DN_FStr8_Add(&str, DN_STR8("abcde"))); - } - - DN_UTEST_TEST("Append format string too much fails") { - DN_FStr8<4> str = {}; - DN_UTEST_ASSERT(&test, !DN_FStr8_AddF(&str, "abcde")); - } - } - return test; -} - -static DN_UTest DN_Test_Fs() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_OS_[Path|File]") { - DN_UTEST_TEST("Make directory recursive \"abcd/efgh\"") { - DN_UTEST_ASSERTF(&test, DN_OS_MakeDir(DN_STR8("abcd/efgh")), "Failed to make directory"); - DN_UTEST_ASSERTF(&test, DN_OS_DirExists(DN_STR8("abcd")), "Directory was not made"); - DN_UTEST_ASSERTF(&test, DN_OS_DirExists(DN_STR8("abcd/efgh")), "Subdirectory was not made"); - DN_UTEST_ASSERTF(&test, DN_OS_FileExists(DN_STR8("abcd")) == false, "This function should only return true for files"); - DN_UTEST_ASSERTF(&test, DN_OS_FileExists(DN_STR8("abcd/efgh")) == false, "This function should only return true for files"); - DN_UTEST_ASSERTF(&test, DN_OS_PathDelete(DN_STR8("abcd/efgh")), "Failed to delete directory"); - DN_UTEST_ASSERTF(&test, DN_OS_PathDelete(DN_STR8("abcd")), "Failed to cleanup directory"); - } - - DN_UTEST_TEST("File write, read, copy, move and delete") { - // NOTE: Write step - DN_Str8 const SRC_FILE = DN_STR8("dqn_test_file"); - DN_B32 write_result = DN_OS_WriteAll(SRC_FILE, DN_STR8("test"), nullptr); - DN_UTEST_ASSERT(&test, write_result); - DN_UTEST_ASSERT(&test, DN_OS_FileExists(SRC_FILE)); - - // NOTE: Read step - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 read_file = DN_OS_ReadAll(tmem.arena, SRC_FILE, nullptr); - DN_UTEST_ASSERTF(&test, DN_Str8_HasData(read_file), "Failed to load file"); - DN_UTEST_ASSERTF(&test, read_file.size == 4, "File read wrong amount of bytes"); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(read_file, DN_STR8("test")), "read(%zu): %.*s", read_file.size, DN_STR_FMT(read_file)); - - // NOTE: Copy step - DN_Str8 const COPY_FILE = DN_STR8("dqn_test_file_copy"); - DN_B32 copy_result = DN_OS_CopyFile(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr); - DN_UTEST_ASSERT(&test, copy_result); - DN_UTEST_ASSERT(&test, DN_OS_FileExists(COPY_FILE)); - - // NOTE: Move step - DN_Str8 const MOVE_FILE = DN_STR8("dqn_test_file_move"); - DN_B32 move_result = DN_OS_MoveFile(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); - DN_UTEST_ASSERT(&test, move_result); - DN_UTEST_ASSERT(&test, DN_OS_FileExists(MOVE_FILE)); - DN_UTEST_ASSERTF(&test, DN_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original"); - - // NOTE: Delete step - DN_B32 delete_src_file = DN_OS_PathDelete(SRC_FILE); - DN_B32 delete_moved_file = DN_OS_PathDelete(MOVE_FILE); - DN_UTEST_ASSERT(&test, delete_src_file); - DN_UTEST_ASSERT(&test, delete_moved_file); - - // NOTE: Deleting non-existent file fails - DN_B32 delete_non_existent_src_file = DN_OS_PathDelete(SRC_FILE); - DN_B32 delete_non_existent_moved_file = DN_OS_PathDelete(MOVE_FILE); - DN_UTEST_ASSERT(&test, delete_non_existent_moved_file == false); - DN_UTEST_ASSERT(&test, delete_non_existent_src_file == false); - } - } - return test; -} - -static DN_UTest DN_Test_FixedArray() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_FArray") { - DN_UTEST_TEST("Initialise from raw array") { - int raw_array[] = {1, 2}; - auto array = DN_FArray_Init(raw_array, DN_ARRAY_UCOUNT(raw_array)); - DN_UTEST_ASSERT(&test, array.size == 2); - DN_UTEST_ASSERT(&test, array.data[0] == 1); - DN_UTEST_ASSERT(&test, array.data[1] == 2); - } - - DN_UTEST_TEST("Erase stable 1 element from array") { - int raw_array[] = {1, 2, 3}; - auto array = DN_FArray_Init(raw_array, DN_ARRAY_UCOUNT(raw_array)); - DN_FArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Stable); - DN_UTEST_ASSERT(&test, array.size == 2); - DN_UTEST_ASSERT(&test, array.data[0] == 1); - DN_UTEST_ASSERT(&test, array.data[1] == 3); - } - - DN_UTEST_TEST("Erase unstable 1 element from array") { - int raw_array[] = {1, 2, 3}; - auto array = DN_FArray_Init(raw_array, DN_ARRAY_UCOUNT(raw_array)); - DN_FArray_EraseRange(&array, 0 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Unstable); - DN_UTEST_ASSERT(&test, array.size == 2); - DN_UTEST_ASSERT(&test, array.data[0] == 3); - DN_UTEST_ASSERT(&test, array.data[1] == 2); - } - - DN_UTEST_TEST("Add 1 element to array") { - int const ITEM = 2; - int raw_array[] = {1}; - auto array = DN_FArray_Init(raw_array, DN_ARRAY_UCOUNT(raw_array)); - DN_FArray_Add(&array, ITEM); - DN_UTEST_ASSERT(&test, array.size == 2); - DN_UTEST_ASSERT(&test, array.data[0] == 1); - DN_UTEST_ASSERT(&test, array.data[1] == ITEM); - } - - DN_UTEST_TEST("Clear array") { - int raw_array[] = {1}; - auto array = DN_FArray_Init(raw_array, DN_ARRAY_UCOUNT(raw_array)); - DN_FArray_Clear(&array); - DN_UTEST_ASSERT(&test, array.size == 0); - } - } - return test; -} - -static DN_UTest DN_Test_Intrinsics() -{ - DN_UTest test = {}; - // TODO(dn): We don't have meaningful tests here, but since - // atomics/intrinsics are implemented using macros we ensure the macro was - // written properly with these tests. - - DN_MSVC_WARNING_PUSH - - // NOTE: MSVC SAL complains that we are using Interlocked functionality on - // variables it has detected as *not* being shared across threads. This is - // fine, we're just running some basic tests, so permit it. - // - // Warning 28112 is a knock-on effect of this that it doesn't like us - // reading the value of the variable that has been used in an Interlocked - // function locally. - DN_MSVC_WARNING_DISABLE(28113) // Accessing a local variable val via an Interlocked function. - DN_MSVC_WARNING_DISABLE(28112) // A variable (val) which is accessed via an Interlocked function must always be accessed via an Interlocked function. See line 759. - - DN_UTEST_GROUP(test, "DN_Atomic") { - DN_UTEST_TEST("DN_Atomic_AddU32") { - uint32_t val = 0; - DN_Atomic_AddU32(&val, 1); - DN_UTEST_ASSERTF(&test, val == 1, "val: %u", val); - } - - DN_UTEST_TEST("DN_Atomic_AddU64") { - uint64_t val = 0; - DN_Atomic_AddU64(&val, 1); - DN_UTEST_ASSERTF(&test, val == 1, "val: %" PRIu64, val); - } - - DN_UTEST_TEST("DN_Atomic_SubU32") { - uint32_t val = 1; - DN_Atomic_SubU32(&val, 1); - DN_UTEST_ASSERTF(&test, val == 0, "val: %u", val); - } - - DN_UTEST_TEST("DN_Atomic_SubU64") { - uint64_t val = 1; - DN_Atomic_SubU64(&val, 1); - DN_UTEST_ASSERTF(&test, val == 0, "val: %" PRIu64, val); - } - - DN_UTEST_TEST("DN_Atomic_SetValue32") { - long a = 0; - long b = 111; - DN_Atomic_SetValue32(&a, b); - DN_UTEST_ASSERTF(&test, a == b, "a: %ld, b: %ld", a, b); - } - - DN_UTEST_TEST("DN_Atomic_SetValue64") { - int64_t a = 0; - int64_t b = 111; - DN_Atomic_SetValue64(DN_CAST(uint64_t *)&a, b); - DN_UTEST_ASSERTF(&test, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); - } - - DN_UTest_Begin(&test, "DN_CPU_TSC"); - DN_CPU_TSC(); - DN_UTest_End(&test); - - DN_UTest_Begin(&test, "DN_CompilerReadBarrierAndCPUReadFence"); - DN_CompilerReadBarrierAndCPUReadFence; - DN_UTest_End(&test); - - DN_UTest_Begin(&test, "DN_CompilerWriteBarrierAndCPUWriteFence"); - DN_CompilerWriteBarrierAndCPUWriteFence; - DN_UTest_End(&test); - } - DN_MSVC_WARNING_POP - - return test; -} - -#if defined(DN_UNIT_TESTS_WITH_KECCAK) -DN_GCC_WARNING_PUSH -DN_GCC_WARNING_DISABLE(-Wunused-parameter) -DN_GCC_WARNING_DISABLE(-Wsign-compare) - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4244) -DN_MSVC_WARNING_DISABLE(4100) -DN_MSVC_WARNING_DISABLE(6385) -// NOTE: Keccak Reference Implementation /////////////////////////////////////////////////////////// -// A very compact Keccak implementation taken from the reference implementation -// repository -// -// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c - -#define FOR(i,n) for(i=0; i>1; } -#define ROL(a,o) ((((uint64_t)a)<>(64-o))) - -static uint64_t DN_RefImpl_load64_ (const uint8_t *x) { int i; uint64_t u=0; FOR(i,8) { u<<=8; u|=x[7-i]; } return u; } -static void DN_RefImpl_store64_(uint8_t *x, uint64_t u) { int i; FOR(i,8) { x[i]=u; u>>=8; } } -static void DN_RefImpl_xor64_ (uint8_t *x, uint64_t u) { int i; FOR(i,8) { x[i]^=u; u>>=8; } } - -#define rL(x,y) DN_RefImpl_load64_((uint8_t*)s+8*(x+5*y)) -#define wL(x,y,l) DN_RefImpl_store64_((uint8_t*)s+8*(x+5*y),l) -#define XL(x,y,l) DN_RefImpl_xor64_((uint8_t*)s+8*(x+5*y),l) - -void DN_RefImpl_Keccak_F1600(void *s) -{ - int r,x,y,i,j,Y; uint8_t R=0x01; uint64_t C[5],D; - for(i=0; i<24; i++) { - /*??*/ FOR(x,5) C[x]=rL(x,0)^rL(x,1)^rL(x,2)^rL(x,3)^rL(x,4); FOR(x,5) { D=C[(x+4)%5]^ROL(C[(x+1)%5],1); FOR(y,5) XL(x,y,D); } - /*????*/ x=1; y=r=0; D=rL(x,y); FOR(j,24) { r+=j+1; Y=(2*x+3*y)%5; x=y; y=Y; C[0]=rL(x,y); wL(x,y,ROL(D,r%64)); D=C[0]; } - /*??*/ FOR(y,5) { FOR(x,5) C[x]=rL(x,y); FOR(x,5) wL(x,y,C[x]^((~C[(x+1)%5])&C[(x+2)%5])); } - /*??*/ FOR(j,7) if (DN_RefImpl_LFSR86540_(&R)) XL(0,0,(uint64_t)1<<((1<0) { b=(inLen0) { b=(outLen0) DN_RefImpl_Keccak_F1600(s); } -} - -#undef XL -#undef wL -#undef rL -#undef ROL -#undef FOR -DN_MSVC_WARNING_POP -DN_GCC_WARNING_POP - -#define DN_KECCAK_IMPLEMENTATION -#include "Standalone/dqn_keccak.h" - -#define DN_UTEST_HASH_X_MACRO \ - DN_UTEST_HASH_X_ENTRY(SHA3_224, "SHA3-224") \ - DN_UTEST_HASH_X_ENTRY(SHA3_256, "SHA3-256") \ - DN_UTEST_HASH_X_ENTRY(SHA3_384, "SHA3-384") \ - DN_UTEST_HASH_X_ENTRY(SHA3_512, "SHA3-512") \ - DN_UTEST_HASH_X_ENTRY(Keccak_224, "Keccak-224") \ - DN_UTEST_HASH_X_ENTRY(Keccak_256, "Keccak-256") \ - DN_UTEST_HASH_X_ENTRY(Keccak_384, "Keccak-384") \ - DN_UTEST_HASH_X_ENTRY(Keccak_512, "Keccak-512") \ - DN_UTEST_HASH_X_ENTRY(Count, "Keccak-512") - -enum DN_Tests__HashType -{ -#define DN_UTEST_HASH_X_ENTRY(enum_val, string) Hash_##enum_val, - DN_UTEST_HASH_X_MACRO -#undef DN_UTEST_HASH_X_ENTRY -}; - -DN_Str8 const DN_UTEST_HASH_STRING_[] = -{ -#define DN_UTEST_HASH_X_ENTRY(enum_val, string) DN_STR8(string), - DN_UTEST_HASH_X_MACRO -#undef DN_UTEST_HASH_X_ENTRY -}; - -void DN_Test_KeccakDispatch_(DN_UTest *test, int hash_type, DN_Str8 input) -{ - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 input_hex = DN_BytesToHex(tmem.arena, input.data, input.size); - - switch(hash_type) - { - case Hash_SHA3_224: - { - DN_KeccakBytes28 hash = DN_SHA3_224StringToBytes28(input); - DN_KeccakBytes28 expect; - DN_RefImpl_FIPS202_SHA3_224_(DN_CAST(uint8_t *)input.data, input.size, (uint8_t *)expect.data); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes28Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING56_FMT(DN_KeccakBytes28ToHex(&hash).data), - DN_KECCAK_STRING56_FMT(DN_KeccakBytes28ToHex(&expect).data)); - } - break; - - case Hash_SHA3_256: - { - DN_KeccakBytes32 hash = DN_SHA3_256StringToBytes32(input); - DN_KeccakBytes32 expect; - DN_RefImpl_FIPS202_SHA3_256_(DN_CAST(uint8_t *)input.data, input.size, (uint8_t *)expect.data); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes32Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING64_FMT(DN_KeccakBytes32ToHex(&hash).data), - DN_KECCAK_STRING64_FMT(DN_KeccakBytes32ToHex(&expect).data)); - } - break; - - case Hash_SHA3_384: - { - DN_KeccakBytes48 hash = DN_SHA3_384StringToBytes48(input); - DN_KeccakBytes48 expect; - DN_RefImpl_FIPS202_SHA3_384_(DN_CAST(uint8_t *)input.data, input.size, (uint8_t *)expect.data); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes48Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING96_FMT(DN_KeccakBytes48ToHex(&hash).data), - DN_KECCAK_STRING96_FMT(DN_KeccakBytes48ToHex(&expect).data)); - } - break; - - case Hash_SHA3_512: - { - DN_KeccakBytes64 hash = DN_SHA3_512StringToBytes64(input); - DN_KeccakBytes64 expect; - DN_RefImpl_FIPS202_SHA3_512_(DN_CAST(uint8_t *)input.data, input.size, (uint8_t *)expect.data); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes64Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING128_FMT(DN_KeccakBytes64ToHex(&hash).data), - DN_KECCAK_STRING128_FMT(DN_KeccakBytes64ToHex(&expect).data)); - } - break; - - case Hash_Keccak_224: - { - DN_KeccakBytes28 hash = DN_Keccak224StringToBytes28(input); - DN_KeccakBytes28 expect; - DN_RefImpl_Keccak_(1152, 448, DN_CAST(uint8_t *)input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes28Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING56_FMT(DN_KeccakBytes28ToHex(&hash).data), - DN_KECCAK_STRING56_FMT(DN_KeccakBytes28ToHex(&expect).data)); - } - break; - - case Hash_Keccak_256: - { - DN_KeccakBytes32 hash = DN_Keccak256StringToBytes32(input); - DN_KeccakBytes32 expect; - DN_RefImpl_Keccak_(1088, 512, DN_CAST(uint8_t *)input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes32Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING64_FMT(DN_KeccakBytes32ToHex(&hash).data), - DN_KECCAK_STRING64_FMT(DN_KeccakBytes32ToHex(&expect).data)); - } - break; - - case Hash_Keccak_384: - { - DN_KeccakBytes48 hash = DN_Keccak384StringToBytes48(input); - DN_KeccakBytes48 expect; - DN_RefImpl_Keccak_(832, 768, DN_CAST(uint8_t *)input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes48Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING96_FMT(DN_KeccakBytes48ToHex(&hash).data), - DN_KECCAK_STRING96_FMT(DN_KeccakBytes48ToHex(&expect).data)); - } - break; - - case Hash_Keccak_512: - { - DN_KeccakBytes64 hash = DN_Keccak512StringToBytes64(input); - DN_KeccakBytes64 expect; - DN_RefImpl_Keccak_(576, 1024, DN_CAST(uint8_t *)input.data, input.size, 0x01, (uint8_t *)expect.data, sizeof(expect)); - DN_UTEST_ASSERTF(test, - DN_KeccakBytes64Equals(&hash, &expect), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s" - , - DN_STR_FMT(input_hex), - DN_KECCAK_STRING128_FMT(DN_KeccakBytes64ToHex(&hash).data), - DN_KECCAK_STRING128_FMT(DN_KeccakBytes64ToHex(&expect).data)); - } - break; - - } -} - -DN_UTest DN_Test_Keccak() -{ - DN_UTest test = {}; - DN_Str8 const INPUTS[] = { - DN_STR8("abc"), - DN_STR8(""), - DN_STR8("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), - DN_STR8("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno" - "pqrstnopqrstu"), - }; - - DN_UTEST_GROUP(test, "DN_Keccak") - { - for (int hash_type = 0; hash_type < Hash_Count; hash_type++) { - DN_PCG32 rng = DN_PCG32_Init(0xd48e'be21'2af8'733d); - for (DN_Str8 input : INPUTS) { - DN_UTest_Begin(&test, "%.*s - Input: %.*s", DN_STR_FMT(DN_UTEST_HASH_STRING_[hash_type]), DN_CAST(int)DN_MIN(input.size, 54), input.data); - DN_Test_KeccakDispatch_(&test, hash_type, input); - DN_UTest_End(&test); - } - - DN_UTest_Begin(&test, "%.*s - Deterministic random inputs", DN_STR_FMT(DN_UTEST_HASH_STRING_[hash_type])); - for (DN_USize index = 0; index < 128; index++) { - char src[4096] = {}; - uint32_t src_size = DN_PCG32_Range(&rng, 0, sizeof(src)); - - for (DN_USize src_index = 0; src_index < src_size; src_index++) - src[src_index] = DN_CAST(char)DN_PCG32_Range(&rng, 0, 255); - - DN_Str8 input = DN_Str8_Init(src, src_size); - DN_Test_KeccakDispatch_(&test, hash_type, input); - } - DN_UTest_End(&test); - } - } - return test; -} -#endif // defined(DN_UNIT_TESTS_WITH_KECCAK) - -static DN_UTest DN_Test_M4() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_M4") { - DN_UTEST_TEST("Simple translate and scale matrix") { - DN_M4 translate = DN_M4_TranslateF(1, 2, 3); - DN_M4 scale = DN_M4_ScaleF(2, 2, 2); - DN_M4 result = DN_M4_Mul(translate, scale); - - const DN_M4 EXPECT = {{ - {2, 0, 0, 0}, - {0, 2, 0, 0}, - {0, 0, 2, 0}, - {1, 2, 3, 1}, - }}; - - DN_UTEST_ASSERTF(&test, - memcmp(result.columns, EXPECT.columns, sizeof(EXPECT)) == 0, - "\nresult =\n%s\nexpected =\n%s", - DN_M4_ColumnMajorString(result).data, - DN_M4_ColumnMajorString(EXPECT).data); - } - } - return test; -} - -static DN_UTest DN_Test_OS() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_OS") { - DN_UTEST_TEST("Generate secure RNG bytes with nullptr") { - DN_B32 result = DN_OS_SecureRNGBytes(nullptr, 1); - DN_UTEST_ASSERT(&test, result == false); - } - - DN_UTEST_TEST("Generate secure RNG 32 bytes") { - char const ZERO[32] = {}; - char buf[32] = {}; - bool result = DN_OS_SecureRNGBytes(buf, DN_ARRAY_UCOUNT(buf)); - DN_UTEST_ASSERT(&test, result); - DN_UTEST_ASSERT(&test, DN_MEMCMP(buf, ZERO, DN_ARRAY_UCOUNT(buf)) != 0); - } - - DN_UTEST_TEST("Generate secure RNG 0 bytes") { - char buf[32] = {}; - buf[0] = 'Z'; - DN_B32 result = DN_OS_SecureRNGBytes(buf, 0); - DN_UTEST_ASSERT(&test, result); - DN_UTEST_ASSERT(&test, buf[0] == 'Z'); - } - - DN_UTEST_TEST("Query executable directory") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 result = DN_OS_EXEDir(tmem.arena); - DN_UTEST_ASSERT(&test, DN_Str8_HasData(result)); - DN_UTEST_ASSERTF(&test, DN_OS_DirExists(result), "result(%zu): %.*s", result.size, DN_STR_FMT(result)); - } - - DN_UTEST_TEST("DN_OS_PerfCounterNow") { - uint64_t result = DN_OS_PerfCounterNow(); - DN_UTEST_ASSERT(&test, result != 0); - } - - DN_UTEST_TEST("Consecutive ticks are ordered") { - uint64_t a = DN_OS_PerfCounterNow(); - uint64_t b = DN_OS_PerfCounterNow(); - DN_UTEST_ASSERTF(&test, b >= a, "a: %" PRIu64 ", b: %" PRIu64, a, b); - } - - DN_UTEST_TEST("Ticks to time are a correct order of magnitude") { - uint64_t a = DN_OS_PerfCounterNow(); - uint64_t b = DN_OS_PerfCounterNow(); - DN_F64 s = DN_OS_PerfCounterS(a, b); - DN_F64 ms = DN_OS_PerfCounterMs(a, b); - DN_F64 us = DN_OS_PerfCounterUs(a, b); - DN_F64 ns = DN_OS_PerfCounterNs(a, b); - DN_UTEST_ASSERTF(&test, s <= ms, "s: %f, ms: %f", s, ms); - DN_UTEST_ASSERTF(&test, ms <= us, "ms: %f, us: %f", ms, us); - DN_UTEST_ASSERTF(&test, us <= ns, "us: %f, ns: %f", us, ns); - } - } - - return test; -} - -static DN_UTest DN_Test_Rect() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_Rect") { - DN_UTEST_TEST("No intersection") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init1N(0), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(200, 0), DN_V2F32_Init2N(200, 200)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 0 && ab_max.y == 0, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("A's min intersects B") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(50, 50), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("B's min intersects A") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(50, 50), DN_V2F32_Init2N(100, 100)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 50 && ab.pos.y == 50 && ab_max.x == 100 && ab_max.y == 100, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("A's max intersects B") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(-50, -50), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("B's max intersects A") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(-50, -50), DN_V2F32_Init2N(100, 100)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 50 && ab_max.y == 50, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - - DN_UTEST_TEST("B contains A") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(25, 25), DN_V2F32_Init2N( 25, 25)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("A contains B") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N( 0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect b = DN_Rect_Init2V2(DN_V2F32_Init2N(25, 25), DN_V2F32_Init2N( 25, 25)); - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 25 && ab.pos.y == 25 && ab_max.x == 50 && ab_max.y == 50, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - - DN_UTEST_TEST("A equals B") { - DN_Rect a = DN_Rect_Init2V2(DN_V2F32_Init2N(0, 0), DN_V2F32_Init2N(100, 100)); - DN_Rect b = a; - DN_Rect ab = DN_Rect_Intersection(a, b); - - DN_V2F32 ab_max = ab.pos + ab.size; - DN_UTEST_ASSERTF(&test, - ab.pos.x == 0 && ab.pos.y == 0 && ab_max.x == 100 && ab_max.y == 100, - "ab = { min.x = %.2f, min.y = %.2f, max.x = %.2f. max.y = %.2f }", - ab.pos.x, - ab.pos.y, - ab_max.x, - ab_max.y); - } - } - return test; -} - -static DN_UTest DN_Test_Str8() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_Str8") { - DN_UTEST_TEST("Initialise with string literal w/ macro") { - DN_Str8 string = DN_STR8("AB"); - DN_UTEST_ASSERTF(&test, string.size == 2, "size: %zu", string.size); - DN_UTEST_ASSERTF(&test, string.data[0] == 'A', "string[0]: %c", string.data[0]); - DN_UTEST_ASSERTF(&test, string.data[1] == 'B', "string[1]: %c", string.data[1]); - } - - DN_UTEST_TEST("Initialise with format string") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_Str8_InitF(tmem.arena, "%s", "AB"); - DN_UTEST_ASSERTF(&test, string.size == 2, "size: %zu", string.size); - DN_UTEST_ASSERTF(&test, string.data[0] == 'A', "string[0]: %c", string.data[0]); - DN_UTEST_ASSERTF(&test, string.data[1] == 'B', "string[1]: %c", string.data[1]); - DN_UTEST_ASSERTF(&test, string.data[2] == 0, "string[2]: %c", string.data[2]); - } - - DN_UTEST_TEST("Copy string") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_STR8("AB"); - DN_Str8 copy = DN_Str8_Copy(tmem.arena, string); - DN_UTEST_ASSERTF(&test, copy.size == 2, "size: %zu", copy.size); - DN_UTEST_ASSERTF(&test, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]); - DN_UTEST_ASSERTF(&test, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]); - DN_UTEST_ASSERTF(&test, copy.data[2] == 0, "copy[2]: %c", copy.data[2]); - } - - DN_UTEST_TEST("Trim whitespace around string") { - DN_Str8 string = DN_Str8_TrimWhitespaceAround(DN_STR8(" AB ")); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(string, DN_STR8("AB")), "[string=%.*s]", DN_STR_FMT(string)); - } - - DN_UTEST_TEST("Allocate string from arena") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 string = DN_Str8_Alloc(tmem.arena, 2, DN_ZeroMem_No); - DN_UTEST_ASSERTF(&test, string.size == 2, "size: %zu", string.size); - } - - // NOTE: DN_CStr8_Trim[Prefix/Suffix] - // --------------------------------------------------------------------------------------------- - DN_UTEST_TEST("Trim prefix with matching prefix") { - DN_Str8 input = DN_STR8("nft/abc"); - DN_Str8 result = DN_Str8_TrimPrefix(input, DN_STR8("nft/")); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(result, DN_STR8("abc")), "%.*s", DN_STR_FMT(result)); - } - - DN_UTEST_TEST("Trim prefix with non matching prefix") { - DN_Str8 input = DN_STR8("nft/abc"); - DN_Str8 result = DN_Str8_TrimPrefix(input, DN_STR8(" ft/")); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(result, input), "%.*s", DN_STR_FMT(result)); - } - - DN_UTEST_TEST("Trim suffix with matching suffix") { - DN_Str8 input = DN_STR8("nft/abc"); - DN_Str8 result = DN_Str8_TrimSuffix(input, DN_STR8("abc")); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(result, DN_STR8("nft/")), "%.*s", DN_STR_FMT(result)); - } - - DN_UTEST_TEST("Trim suffix with non matching suffix") { - DN_Str8 input = DN_STR8("nft/abc"); - DN_Str8 result = DN_Str8_TrimSuffix(input, DN_STR8("ab")); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(result, input), "%.*s", DN_STR_FMT(result)); - } - - // NOTE: DN_Str8_IsAllDigits ////////////////////////////////////////////////////////////// - DN_UTEST_TEST("Is all digits fails on non-digit string") { - DN_B32 result = DN_Str8_IsAll(DN_STR8("@123string"), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, result == false); - } - - DN_UTEST_TEST("Is all digits fails on nullptr") { - DN_B32 result = DN_Str8_IsAll(DN_Str8_Init(nullptr, 0), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, result == false); - } - - DN_UTEST_TEST("Is all digits fails on nullptr w/ size") { - DN_B32 result = DN_Str8_IsAll(DN_Str8_Init(nullptr, 1), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, result == false); - } - - DN_UTEST_TEST("Is all digits fails on string w/ 0 size") { - char const buf[] = "@123string"; - DN_B32 result = DN_Str8_IsAll(DN_Str8_Init(buf, 0), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, !result); - } - - DN_UTEST_TEST("Is all digits success") { - DN_B32 result = DN_Str8_IsAll(DN_STR8("23"), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, DN_CAST(bool)result == true); - } - - DN_UTEST_TEST("Is all digits fails on whitespace") { - DN_B32 result = DN_Str8_IsAll(DN_STR8("23 "), DN_Str8IsAll_Digits); - DN_UTEST_ASSERT(&test, DN_CAST(bool)result == false); - } - - // NOTE: DN_Str8_BinarySplit - // --------------------------------------------------------------------------------------------- - { - { - char const *TEST_FMT = "Binary split \"%.*s\" with \"%.*s\""; - DN_Str8 delimiter = DN_STR8("/"); - DN_Str8 input = DN_STR8("abcdef"); - DN_UTEST_TEST(TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) { - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.lhs, DN_STR8("abcdef")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.rhs, DN_STR8("")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); - } - - input = DN_STR8("abc/def"); - DN_UTEST_TEST(TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) { - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.lhs, DN_STR8("abc")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.rhs, DN_STR8("def")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); - } - - input = DN_STR8("/abcdef"); - DN_UTEST_TEST(TEST_FMT, DN_STR_FMT(input), DN_STR_FMT(delimiter)) { - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.lhs, DN_STR8("")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.rhs, DN_STR8("abcdef")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); - } - } - - { - DN_Str8 delimiter = DN_STR8("-=-"); - DN_Str8 input = DN_STR8("123-=-456"); - DN_UTEST_TEST("Binary split \"%.*s\" with \"%.*s\"", DN_STR_FMT(input), DN_STR_FMT(delimiter)) { - DN_Str8BinarySplitResult split = DN_Str8_BinarySplit(input, delimiter); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.lhs, DN_STR8("123")), "[lhs=%.*s]", DN_STR_FMT(split.lhs)); - DN_UTEST_ASSERTF(&test, DN_Str8_Eq(split.rhs, DN_STR8("456")), "[rhs=%.*s]", DN_STR_FMT(split.rhs)); - } - } - } - - // NOTE: DN_Str8_ToI64 //////////////////////////////////////////////////////////////////// - DN_UTEST_TEST("To I64: Convert null string") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_Str8_Init(nullptr, 5), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 0); - } - - DN_UTEST_TEST("To I64: Convert empty string") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8(""), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 0); - } - - DN_UTEST_TEST("To I64: Convert \"1\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("1"), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 1); - } - - DN_UTEST_TEST("To I64: Convert \"-0\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-0"), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 0); - } - - DN_UTEST_TEST("To I64: Convert \"-1\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("-1"), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == -1); - } - - DN_UTEST_TEST("To I64: Convert \"1.2\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("1.2"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERT(&test, result.value == 1); - } - - DN_UTEST_TEST("To I64: Convert \"1,234\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("1,234"), ','); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 1234); - } - - DN_UTEST_TEST("To I64: Convert \"1,2\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("1,2"), ','); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERT(&test, result.value == 12); - } - - DN_UTEST_TEST("To I64: Convert \"12a3\"") { - DN_Str8ToI64Result result = DN_Str8_ToI64(DN_STR8("12a3"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERT(&test, result.value == 12); - } - - // NOTE: DN_Str8_ToU64 - // --------------------------------------------------------------------------------------------- - DN_UTEST_TEST("To U64: Convert nullptr") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_Str8_Init(nullptr, 5), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERTF(&test, result.value == 0, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert empty string") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8(""), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERTF(&test, result.value == 0, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"1\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("1"), 0); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERTF(&test, result.value == 1, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"-0\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("-0"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERTF(&test, result.value == 0, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"-1\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("-1"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERTF(&test, result.value == 0, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"1.2\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("1.2"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERTF(&test, result.value == 1, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"1,234\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("1,234"), ','); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERTF(&test, result.value == 1234, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"1,2\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("1,2"), ','); - DN_UTEST_ASSERT(&test, result.success); - DN_UTEST_ASSERTF(&test, result.value == 12, "result: %" PRIu64, result.value); - } - - DN_UTEST_TEST("To U64: Convert \"12a3\"") { - DN_Str8ToU64Result result = DN_Str8_ToU64(DN_STR8("12a3"), 0); - DN_UTEST_ASSERT(&test, !result.success); - DN_UTEST_ASSERTF(&test, result.value == 12, "result: %" PRIu64, result.value); - } - - // NOTE: DN_Str8_Find ///////////////////////////////////////////////////////////////////// - DN_UTEST_TEST("Find: String (char) is not in buffer") { - DN_Str8 buf = DN_STR8("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_STR8("2"); - DN_Str8FindResult result = DN_Str8_FindStr8(buf, find, DN_Str8EqCase_Sensitive); - DN_UTEST_ASSERT(&test, !result.found); - DN_UTEST_ASSERT(&test, result.index == 0); - DN_UTEST_ASSERT(&test, result.match.data == nullptr); - DN_UTEST_ASSERT(&test, result.match.size == 0); - } - - DN_UTEST_TEST("Find: String (char) is in buffer") { - DN_Str8 buf = DN_STR8("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_STR8("6"); - DN_Str8FindResult result = DN_Str8_FindStr8(buf, find, DN_Str8EqCase_Sensitive); - DN_UTEST_ASSERT(&test, result.found); - DN_UTEST_ASSERT(&test, result.index == 2); - DN_UTEST_ASSERT(&test, result.match.data[0] == '6'); - } - - // NOTE: DN_Str8_FileNameFromPath ///////////////////////////////////////////////////////// - DN_UTEST_TEST("File name from Windows path") { - DN_Str8 buf = DN_STR8("C:\\ABC\\test.exe"); - DN_Str8 result = DN_Str8_FileNameFromPath(buf); - DN_UTEST_ASSERTF(&test, result == DN_STR8("test.exe"), "%.*s", DN_STR_FMT(result)); - } - - DN_UTEST_TEST("File name from Linux path") { - DN_Str8 buf = DN_STR8("/ABC/test.exe"); - DN_Str8 result = DN_Str8_FileNameFromPath(buf); - DN_UTEST_ASSERTF(&test, result == DN_STR8("test.exe"), "%.*s", DN_STR_FMT(result)); - } - - // NOTE: DN_Str8_TrimPrefix - // ========================================================================================= - DN_UTEST_TEST("Trim prefix") { - DN_Str8 prefix = DN_STR8("@123"); - DN_Str8 buf = DN_STR8("@123string"); - DN_Str8 result = DN_Str8_TrimPrefix(buf, prefix, DN_Str8EqCase_Sensitive); - DN_UTEST_ASSERT(&test, result == DN_STR8("string")); - } - } - return test; -} - -static DN_UTest DN_Test_TicketMutex() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_TicketMutex") { - DN_UTEST_TEST("Ticket mutex start and stop") { - // TODO: We don't have a meaningful test but since atomics are - // implemented with a macro this ensures that we test that they are - // written correctly. - DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); - DN_TicketMutex_End(&mutex); - DN_UTEST_ASSERT(&test, mutex.ticket == mutex.serving); - } - - DN_UTEST_TEST("Ticket mutex start and stop w/ advanced API") { - DN_TicketMutex mutex = {}; - unsigned int ticket_a = DN_TicketMutex_MakeTicket(&mutex); - unsigned int ticket_b = DN_TicketMutex_MakeTicket(&mutex); - DN_UTEST_ASSERT(&test, DN_CAST(bool)DN_TicketMutex_CanLock(&mutex, ticket_b) == false); - DN_UTEST_ASSERT(&test, DN_CAST(bool)DN_TicketMutex_CanLock(&mutex, ticket_a) == true); - - DN_TicketMutex_BeginTicket(&mutex, ticket_a); - DN_TicketMutex_End(&mutex); - DN_TicketMutex_BeginTicket(&mutex, ticket_b); - DN_TicketMutex_End(&mutex); - - DN_UTEST_ASSERT(&test, mutex.ticket == mutex.serving); - DN_UTEST_ASSERT(&test, mutex.ticket == ticket_b + 1); - } - } - return test; -} - -static DN_UTest DN_Test_VArray() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "DN_VArray") { - { - DN_VArray array = DN_VArray_InitByteSize(DN_KILOBYTES(64)); - DN_DEFER { - DN_VArray_Deinit(&array); - }; - - DN_UTEST_TEST("Test adding an array of items to the array") { - uint32_t array_literal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - DN_VArray_AddArray(&array, array_literal, DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test stable erase, 1 item, the '2' value from the array") { - DN_VArray_EraseRange(&array, 2 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Stable); - uint32_t array_literal[] = {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test unstable erase, 1 item, the '1' value from the array") { - DN_VArray_EraseRange(&array, 1 /*begin_index*/, 1 /*count*/, DN_ArrayErase_Unstable); - uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_ArrayErase erase_enums[] = {DN_ArrayErase_Stable, DN_ArrayErase_Unstable}; - DN_UTEST_TEST("Test un/stable erase, OOB") { - for (DN_ArrayErase erase : erase_enums) { - uint32_t array_literal[] = {0, 15, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; - DN_VArray_EraseRange(&array, DN_ARRAY_UCOUNT(array_literal) /*begin_index*/, DN_ARRAY_UCOUNT(array_literal) + 100 /*count*/, erase); - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - } - - DN_UTEST_TEST("Test flipped begin/end index stable erase, 2 items, the '15, 3' value from the array") { - DN_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Stable); - uint32_t array_literal[] = {0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test flipped begin/end index unstable erase, 2 items, the '4, 5' value from the array") { - DN_VArray_EraseRange(&array, 2 /*begin_index*/, -2 /*count*/, DN_ArrayErase_Unstable); - uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10, 11, 12}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test stable erase range, 2+1 (oob) item, the '13, 14, +1 OOB' value from the array") { - DN_VArray_EraseRange(&array, 8 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Stable); - uint32_t array_literal[] = {0, 13, 14, 6, 7, 8, 9, 10}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test unstable erase range, 3+1 (oob) item, the '11, 12, +1 OOB' value from the array") { - DN_VArray_EraseRange(&array, 6 /*begin_index*/, 3 /*count*/, DN_ArrayErase_Unstable); - uint32_t array_literal[] = {0, 13, 14, 6, 7, 8}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test stable erase -overflow OOB, erasing the '0, 13' value from the array") { - DN_VArray_EraseRange(&array, 1 /*begin_index*/, -DN_ISIZE_MAX /*count*/, DN_ArrayErase_Stable); - uint32_t array_literal[] = {14, 6, 7, 8}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test unstable erase +overflow OOB, erasing the '7, 8' value from the array") { - DN_VArray_EraseRange(&array, 2 /*begin_index*/, DN_ISIZE_MAX /*count*/, DN_ArrayErase_Unstable); - uint32_t array_literal[] = {14, 6}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(array_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, array_literal, DN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); - } - - DN_UTEST_TEST("Test adding an array of items after erase") { - uint32_t array_literal[] = {0, 1, 2, 3}; - DN_VArray_AddArray(&array, array_literal, DN_ARRAY_UCOUNT(array_literal)); - - uint32_t expected_literal[] = {14, 6, 0, 1, 2, 3}; - DN_UTEST_ASSERT(&test, array.size == DN_ARRAY_UCOUNT(expected_literal)); - DN_UTEST_ASSERT(&test, DN_MEMCMP(array.data, expected_literal, DN_ARRAY_UCOUNT(expected_literal) * sizeof(expected_literal[0])) == 0); - } - } - - DN_UTEST_TEST("Array of unaligned objects are contiguously laid out in memory") { - // NOTE: Since we allocate from a virtual memory block, each time - // we request memory from the block we can demand some alignment - // on the returned pointer from the memory block. If there's - // additional alignment done in that function then we can no - // longer access the items in the array contiguously leading to - // confusing memory "corruption" errors. - // - // This test makes sure that the unaligned objects are allocated - // from the memory block (and hence the array) contiguously - // when the size of the object is not aligned with the required - // alignment of the object. - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4324) // warning C4324: 'TestVArray::UnalignedObject': structure was padded due to alignment specifier - struct alignas(8) UnalignedObject { - char data[511]; - }; - DN_MSVC_WARNING_POP - - DN_VArray array = DN_VArray_InitByteSize(DN_KILOBYTES(64)); - DN_DEFER { - DN_VArray_Deinit(&array); - }; - - // NOTE: Verify that the items returned from the data array are - // contiguous in memory. - UnalignedObject *make_item_a = DN_VArray_MakeArray(&array, 1, DN_ZeroMem_Yes); - UnalignedObject *make_item_b = DN_VArray_MakeArray(&array, 1, DN_ZeroMem_Yes); - DN_MEMSET(make_item_a->data, 'a', sizeof(make_item_a->data)); - DN_MEMSET(make_item_b->data, 'b', sizeof(make_item_b->data)); - DN_UTEST_ASSERT(&test, (uintptr_t)make_item_b == (uintptr_t)(make_item_a + 1)); - - // NOTE: Verify that accessing the items from the data array yield - // the same object. - DN_UTEST_ASSERT(&test, array.size == 2); - UnalignedObject *data_item_a = array.data + 0; - UnalignedObject *data_item_b = array.data + 1; - DN_UTEST_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)(data_item_a + 1)); - DN_UTEST_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)(make_item_a + 1)); - DN_UTEST_ASSERT(&test, (uintptr_t)data_item_b == (uintptr_t)make_item_b); - - for (DN_USize i = 0; i < sizeof(data_item_a->data); i++) { - DN_UTEST_ASSERT(&test, data_item_a->data[i] == 'a'); - } - - for (DN_USize i = 0; i < sizeof(data_item_b->data); i++) { - DN_UTEST_ASSERT(&test, data_item_b->data[i] == 'b'); - } - } - } - return test; -} - -#if defined(DN_PLATFORM_WIN32) -static DN_UTest DN_Test_Win() -{ - DN_UTest test = {}; - DN_UTEST_GROUP(test, "OS Win32") { - DN_TLSTMem tmem = DN_TLS_TMem(nullptr); - DN_Str8 input8 = DN_STR8("String"); - DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; - - DN_UTEST_TEST("Str8 to Str16") { - DN_Str16 result = DN_Win_Str8ToStr16(tmem.arena, input8); - DN_UTEST_ASSERT(&test, result == input16); - } - - DN_UTEST_TEST("Str16 to Str8") { - DN_Str8 result = DN_Win_Str16ToStr8(tmem.arena, input16); - DN_UTEST_ASSERT(&test, result == input8); - } - - DN_UTEST_TEST("Str16 to Str8: Null terminates string") { - int size_required = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); - char *string = DN_Arena_NewArray(tmem.arena, char, size_required + 1, DN_ZeroMem_No); - - // Fill the string with error sentinels - DN_MEMSET(string, 'Z', size_required + 1); - - int size_returned = DN_Win_Str16ToStr8Buffer(input16, string, size_required + 1); - char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; - - DN_UTEST_ASSERTF(&test, size_required == size_returned, "string_size: %d, result: %d", size_required, size_returned); - DN_UTEST_ASSERTF(&test, size_returned == DN_ARRAY_UCOUNT(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DN_ARRAY_UCOUNT(EXPECTED) - 1); - DN_UTEST_ASSERT(&test, DN_MEMCMP(EXPECTED, string, sizeof(EXPECTED)) == 0); - } - - DN_UTEST_TEST("Str16 to Str8: Arena null terminates string") { - DN_Str8 string8 = DN_Win_Str16ToStr8(tmem.arena, input16); - int size_returned = DN_Win_Str16ToStr8Buffer(input16, nullptr, 0); - char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; - - DN_UTEST_ASSERTF(&test, DN_CAST(int)string8.size == size_returned, "string_size: %d, result: %d", DN_CAST(int)string8.size, size_returned); - DN_UTEST_ASSERTF(&test, DN_CAST(int)string8.size == DN_ARRAY_UCOUNT(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_CAST(int)string8.size, DN_ARRAY_UCOUNT(EXPECTED) - 1); - DN_UTEST_ASSERT (&test, DN_MEMCMP(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); - } - } - return test; -} -#endif // DN_PLATFORM_WIN#@ - -void DN_Test_RunSuite() -{ - DN_UTest tests[] = - { - DN_Test_Base(), - DN_Test_Arena(), - DN_Test_Bin(), - DN_Test_BinarySearch(), - DN_Test_DSMap(), - DN_Test_FStr8(), - DN_Test_Fs(), - DN_Test_FixedArray(), - DN_Test_Intrinsics(), - #if defined(DN_UNIT_TESTS_WITH_KECCAK) - DN_Test_Keccak(), - #endif - DN_Test_M4(), - DN_Test_OS(), - DN_Test_Rect(), - DN_Test_Str8(), - DN_Test_TicketMutex(), - DN_Test_VArray(), - #if defined(DN_PLATFORM_WIN32) - DN_Test_Win(), - #endif - }; - - int total_tests = 0; - int total_good_tests = 0; - for (const DN_UTest &test : tests) { - total_tests += test.num_tests_in_group; - total_good_tests += test.num_tests_ok_in_group; - } - - fprintf(stdout, "Summary: %d/%d tests succeeded\n", total_good_tests, total_tests); -} - diff --git a/dqn_unit_tests_main.cpp b/dqn_unit_tests_main.cpp deleted file mode 100644 index d465753..0000000 --- a/dqn_unit_tests_main.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "dqn.h" -#include "dqn.cpp" -#include "dqn_unit_tests.cpp" - -int main(int argc, char *argv[]) -{ - (void)argv; (void)argc; - DN_Core *core = (DN_Core *)DN_OS_MemAlloc(sizeof(DN_Core), DN_ZeroMem_Yes); - DN_Core_Init(core, DN_CoreOnInit_LogAllFeatures); - DN_Test_RunSuite(); - return 0; -} diff --git a/dqn_win32.h b/dqn_win32.h deleted file mode 100644 index fee808e..0000000 --- a/dqn_win32.h +++ /dev/null @@ -1,1318 +0,0 @@ -#pragma once -#include "dqn.h" - -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$\ $$\ $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ -// $$ | $\ $$ |\_$$ _|$$$\ $$ |$$ ___$$\ $$ __$$\ -// $$ |$$$\ $$ | $$ | $$$$\ $$ |\_/ $$ |\__/ $$ | -// $$ $$ $$\$$ | $$ | $$ $$\$$ | $$$$$ / $$$$$$ | -// $$$$ _$$$$ | $$ | $$ \$$$$ | \___$$\ $$ ____/ -// $$$ / \$$$ | $$ | $$ |\$$$ |$$\ $$ |$$ | -// $$ / \$$ |$$$$$$\ $$ | \$$ |\$$$$$$ |$$$$$$$$\ -// \__/ \__|\______|\__| \__| \______/ \________| -// -// dn_win32.h -- Windows replacement header -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -#if defined(DN_COMPILER_MSVC) || defined(DN_COMPILER_CLANG_CL) - #pragma comment(lib, "bcrypt") - #pragma comment(lib, "winhttp") - #pragma comment(lib, "dbghelp") - #pragma comment(lib, "comdlg32") - #pragma comment(lib, "pathcch") - #pragma comment(lib, "shlwapi") -#endif - -#if defined(DN_NO_WIN32_MIN_HEADER) || defined(_INC_WINDOWS) - #define WIN32_LEAN_AND_MEAN - #include // LONG - #include // DN_OS_SecureRNGBytes -> BCryptOpenAlgorithmProvider ... etc - #include // DN_Win_MakeProcessDPIAware -> SetProcessDpiAwareProc - #include // PathRelativePathTO - #include // PathCchCanonicalizeEx - #include // WinHttp* - #include // OPENFILENAMEW - #include -#else - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(4201) // warning C4201: nonstandard extension used: nameless struct/union - - // NOTE: basetsd.h ///////////////////////////////////////////////////////////////////////////// - typedef unsigned __int64 ULONG_PTR, *PULONG_PTR; - typedef unsigned __int64 UINT_PTR, *PUINT_PTR; - typedef ULONG_PTR SIZE_T, *PSIZE_T; - typedef __int64 LONG_PTR, *PLONG_PTR; - typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; - typedef unsigned __int64 ULONG64, *PULONG64; - typedef unsigned __int64 DWORD64, *PDWORD64; - - // NOTE: shared/minwindef.h //////////////////////////////////////////////////////////////////// - struct HINSTANCE__ { - int unused; - }; - typedef struct HINSTANCE__ *HINSTANCE; - - typedef unsigned long DWORD; - typedef int BOOL; - typedef int INT; - typedef unsigned long ULONG; - typedef unsigned int UINT; - typedef unsigned short WORD; - typedef unsigned char BYTE; - typedef unsigned char UCHAR; - typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */ - typedef void * HANDLE; - typedef HANDLE HLOCAL; - - typedef unsigned __int64 WPARAM; - typedef LONG_PTR LPARAM; - typedef LONG_PTR LRESULT; - - #define MAX_PATH 260 - - typedef struct _FILETIME { - DWORD dwLowDateTime; - DWORD dwHighDateTime; - } FILETIME, *PFILETIME, *LPFILETIME; - - // NOTE: shared/winerror.h ///////////////////////////////////////////////////////////////////// - // NOTE: GetModuleFileNameW - #define ERROR_INSUFFICIENT_BUFFER 122L // dderror - - // NOTE: um/winnls.h /////////////////////////////////////////////////////////////////////////// - // NOTE: MultiByteToWideChar - #define CP_UTF8 65001 // UTF-8 translation - - // NOTE: um/winnt.h //////////////////////////////////////////////////////////////////////////// - typedef void VOID; - typedef __int64 LONGLONG; - typedef unsigned __int64 ULONGLONG; - typedef void * HANDLE; - typedef char CHAR; - typedef short SHORT; - typedef long LONG; - typedef wchar_t WCHAR; // wc, 16-bit UNICODE character - typedef CHAR * NPSTR, *LPSTR, *PSTR; - typedef WCHAR * NWPSTR, *LPWSTR, *PWSTR; - typedef long HRESULT; - - // NOTE: VirtualAlloc: Allocation Type - #define MEM_RESERVE 0x00002000 - #define MEM_COMMIT 0x00001000 - #define MEM_DECOMMIT 0x00004000 - #define MEM_RELEASE 0x00008000 - - // NOTE: VirtualAlloc: Page Permissions - #define PAGE_NOACCESS 0x01 - #define PAGE_READONLY 0x02 - #define PAGE_READWRITE 0x04 - #define PAGE_GUARD 0x100 - - // NOTE: HeapAlloc - #define HEAP_ZERO_MEMORY 0x00000008 - #define HEAP_NO_SERIALIZE 0x00000001 - #define HEAP_GROWABLE 0x00000002 - #define HEAP_GENERATE_EXCEPTIONS 0x00000004 - #define HEAP_ZERO_MEMORY 0x00000008 - #define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010 - #define HEAP_TAIL_CHECKING_ENABLED 0x00000020 - #define HEAP_FREE_CHECKING_ENABLED 0x00000040 - #define HEAP_DISABLE_COALESCE_ON_FREE 0x00000080 - #define HEAP_CREATE_ALIGN_16 0x00010000 - #define HEAP_CREATE_ENABLE_TRACING 0x00020000 - #define HEAP_CREATE_ENABLE_EXECUTE 0x00040000 - #define HEAP_MAXIMUM_TAG 0x0FFF - #define HEAP_PSEUDO_TAG_FLAG 0x8000 - #define HEAP_TAG_SHIFT 18 - #define HEAP_CREATE_SEGMENT_HEAP 0x00000100 - #define HEAP_CREATE_HARDENED 0x00000200 - - // NOTE: FormatMessageA - #define MAKELANGID(p, s) ((((WORD )(s)) << 10) | (WORD )(p)) - #define LANG_NEUTRAL 0x00 - #define SUBLANG_DEFAULT 0x01 // user default - - // NOTE: CreateFile - #define GENERIC_READ (0x80000000L) - #define GENERIC_WRITE (0x40000000L) - #define GENERIC_EXECUTE (0x20000000L) - #define GENERIC_ALL (0x10000000L) - - #define FILE_APPEND_DATA (0x0004) // file - - // NOTE: CreateFile/FindFirstFile - #define FILE_SHARE_READ 0x00000001 - #define FILE_SHARE_WRITE 0x00000002 - #define FILE_SHARE_DELETE 0x00000004 - - #define FILE_ATTRIBUTE_READONLY 0x00000001 - #define FILE_ATTRIBUTE_HIDDEN 0x00000002 - #define FILE_ATTRIBUTE_SYSTEM 0x00000004 - #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 - #define FILE_ATTRIBUTE_NORMAL 0x00000080 - - // NOTE: STACKFRAME64 - #define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) - - // NOTE: WaitForSingleObject - #define WAIT_TIMEOUT 258L // dderror - #define STATUS_WAIT_0 ((DWORD )0x00000000L) - #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) - - #define S_OK ((HRESULT)0L) - #define S_FALSE ((HRESULT)1L) - - typedef union _ULARGE_INTEGER { - struct { - DWORD LowPart; - DWORD HighPart; - } DUMMYSTRUCTNAME; - struct { - DWORD LowPart; - DWORD HighPart; - } u; - ULONGLONG QuadPart; - } ULARGE_INTEGER; - - typedef union _LARGE_INTEGER { - struct { - DWORD LowPart; - LONG HighPart; - } DUMMYSTRUCTNAME; - struct { - DWORD LowPart; - LONG HighPart; - } u; - LONGLONG QuadPart; - } LARGE_INTEGER; - - typedef struct __declspec(align(16)) _M128A { - ULONGLONG Low; - LONGLONG High; - } M128A, *PM128A; - - typedef struct __declspec(align(16)) _XSAVE_FORMAT { - WORD ControlWord; - WORD StatusWord; - BYTE TagWord; - BYTE Reserved1; - WORD ErrorOpcode; - DWORD ErrorOffset; - WORD ErrorSelector; - WORD Reserved2; - DWORD DataOffset; - WORD DataSelector; - WORD Reserved3; - DWORD MxCsr; - DWORD MxCsr_Mask; - M128A FloatRegisters[8]; - #if defined(_WIN64) - M128A XmmRegisters[16]; - BYTE Reserved4[96]; - #else - M128A XmmRegisters[8]; - BYTE Reserved4[224]; - #endif - } XSAVE_FORMAT, *PXSAVE_FORMAT; - typedef XSAVE_FORMAT XMM_SAVE_AREA32, *PXMM_SAVE_AREA32; - - typedef struct __declspec(align(16)) _CONTEXT { - DWORD64 P1Home; - DWORD64 P2Home; - DWORD64 P3Home; - DWORD64 P4Home; - DWORD64 P5Home; - DWORD64 P6Home; - DWORD ContextFlags; - DWORD MxCsr; - WORD SegCs; - WORD SegDs; - WORD SegEs; - WORD SegFs; - WORD SegGs; - WORD SegSs; - DWORD EFlags; - DWORD64 Dr0; - DWORD64 Dr1; - DWORD64 Dr2; - DWORD64 Dr3; - DWORD64 Dr6; - DWORD64 Dr7; - DWORD64 Rax; - DWORD64 Rcx; - DWORD64 Rdx; - DWORD64 Rbx; - DWORD64 Rsp; - DWORD64 Rbp; - DWORD64 Rsi; - DWORD64 Rdi; - DWORD64 R8; - DWORD64 R9; - DWORD64 R10; - DWORD64 R11; - DWORD64 R12; - DWORD64 R13; - DWORD64 R14; - DWORD64 R15; - DWORD64 Rip; - - union { - XMM_SAVE_AREA32 FltSave; - - struct { - M128A Header[2]; - M128A Legacy[8]; - M128A Xmm0; - M128A Xmm1; - M128A Xmm2; - M128A Xmm3; - M128A Xmm4; - M128A Xmm5; - M128A Xmm6; - M128A Xmm7; - M128A Xmm8; - M128A Xmm9; - M128A Xmm10; - M128A Xmm11; - M128A Xmm12; - M128A Xmm13; - M128A Xmm14; - M128A Xmm15; - } DUMMYSTRUCTNAME; - } DUMMYUNIONNAME; - - M128A VectorRegister[26]; - DWORD64 VectorControl; - DWORD64 DebugControl; - DWORD64 LastBranchToRip; - DWORD64 LastBranchFromRip; - DWORD64 LastExceptionToRip; - DWORD64 LastExceptionFromRip; - } CONTEXT; - - typedef struct _LIST_ENTRY { - struct _LIST_ENTRY *Flink; - struct _LIST_ENTRY *Blink; - } LIST_ENTRY, *PLIST_ENTRY, PRLIST_ENTRY; - - typedef struct _RTL_CRITICAL_SECTION_DEBUG { - WORD Type; - WORD CreatorBackTraceIndex; - struct _RTL_CRITICAL_SECTION *CriticalSection; - LIST_ENTRY ProcessLocksList; - DWORD EntryCount; - DWORD ContentionCount; - DWORD Flags; - WORD CreatorBackTraceIndexHigh; - WORD Identifier; - } RTL_CRITICAL_SECTION_DEBUG, *PRTL_CRITICAL_SECTION_DEBUG, RTL_RESOURCE_DEBUG, *PRTL_RESOURCE_DEBUG; - - #pragma pack(push, 8) - typedef struct _RTL_CRITICAL_SECTION { - PRTL_CRITICAL_SECTION_DEBUG DebugInfo; - - // - // The following three fields control entering and exiting the critical - // section for the resource - // - - LONG LockCount; - LONG RecursionCount; - HANDLE OwningThread; // from the thread's ClientId->UniqueThread - HANDLE LockSemaphore; - ULONG_PTR SpinCount; // force size on 64-bit systems when packed - } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; - #pragma pack(pop) - - typedef struct _MODLOAD_DATA { - DWORD ssize; // size of this struct - DWORD ssig; // signature identifying the passed data - VOID *data; // pointer to passed data - DWORD size; // size of passed data - DWORD flags; // options - } MODLOAD_DATA, *PMODLOAD_DATA; - - #define SLMFLAG_VIRTUAL 0x1 - #define SLMFLAG_ALT_INDEX 0x2 - #define SLMFLAG_NO_SYMBOLS 0x4 - - extern "C" - { - __declspec(dllimport) VOID __stdcall RtlCaptureContext(CONTEXT *ContextRecord); - __declspec(dllimport) HANDLE __stdcall GetCurrentProcess(void); - __declspec(dllimport) HANDLE __stdcall GetCurrentThread(void); - __declspec(dllimport) DWORD __stdcall SymSetOptions(DWORD SymOptions); - __declspec(dllimport) BOOL __stdcall SymInitialize(HANDLE hProcess, const CHAR* UserSearchPath, BOOL fInvadeProcess); - __declspec(dllimport) DWORD64 __stdcall SymLoadModuleEx(HANDLE hProcess, HANDLE hFile, CHAR const *ImageName, CHAR const *ModuleName, DWORD64 BaseOfDll, DWORD DllSize, MODLOAD_DATA *Data, DWORD Flags); - __declspec(dllimport) BOOL __stdcall SymUnloadModule64(HANDLE hProcess, DWORD64 BaseOfDll); - } - - // NOTE: um/heapapi.h //////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) HANDLE __stdcall HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize); - __declspec(dllimport) BOOL __stdcall HeapDestroy(HANDLE hHeap); - __declspec(dllimport) VOID * __stdcall HeapAlloc(HANDLE hHeap, DWORD dwFlags,SIZE_T dwBytes); - __declspec(dllimport) VOID * __stdcall HeapReAlloc(HANDLE hHeap, DWORD dwFlags, VOID *lpMem, SIZE_T dwBytes); - __declspec(dllimport) BOOL __stdcall HeapFree(HANDLE hHeap, DWORD dwFlags, VOID *lpMem); - __declspec(dllimport) SIZE_T __stdcall HeapSize(HANDLE hHeap, DWORD dwFlags, VOID const *lpMem); - __declspec(dllimport) HANDLE __stdcall GetProcessHeap(VOID); - __declspec(dllimport) SIZE_T __stdcall HeapCompact(HANDLE hHeap, DWORD dwFlags); - } - - // NOTE: shared/windef.h //////////////////////////////////////////////////////////////////// - typedef struct tagPOINT - { - LONG x; - LONG y; - } POINT, *PPOINT, *NPPOINT, *LPPOINT; - - // NOTE: handleapi.h /////////////////////////////////////////////////////////////////////////// - #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) - - extern "C" - { - __declspec(dllimport) BOOL __stdcall CloseHandle(HANDLE hObject); - } - - // NOTE: consoleapi.h /////////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall WriteConsoleA(HANDLE hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, DWORD *lpNumberOfCharsWritten, VOID *lpReserved); - __declspec(dllimport) BOOL __stdcall AllocConsole(VOID); - __declspec(dllimport) BOOL __stdcall FreeConsole(VOID); - __declspec(dllimport) BOOL __stdcall AttachConsole(DWORD dwProcessId); - __declspec(dllimport) BOOL __stdcall GetConsoleMode(HANDLE hConsoleHandle, DWORD *lpMode); - } - - // NOTE: um/minwinbase.h /////////////////////////////////////////////////////////////////////// - // NOTE: FindFirstFile - #define FIND_FIRST_EX_CASE_SENSITIVE 0x00000001 - #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 - - // NOTE: WaitFor.. - #define WAIT_FAILED ((DWORD)0xFFFFFFFF) - #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) - #define WAIT_ABANDONED ((STATUS_ABANDONED_WAIT_0 ) + 0 ) - #define WAIT_ABANDONED_0 ((STATUS_ABANDONED_WAIT_0 ) + 0 ) - - // NOTE: CreateProcessW - #define CREATE_UNICODE_ENVIRONMENT 0x00000400 - #define CREATE_NO_WINDOW 0x08000000 - - typedef enum _GET_FILEEX_INFO_LEVELS { - GetFileExInfoStandard, - GetFileExMaxInfoLevel - } GET_FILEEX_INFO_LEVELS; - - typedef struct _SECURITY_ATTRIBUTES { - DWORD nLength; - VOID *lpSecurityDescriptor; - BOOL bInheritHandle; - } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; - - typedef enum _FINDEX_INFO_LEVELS { - FindExInfoStandard, - FindExInfoBasic, - FindExInfoMaxInfoLevel - } FINDEX_INFO_LEVELS; - - typedef enum _FINDEX_SEARCH_OPS { - FindExSearchNameMatch, - FindExSearchLimitToDirectories, - FindExSearchLimitToDevices, - FindExSearchMaxSearchOp - } FINDEX_SEARCH_OPS; - - typedef struct _WIN32_FIND_DATAW { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - DWORD dwReserved0; - DWORD dwReserved1; - WCHAR cFileName[ MAX_PATH ]; - WCHAR cAlternateFileName[ 14 ]; - #ifdef _MAC - DWORD dwFileType; - DWORD dwCreatorType; - WORD wFinderFlags; - #endif - } WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW; - - typedef struct _SYSTEMTIME { - WORD wYear; - WORD wMonth; - WORD wDayOfWeek; - WORD wDay; - WORD wHour; - WORD wMinute; - WORD wSecond; - WORD wMilliseconds; - } SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME; - - typedef struct _OVERLAPPED { - ULONG_PTR Internal; - ULONG_PTR InternalHigh; - union { - struct { - DWORD Offset; - DWORD OffsetHigh; - } DUMMYSTRUCTNAME; - VOID *Pointer; - } DUMMYUNIONNAME; - - HANDLE hEvent; - } OVERLAPPED, *LPOVERLAPPED; - - typedef RTL_CRITICAL_SECTION CRITICAL_SECTION; - - #define WAIT_FAILED ((DWORD)0xFFFFFFFF) - #define WAIT_OBJECT_0 ((STATUS_WAIT_0 ) + 0 ) - - #define INFINITE 0xFFFFFFFF // Wait/Synchronisation: Infinite timeout - - #define STD_INPUT_HANDLE ((DWORD)-10) - #define STD_OUTPUT_HANDLE ((DWORD)-11) - #define STD_ERROR_HANDLE ((DWORD)-12) - - #define HANDLE_FLAG_INHERIT 0x00000001 - #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 - - // NOTE: MoveFile - #define MOVEFILE_REPLACE_EXISTING 0x00000001 - #define MOVEFILE_COPY_ALLOWED 0x00000002 - - // NOTE: FormatMessageA - #define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100 - #define FORMAT_MESSAGE_IGNORE_INSERTS 0x00000200 - #define FORMAT_MESSAGE_FROM_HMODULE 0x00000800 - #define FORMAT_MESSAGE_FROM_SYSTEM 0x00001000 - - // NOTE: CreateProcessW - #define STARTF_USESTDHANDLES 0x00000100 - - extern "C" - { - __declspec(dllimport) BOOL __stdcall MoveFileExW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, DWORD dwFlags); - __declspec(dllimport) BOOL __stdcall CopyFileW (const WCHAR *lpExistingFileName, const WCHAR *lpNewFileName, BOOL bFailIfExists); - __declspec(dllimport) HANDLE __stdcall CreateSemaphoreA(SECURITY_ATTRIBUTES *lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, const CHAR *lpName); - __declspec(dllimport) DWORD __stdcall FormatMessageW (DWORD dwFlags, VOID const *lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPWSTR lpBuffer, DWORD nSize, va_list *Arguments); - __declspec(dllimport) HLOCAL __stdcall LocalFree (HLOCAL hMem); - } - - // NOTE: um/stringapiset.h ///////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) int __stdcall MultiByteToWideChar(UINT CodePage, DWORD dwFlags, const CHAR *lpMultiByteStr, int cbMultiByte, WCHAR *lpWideCharStr, int cchWideChar); - __declspec(dllimport) int __stdcall WideCharToMultiByte(UINT CodePage, DWORD dwFlags, const WCHAR *lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, const CHAR *lpDefaultChar, BOOL *lpUsedDefaultChar); - } - - // NOTE: um/fileapi.h ////////////////////////////////////////////////////////////////////////// - #define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF) - #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) - - // NOTE: CreateFile - #define CREATE_NEW 1 - #define CREATE_ALWAYS 2 - #define OPEN_EXISTING 3 - #define OPEN_ALWAYS 4 - #define TRUNCATE_EXISTING 5 - - typedef struct _WIN32_FILE_ATTRIBUTE_DATA { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - } WIN32_FILE_ATTRIBUTE_DATA, *LPWIN32_FILE_ATTRIBUTE_DATA; - - extern "C" - { - __declspec(dllimport) BOOL __stdcall FlushFileBuffers (HANDLE hFile); - __declspec(dllimport) BOOL __stdcall CreateDirectoryW (const WCHAR *lpPathName, SECURITY_ATTRIBUTES *lpSecurityAttributes); - __declspec(dllimport) BOOL __stdcall RemoveDirectoryW (const WCHAR *lpPathName); - __declspec(dllimport) BOOL __stdcall FindNextFileW (HANDLE hFindFile, WIN32_FIND_DATAW *lpFindFileData); - __declspec(dllimport) BOOL __stdcall FindClose (HANDLE hFindFile); - - __declspec(dllimport) HANDLE __stdcall FindFirstFileExW (const WCHAR *lpFileName, FINDEX_INFO_LEVELS fInfoLevelId, VOID *lpFindFileData, FINDEX_SEARCH_OPS fSearchOp, VOID *lpSearchFilter, DWORD dwAdditionalFlags); - __declspec(dllimport) BOOL __stdcall GetFileAttributesExW(const WCHAR *lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, VOID *lpFileInformation); - __declspec(dllimport) BOOL __stdcall GetFileSizeEx (HANDLE hFile, LARGE_INTEGER *lpFileSize); - __declspec(dllimport) BOOL __stdcall DeleteFileW (const WCHAR *lpFileName); - __declspec(dllimport) HANDLE __stdcall CreateFileW (const WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, SECURITY_ATTRIBUTES *lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); - __declspec(dllimport) BOOL __stdcall ReadFile (HANDLE hFile, VOID *lpBuffer, DWORD nNumberOfBytesToRead, DWORD *lpNumberOfBytesRead, OVERLAPPED *lpOverlapped); - __declspec(dllimport) BOOL __stdcall WriteFile (HANDLE hFile, const VOID *lpBuffer, DWORD nNumberOfBytesToWrite, DWORD *lpNumberOfBytesWritten, OVERLAPPED *lpOverlapped); - } - - // NOTE: um/processenv.h /////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) DWORD __stdcall GetCurrentDirectoryW (DWORD nBufferLength, WCHAR *lpBuffer); - __declspec(dllimport) HANDLE __stdcall GetStdHandle (DWORD nStdHandle); - __declspec(dllimport) WCHAR* __stdcall GetEnvironmentStringsW (); - __declspec(dllimport) BOOL __stdcall FreeEnvironmentStringsW(WCHAR *penv); - __declspec(dllimport) DWORD __stdcall GetEnvironmentVariableW(WCHAR const *lpName, WCHAR *lpBuffer, DWORD nSize); - __declspec(dllimport) BOOL __stdcall SetEnvironmentVariableW(WCHAR const *lpName, WCHAR const *lpValue); - } - - // NOTE: um/sysinfoapi.h /////////////////////////////////////////////////////////////////////// - typedef struct _SYSTEM_INFO { - union { - DWORD dwOemId; // Obsolete field...do not use - struct { - WORD wProcessorArchitecture; - WORD wReserved; - } DUMMYSTRUCTNAME; - } DUMMYUNIONNAME; - DWORD dwPageSize; - VOID *lpMinimumApplicationAddress; - VOID *lpMaximumApplicationAddress; - DWORD_PTR dwActiveProcessorMask; - DWORD dwNumberOfProcessors; - DWORD dwProcessorType; - DWORD dwAllocationGranularity; - WORD wProcessorLevel; - WORD wProcessorRevision; - } SYSTEM_INFO, *LPSYSTEM_INFO; - - extern "C" - { - __declspec(dllimport) VOID __stdcall GetSystemInfo(SYSTEM_INFO *lpSystemInfo); - __declspec(dllimport) VOID __stdcall GetSystemTime(SYSTEMTIME *lpSystemTime); - __declspec(dllimport) VOID __stdcall GetSystemTimeAsFileTime(FILETIME *lpSystemTimeAsFileTime); - __declspec(dllimport) VOID __stdcall GetLocalTime(SYSTEMTIME *lpSystemTime); - } - - // NOTE: um/timezoneapi.h ////////////////////////////////////////////////////////////////////// - typedef struct _TIME_ZONE_INFORMATION { - LONG Bias; - WCHAR StandardName[32]; - SYSTEMTIME StandardDate; - LONG StandardBias; - WCHAR DaylightName[32]; - SYSTEMTIME DaylightDate; - LONG DaylightBias; - } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION; - - extern "C" - { - __declspec(dllimport) BOOL __stdcall FileTimeToSystemTime (const FILETIME* lpFileTime, SYSTEMTIME *lpSystemTime); - __declspec(dllimport) BOOL __stdcall SystemTimeToFileTime (const SYSTEMTIME* lpSystemTime, FILETIME *lpFileTime); - __declspec(dllimport) BOOL __stdcall TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION* lpTimeZoneInformation, const SYSTEMTIME* lpLocalTime, const LPSYSTEMTIME lpUniversalTime); - } - - // NOTE: shared/windef.h /////////////////////////////////////////////////////////////////////// - typedef struct tagRECT { - LONG left; - LONG top; - LONG right; - LONG bottom; - } RECT; - - struct HWND__ { - int unused; - }; - typedef struct HWND__ *HWND; - - struct DPI_AWARENESS_CONTEXT__ { - int unused; - }; - typedef struct DPI_AWARENESS_CONTEXT__ *DPI_AWARENESS_CONTEXT; - - typedef enum DPI_AWARENESS { - DPI_AWARENESS_INVALID = -1, - DPI_AWARENESS_UNAWARE = 0, - DPI_AWARENESS_SYSTEM_AWARE = 1, - DPI_AWARENESS_PER_MONITOR_AWARE = 2 - } DPI_AWARENESS; - - #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) - - // NOTE: um/winuser.h ////////////////////////////////////////////////////////////////////////// - typedef struct tagWINDOWPLACEMENT { - UINT length; - UINT flags; - UINT showCmd; - POINT ptMinPosition; - POINT ptMaxPosition; - RECT rcNormalPosition; - #ifdef _MAC - RECT rcDevice; - #endif - } WINDOWPLACEMENT; - typedef WINDOWPLACEMENT *PWINDOWPLACEMENT, *LPWINDOWPLACEMENT; - - #define SW_HIDE 0 - #define SW_NORMAL 1 - #define SW_MAXIMIZE 3 - #define SW_SHOWNOACTIVATE 4 - #define SW_SHOW 5 - #define SW_FORCEMINIMIZE 11 - - extern "C" - { - __declspec(dllimport) BOOL __stdcall GetWindowRect (HWND hWnd, RECT *lpRect); - __declspec(dllimport) BOOL __stdcall SetWindowPos (HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); - __declspec(dllimport) UINT __stdcall GetWindowModuleFileNameA(HWND hwnd, LPSTR pszFileName, UINT cchFileNameMax); - __declspec(dllimport) BOOL __stdcall ShowWindow (HWND hWnd, int nCmdShow); - __declspec(dllimport) BOOL __stdcall GetWindowPlacement (HWND hWnd, WINDOWPLACEMENT *lpwndpl); - - } - - // NOTE: um/wininet.h ////////////////////////////////////////////////////////////////////////// - typedef WORD INTERNET_PORT; - typedef VOID *HINTERNET; - - // NOTE: um/winhttp.h ////////////////////////////////////////////////////////////////////////// - #define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0 - #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 - #define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3 - #define WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY 4 - - #define INTERNET_DEFAULT_PORT 0 // use the protocol-specific default - #define INTERNET_DEFAULT_HTTP_PORT 80 // " " HTTP " - #define INTERNET_DEFAULT_HTTPS_PORT 443 // " " HTTPS " - - // NOTE: WinHttpOpen - #define WINHTTP_FLAG_ASYNC 0x10000000 // this session is asynchronous (where supported) - #define WINHTTP_FLAG_SECURE_DEFAULTS 0x30000000 // note that this flag also forces async - - // NOTE: WinHttpOpenRequest - #define WINHTTP_FLAG_SECURE 0x00800000 // use SSL if applicable (HTTPS) - #define WINHTTP_FLAG_ESCAPE_PERCENT 0x00000004 // if escaping enabled, escape percent as well - #define WINHTTP_FLAG_NULL_CODEPAGE 0x00000008 // assume all symbols are ASCII, use fast convertion - #define WINHTTP_FLAG_ESCAPE_DISABLE 0x00000040 // disable escaping - #define WINHTTP_FLAG_ESCAPE_DISABLE_QUERY 0x00000080 // if escaping enabled escape path part, but do not escape query - #define WINHTTP_FLAG_BYPASS_PROXY_CACHE 0x00000100 // add "pragma: no-cache" request header - #define WINHTTP_FLAG_REFRESH WINHTTP_FLAG_BYPASS_PROXY_CACHE - #define WINHTTP_FLAG_AUTOMATIC_CHUNKING 0x00000200 // Send request without content-length header or chunked TE - - #define WINHTTP_NO_PROXY_NAME NULL - #define WINHTTP_NO_PROXY_BYPASS NULL - - // - // WINHTTP_QUERY_FLAG_NUMBER - if this bit is set in the dwInfoLevel parameter of - // HttpQueryHeader(), then the value of the header will be converted to a number - // before being returned to the caller, if applicable - // - #define WINHTTP_QUERY_FLAG_NUMBER 0x20000000 - - #define WINHTTP_QUERY_MIME_VERSION 0 - #define WINHTTP_QUERY_CONTENT_TYPE 1 - #define WINHTTP_QUERY_CONTENT_TRANSFER_ENCODING 2 - #define WINHTTP_QUERY_CONTENT_ID 3 - #define WINHTTP_QUERY_CONTENT_DESCRIPTION 4 - #define WINHTTP_QUERY_CONTENT_LENGTH 5 - #define WINHTTP_QUERY_CONTENT_LANGUAGE 6 - #define WINHTTP_QUERY_ALLOW 7 - #define WINHTTP_QUERY_PUBLIC 8 - #define WINHTTP_QUERY_DATE 9 - #define WINHTTP_QUERY_EXPIRES 10 - #define WINHTTP_QUERY_LAST_MODIFIED 11 - #define WINHTTP_QUERY_MESSAGE_ID 12 - #define WINHTTP_QUERY_URI 13 - #define WINHTTP_QUERY_DERIVED_FROM 14 - #define WINHTTP_QUERY_COST 15 - #define WINHTTP_QUERY_LINK 16 - #define WINHTTP_QUERY_PRAGMA 17 - #define WINHTTP_QUERY_VERSION 18 // special: part of status line - #define WINHTTP_QUERY_STATUS_CODE 19 // special: part of status line - #define WINHTTP_QUERY_STATUS_TEXT 20 // special: part of status line - #define WINHTTP_QUERY_RAW_HEADERS 21 // special: all headers as ASCIIZ - #define WINHTTP_QUERY_RAW_HEADERS_CRLF 22 // special: all headers - #define WINHTTP_QUERY_CONNECTION 23 - #define WINHTTP_QUERY_ACCEPT 24 - #define WINHTTP_QUERY_ACCEPT_CHARSET 25 - #define WINHTTP_QUERY_ACCEPT_ENCODING 26 - #define WINHTTP_QUERY_ACCEPT_LANGUAGE 27 - #define WINHTTP_QUERY_AUTHORIZATION 28 - #define WINHTTP_QUERY_CONTENT_ENCODING 29 - #define WINHTTP_QUERY_FORWARDED 30 - #define WINHTTP_QUERY_FROM 31 - #define WINHTTP_QUERY_IF_MODIFIED_SINCE 32 - #define WINHTTP_QUERY_LOCATION 33 - #define WINHTTP_QUERY_ORIG_URI 34 - #define WINHTTP_QUERY_REFERER 35 - #define WINHTTP_QUERY_RETRY_AFTER 36 - #define WINHTTP_QUERY_SERVER 37 - #define WINHTTP_QUERY_TITLE 38 - #define WINHTTP_QUERY_USER_AGENT 39 - #define WINHTTP_QUERY_WWW_AUTHENTICATE 40 - #define WINHTTP_QUERY_PROXY_AUTHENTICATE 41 - #define WINHTTP_QUERY_ACCEPT_RANGES 42 - #define WINHTTP_QUERY_SET_COOKIE 43 - #define WINHTTP_QUERY_COOKIE 44 - #define WINHTTP_QUERY_REQUEST_METHOD 45 // special: GET/POST etc. - #define WINHTTP_QUERY_REFRESH 46 - #define WINHTTP_QUERY_CONTENT_DISPOSITION 47 - - // NOTE: WinHttpQueryHeaders prettifiers for optional parameters. - #define WINHTTP_HEADER_NAME_BY_INDEX NULL - #define WINHTTP_NO_OUTPUT_BUFFER NULL - #define WINHTTP_NO_HEADER_INDEX NULL - - // NOTE: Http Response Status Codes - #define HTTP_STATUS_CONTINUE 100 // OK to continue with request - #define HTTP_STATUS_SWITCH_PROTOCOLS 101 // server has switched protocols in upgrade header - - #define HTTP_STATUS_OK 200 // request completed - #define HTTP_STATUS_CREATED 201 // object created, reason = new URI - #define HTTP_STATUS_ACCEPTED 202 // async completion (TBS) - #define HTTP_STATUS_PARTIAL 203 // partial completion - #define HTTP_STATUS_NO_CONTENT 204 // no info to return - #define HTTP_STATUS_RESET_CONTENT 205 // request completed, but clear form - #define HTTP_STATUS_PARTIAL_CONTENT 206 // partial GET fulfilled - #define HTTP_STATUS_WEBDAV_MULTI_STATUS 207 // WebDAV Multi-Status - - #define HTTP_STATUS_AMBIGUOUS 300 // server couldn't decide what to return - #define HTTP_STATUS_MOVED 301 // object permanently moved - #define HTTP_STATUS_REDIRECT 302 // object temporarily moved - #define HTTP_STATUS_REDIRECT_METHOD 303 // redirection w/ new access method - #define HTTP_STATUS_NOT_MODIFIED 304 // if-modified-since was not modified - #define HTTP_STATUS_USE_PROXY 305 // redirection to proxy, location header specifies proxy to use - #define HTTP_STATUS_REDIRECT_KEEP_VERB 307 // HTTP/1.1: keep same verb - #define HTTP_STATUS_PERMANENT_REDIRECT 308 // Object permanently moved keep verb - - #define HTTP_STATUS_BAD_REQUEST 400 // invalid syntax - #define HTTP_STATUS_DENIED 401 // access denied - #define HTTP_STATUS_PAYMENT_REQ 402 // payment required - #define HTTP_STATUS_FORBIDDEN 403 // request forbidden - #define HTTP_STATUS_NOT_FOUND 404 // object not found - #define HTTP_STATUS_BAD_METHOD 405 // method is not allowed - #define HTTP_STATUS_NONE_ACCEPTABLE 406 // no response acceptable to client found - #define HTTP_STATUS_PROXY_AUTH_REQ 407 // proxy authentication required - #define HTTP_STATUS_REQUEST_TIMEOUT 408 // server timed out waiting for request - #define HTTP_STATUS_CONFLICT 409 // user should resubmit with more info - #define HTTP_STATUS_GONE 410 // the resource is no longer available - #define HTTP_STATUS_LENGTH_REQUIRED 411 // the server refused to accept request w/o a length - #define HTTP_STATUS_PRECOND_FAILED 412 // precondition given in request failed - #define HTTP_STATUS_REQUEST_TOO_LARGE 413 // request entity was too large - #define HTTP_STATUS_URI_TOO_LONG 414 // request URI too long - #define HTTP_STATUS_UNSUPPORTED_MEDIA 415 // unsupported media type - #define HTTP_STATUS_RETRY_WITH 449 // retry after doing the appropriate action. - - #define HTTP_STATUS_SERVER_ERROR 500 // internal server error - #define HTTP_STATUS_NOT_SUPPORTED 501 // required not supported - #define HTTP_STATUS_BAD_GATEWAY 502 // error response received from gateway - #define HTTP_STATUS_SERVICE_UNAVAIL 503 // temporarily overloaded - #define HTTP_STATUS_GATEWAY_TIMEOUT 504 // timed out waiting for gateway - #define HTTP_STATUS_VERSION_NOT_SUP 505 // HTTP version not supported - - #define HTTP_STATUS_FIRST HTTP_STATUS_CONTINUE - #define HTTP_STATUS_LAST HTTP_STATUS_VERSION_NOT_SUP - - #define WINHTTP_CALLBACK_STATUS_RESOLVING_NAME 0x00000001 - #define WINHTTP_CALLBACK_STATUS_NAME_RESOLVED 0x00000002 - #define WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER 0x00000004 - #define WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER 0x00000008 - #define WINHTTP_CALLBACK_STATUS_SENDING_REQUEST 0x00000010 - #define WINHTTP_CALLBACK_STATUS_REQUEST_SENT 0x00000020 - #define WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE 0x00000040 - #define WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED 0x00000080 - #define WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION 0x00000100 - #define WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED 0x00000200 - #define WINHTTP_CALLBACK_STATUS_HANDLE_CREATED 0x00000400 - #define WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING 0x00000800 - #define WINHTTP_CALLBACK_STATUS_DETECTING_PROXY 0x00001000 - #define WINHTTP_CALLBACK_STATUS_REDIRECT 0x00004000 - #define WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE 0x00008000 - #define WINHTTP_CALLBACK_STATUS_SECURE_FAILURE 0x00010000 - #define WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE 0x00020000 - #define WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE 0x00040000 - #define WINHTTP_CALLBACK_STATUS_READ_COMPLETE 0x00080000 - #define WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE 0x00100000 - #define WINHTTP_CALLBACK_STATUS_REQUEST_ERROR 0x00200000 - #define WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE 0x00400000 - - #define WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE 0x01000000 - #define WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE 0x02000000 - #define WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE 0x04000000 - #define WINHTTP_CALLBACK_STATUS_SETTINGS_WRITE_COMPLETE 0x10000000 - #define WINHTTP_CALLBACK_STATUS_SETTINGS_READ_COMPLETE 0x20000000 - - #define WINHTTP_CALLBACK_FLAG_RESOLVE_NAME (WINHTTP_CALLBACK_STATUS_RESOLVING_NAME | WINHTTP_CALLBACK_STATUS_NAME_RESOLVED) - #define WINHTTP_CALLBACK_FLAG_CONNECT_TO_SERVER (WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER | WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER) - #define WINHTTP_CALLBACK_FLAG_SEND_REQUEST (WINHTTP_CALLBACK_STATUS_SENDING_REQUEST | WINHTTP_CALLBACK_STATUS_REQUEST_SENT) - #define WINHTTP_CALLBACK_FLAG_RECEIVE_RESPONSE (WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE | WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED) - #define WINHTTP_CALLBACK_FLAG_CLOSE_CONNECTION (WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION | WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED) - #define WINHTTP_CALLBACK_FLAG_HANDLES (WINHTTP_CALLBACK_STATUS_HANDLE_CREATED | WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING) - #define WINHTTP_CALLBACK_FLAG_DETECTING_PROXY WINHTTP_CALLBACK_STATUS_DETECTING_PROXY - #define WINHTTP_CALLBACK_FLAG_REDIRECT WINHTTP_CALLBACK_STATUS_REDIRECT - #define WINHTTP_CALLBACK_FLAG_INTERMEDIATE_RESPONSE WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE - #define WINHTTP_CALLBACK_FLAG_SECURE_FAILURE WINHTTP_CALLBACK_STATUS_SECURE_FAILURE - #define WINHTTP_CALLBACK_FLAG_SENDREQUEST_COMPLETE WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE - #define WINHTTP_CALLBACK_FLAG_HEADERS_AVAILABLE WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE - #define WINHTTP_CALLBACK_FLAG_DATA_AVAILABLE WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE - #define WINHTTP_CALLBACK_FLAG_READ_COMPLETE WINHTTP_CALLBACK_STATUS_READ_COMPLETE - #define WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE - #define WINHTTP_CALLBACK_FLAG_REQUEST_ERROR WINHTTP_CALLBACK_STATUS_REQUEST_ERROR - - #define WINHTTP_CALLBACK_FLAG_GETPROXYFORURL_COMPLETE WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE - - #define WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS (WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE \ - | WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE \ - | WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE \ - | WINHTTP_CALLBACK_STATUS_READ_COMPLETE \ - | WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE \ - | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR \ - | WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE) - - #define WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS 0xffffffff - #define WINHTTP_INVALID_STATUS_CALLBACK ((WINHTTP_STATUS_CALLBACK)(-1L)) - - typedef struct _WINHTTP_EXTENDED_HEADER - { - union - { - CHAR const *pwszName; - WCHAR const *pszName; - }; - union - { - WCHAR const *pwszValue; - CHAR const *pszValue; - }; - } WINHTTP_EXTENDED_HEADER, *PWINHTTP_EXTENDED_HEADER; - - typedef struct _WINHTTP_ASYNC_RESULT - { - DWORD *dwResult; // indicates which async API has encountered an error - DWORD dwError; // the error code if the API failed - } WINHTTP_ASYNC_RESULT, *LPWINHTTP_ASYNC_RESULT, *PWINHTTP_ASYNC_RESULT; - - typedef - VOID - (*WINHTTP_STATUS_CALLBACK)( - HINTERNET hInternet, - DWORD *dwContext, - DWORD dwInternetStatus, - VOID *lpvStatusInformation, - DWORD dwStatusInformationLength - ); - - extern "C" - { - __declspec(dllimport) HINTERNET __stdcall WinHttpOpen(WCHAR const *pszAgentW, DWORD dwAccessType, WCHAR const *pszProxyW, WCHAR const *pszProxyBypassW, DWORD dwFlags); - __declspec(dllimport) BOOL __stdcall WinHttpCloseHandle(HINTERNET hInternet); - __declspec(dllimport) HINTERNET __stdcall WinHttpConnect(HINTERNET hSession, WCHAR const *pswzServerName, INTERNET_PORT nServerPort, DWORD dwReserved); - __declspec(dllimport) BOOL __stdcall WinHttpReadData(HINTERNET hRequest, VOID *lpBuffer, DWORD dwNumberOfBytesToRead, DWORD *lpdwNumberOfBytesRead); - __declspec(dllimport) HINTERNET __stdcall WinHttpOpenRequest(HINTERNET hConnect, WCHAR const *pwszVerb, WCHAR const *pwszObjectName, WCHAR const *pwszVersion, WCHAR const *pwszReferrer, WCHAR const *ppwszAcceptTypes, DWORD dwFlags); - __declspec(dllimport) BOOL __stdcall WinHttpSendRequest(HINTERNET hRequest, WCHAR const *lpszHeaders, DWORD dwHeadersLength, VOID *lpOptional, DWORD dwOptionalLength, DWORD dwTotalLength, DWORD_PTR dwContext); - __declspec(dllimport) DWORD __stdcall WinHttpAddRequestHeadersEx(HINTERNET hRequest, DWORD dwModifiers, ULONGLONG ullFlags, ULONGLONG ullExtra, DWORD cHeaders, WINHTTP_EXTENDED_HEADER *pHeaders); - __declspec(dllimport) BOOL __stdcall WinHttpSetCredentials(HINTERNET hRequest, // HINTERNET handle returned by WinHttpOpenRequest. - DWORD AuthTargets, // Only WINHTTP_AUTH_TARGET_SERVER and WINHTTP_AUTH_TARGET_PROXY are supported in this version and they are mutually exclusive - DWORD AuthScheme, // must be one of the supported Auth Schemes returned from WinHttpQueryAuthSchemes() - WCHAR * pwszUserName, // 1) NULL if default creds is to be used, in which case pszPassword will be ignored - WCHAR * pwszPassword, // 1) "" == Blank Password; 2)Parameter ignored if pszUserName is NULL; 3) Invalid to pass in NULL if pszUserName is not NULL - VOID * pAuthParams); - __declspec(dllimport) BOOL __stdcall WinHttpQueryHeaders(HINTERNET hRequest, DWORD dwInfoLevel, WCHAR const *pwszName, VOID *lpBuffer, DWORD *lpdwBufferLength, DWORD *lpdwIndex); - __declspec(dllimport) BOOL __stdcall WinHttpReceiveResponse(HINTERNET hRequest, VOID *lpReserved); - __declspec(dllimport) WINHTTP_STATUS_CALLBACK __stdcall WinHttpSetStatusCallback(HINTERNET hInternet, WINHTTP_STATUS_CALLBACK lpfnInternetCallback, DWORD dwNotificationFlags, DWORD_PTR dwReserved); - } - - // NOTE: um/DbgHelp.h ////////////////////////////////////////////////////////////////////////// - #define SYMOPT_CASE_INSENSITIVE 0x00000001 - #define SYMOPT_UNDNAME 0x00000002 - #define SYMOPT_DEFERRED_LOADS 0x00000004 - #define SYMOPT_NO_CPP 0x00000008 - #define SYMOPT_LOAD_LINES 0x00000010 - #define SYMOPT_OMAP_FIND_NEAREST 0x00000020 - #define SYMOPT_LOAD_ANYTHING 0x00000040 - #define SYMOPT_IGNORE_CVREC 0x00000080 - #define SYMOPT_NO_UNQUALIFIED_LOADS 0x00000100 - #define SYMOPT_FAIL_CRITICAL_ERRORS 0x00000200 - #define SYMOPT_EXACT_SYMBOLS 0x00000400 - #define SYMOPT_ALLOW_ABSOLUTE_SYMBOLS 0x00000800 - #define SYMOPT_IGNORE_NT_SYMPATH 0x00001000 - #define SYMOPT_INCLUDE_32BIT_MODULES 0x00002000 - #define SYMOPT_PUBLICS_ONLY 0x00004000 - #define SYMOPT_NO_PUBLICS 0x00008000 - #define SYMOPT_AUTO_PUBLICS 0x00010000 - #define SYMOPT_NO_IMAGE_SEARCH 0x00020000 - #define SYMOPT_SECURE 0x00040000 - #define SYMOPT_NO_PROMPTS 0x00080000 - #define SYMOPT_OVERWRITE 0x00100000 - #define SYMOPT_IGNORE_IMAGEDIR 0x00200000 - #define SYMOPT_FLAT_DIRECTORY 0x00400000 - #define SYMOPT_FAVOR_COMPRESSED 0x00800000 - #define SYMOPT_ALLOW_ZERO_ADDRESS 0x01000000 - #define SYMOPT_DISABLE_SYMSRV_AUTODETECT 0x02000000 - #define SYMOPT_READONLY_CACHE 0x04000000 - #define SYMOPT_SYMPATH_LAST 0x08000000 - #define SYMOPT_DISABLE_FAST_SYMBOLS 0x10000000 - #define SYMOPT_DISABLE_SYMSRV_TIMEOUT 0x20000000 - #define SYMOPT_DISABLE_SRVSTAR_ON_STARTUP 0x40000000 - #define SYMOPT_DEBUG 0x80000000 - - #define MAX_SYM_NAME 2000 - - typedef enum { - AddrMode1616, - AddrMode1632, - AddrModeReal, - AddrModeFlat - } ADDRESS_MODE; - - typedef struct _tagADDRESS64 { - DWORD64 Offset; - WORD Segment; - ADDRESS_MODE Mode; - } ADDRESS64, *LPADDRESS64; - - - typedef struct _KDHELP64 { - DWORD64 Thread; - DWORD ThCallbackStack; - DWORD ThCallbackBStore; - DWORD NextCallback; - DWORD FramePointer; - DWORD64 KiCallUserMode; - DWORD64 KeUserCallbackDispatcher; - DWORD64 SystemRangeStart; - DWORD64 KiUserExceptionDispatcher; - DWORD64 StackBase; - DWORD64 StackLimit; - DWORD BuildVersion; - DWORD RetpolineStubFunctionTableSize; - DWORD64 RetpolineStubFunctionTable; - DWORD RetpolineStubOffset; - DWORD RetpolineStubSize; - DWORD64 Reserved0[2]; - } KDHELP64, *PKDHELP64; - - typedef struct _tagSTACKFRAME64 { - ADDRESS64 AddrPC; // program counter - ADDRESS64 AddrReturn; // return address - ADDRESS64 AddrFrame; // frame pointer - ADDRESS64 AddrStack; // stack pointer - ADDRESS64 AddrBStore; // backing store pointer - VOID *FuncTableEntry; // pointer to pdata/fpo or NULL - DWORD64 Params[4]; // possible arguments to the function - BOOL Far; // WOW far call - BOOL Virtual; // is this a virtual frame? - DWORD64 Reserved[3]; - KDHELP64 KdHelp; - } STACKFRAME64; - - typedef struct _IMAGEHLP_LINEW64 { - DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_LINE64) - VOID *Key; // internal - DWORD LineNumber; // line number in file - WCHAR *FileName; // full filename - DWORD64 Address; // first instruction of line - } IMAGEHLP_LINEW64; - - typedef struct _SYMBOL_INFOW { - ULONG SizeOfStruct; - ULONG TypeIndex; // Type Index of symbol - ULONG64 Reserved[2]; - ULONG Index; - ULONG Size; - ULONG64 ModBase; // Base Address of module comtaining this symbol - ULONG Flags; - ULONG64 Value; // Value of symbol, ValuePresent should be 1 - ULONG64 Address; // Address of symbol including base address of module - ULONG Register; // register holding value or pointer to value - ULONG Scope; // scope of the symbol - ULONG Tag; // pdb classification - ULONG NameLen; // Actual length of name - ULONG MaxNameLen; - WCHAR Name[1]; // Name of symbol - } SYMBOL_INFOW; - - typedef BOOL (__stdcall READ_PROCESS_MEMORY_ROUTINE64)(HANDLE hProcess, DWORD64 qwBaseAddress, VOID *lpBuffer, DWORD nSize, DWORD *lpNumberOfBytesRead); - typedef VOID * (__stdcall FUNCTION_TABLE_ACCESS_ROUTINE64)(HANDLE ahProcess, DWORD64 AddrBase); - typedef DWORD64(__stdcall GET_MODULE_BASE_ROUTINE64)(HANDLE hProcess, DWORD64 Address); - typedef DWORD64(__stdcall TRANSLATE_ADDRESS_ROUTINE64)(HANDLE hProcess, HANDLE hThread, ADDRESS64 *lpaddr); - - extern "C" - { - __declspec(dllimport) BOOL __stdcall StackWalk64 (DWORD MachineType, HANDLE hProcess, HANDLE hThread, STACKFRAME64 *StackFrame, VOID *ContextRecord, READ_PROCESS_MEMORY_ROUTINE64 *ReadMemoryRoutine, FUNCTION_TABLE_ACCESS_ROUTINE64 *FunctionTableAccessRoutine, GET_MODULE_BASE_ROUTINE64 *GetModuleBaseRoutine, TRANSLATE_ADDRESS_ROUTINE64 *TranslateAddress); - __declspec(dllimport) BOOL __stdcall SymFromAddrW (HANDLE hProcess, DWORD64 Address, DWORD64 *Displacement, SYMBOL_INFOW *Symbol); - __declspec(dllimport) VOID * __stdcall SymFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase); - __declspec(dllimport) BOOL __stdcall SymGetLineFromAddrW64 (HANDLE hProcess, DWORD64 dwAddr, DWORD *pdwDisplacement, IMAGEHLP_LINEW64 *Line); - __declspec(dllimport) DWORD64 __stdcall SymGetModuleBase64 (HANDLE hProcess, DWORD64 qwAddr); - __declspec(dllimport) BOOL __stdcall SymRefreshModuleList (HANDLE hProcess); - }; - - // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) DWORD __stdcall GetLastError(VOID); - } - - // NOTE: um/libloaderapi.h ///////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) HMODULE __stdcall LoadLibraryA (const CHAR *lpLibFileName); - __declspec(dllimport) BOOL __stdcall FreeLibrary (HMODULE hLibModule); - __declspec(dllimport) void * __stdcall GetProcAddress (HMODULE hModule, const CHAR *lpProcName); - __declspec(dllimport) HMODULE __stdcall GetModuleHandleA (const CHAR *lpModuleName); - __declspec(dllimport) DWORD __stdcall GetModuleFileNameW(HMODULE hModule, WCHAR *lpFilename, DWORD nSize); - } - - // NOTE: um/synchapi.h ///////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) VOID __stdcall InitializeCriticalSection (CRITICAL_SECTION *lpCriticalSection); - __declspec(dllimport) VOID __stdcall EnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); - __declspec(dllimport) VOID __stdcall LeaveCriticalSection (CRITICAL_SECTION *lpCriticalSection); - __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); - __declspec(dllimport) BOOL __stdcall InitializeCriticalSectionEx (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount, DWORD Flags); - __declspec(dllimport) DWORD __stdcall SetCriticalSectionSpinCount (CRITICAL_SECTION *lpCriticalSection, DWORD dwSpinCount); - __declspec(dllimport) BOOL __stdcall TryEnterCriticalSection (CRITICAL_SECTION *lpCriticalSection); - __declspec(dllimport) VOID __stdcall DeleteCriticalSection (CRITICAL_SECTION *lpCriticalSection); - __declspec(dllimport) DWORD __stdcall WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds); - __declspec(dllimport) BOOL __stdcall ReleaseSemaphore (HANDLE hSemaphore, LONG lReleaseCount, LONG *lpPreviousCount); - __declspec(dllimport) VOID __stdcall Sleep (DWORD dwMilliseconds); - } - - // NOTE: um/profileapi.h /////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall QueryPerformanceCounter (LARGE_INTEGER* lpPerformanceCount); - __declspec(dllimport) BOOL __stdcall QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency); - } - - // NOTE: um/processthreadsapi.h //////////////////////////////////////////////////////////////// - typedef struct _PROCESS_INFORMATION { - HANDLE hProcess; - HANDLE hThread; - DWORD dwProcessId; - DWORD dwThreadId; - } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; - - typedef struct _STARTUPINFOW { - DWORD cb; - WCHAR *lpReserved; - WCHAR *lpDesktop; - WCHAR *lpTitle; - DWORD dwX; - DWORD dwY; - DWORD dwXSize; - DWORD dwYSize; - DWORD dwXCountChars; - DWORD dwYCountChars; - DWORD dwFillAttribute; - DWORD dwFlags; - WORD wShowWindow; - WORD cbReserved2; - BYTE *lpReserved2; - HANDLE hStdInput; - HANDLE hStdOutput; - HANDLE hStdError; - } STARTUPINFOW, *LPSTARTUPINFOW; - - typedef DWORD (__stdcall *PTHREAD_START_ROUTINE)( - VOID *lpThreadParameter - ); - typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE; - - extern "C" - { - __declspec(dllimport) BOOL __stdcall CreateProcessW (WCHAR const *lpApplicationName, WCHAR *lpCommandLine, SECURITY_ATTRIBUTES *lpProcessAttributes, SECURITY_ATTRIBUTES *lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, VOID *lpEnvironment, WCHAR const *lpCurrentDirectory, STARTUPINFOW *lpStartupInfo, PROCESS_INFORMATION *lpProcessInformation); - __declspec(dllimport) HANDLE __stdcall CreateThread (SECURITY_ATTRIBUTES *lpThreadAttributes, SIZE_T dwStackSize, PTHREAD_START_ROUTINE lpStartAddress, VOID *lpParameter, DWORD dwCreationFlags, DWORD *lpThreadId); - __declspec(dllimport) DWORD __stdcall GetCurrentThreadId(VOID); - __declspec(dllimport) BOOL __stdcall GetExitCodeProcess(HANDLE hProcess, DWORD *lpExitCode); - __declspec(dllimport) void __stdcall ExitProcess (UINT uExitCode); - } - - // NOTE: um/memoryapi.h //////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) VOID * __stdcall VirtualAlloc (VOID *lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); - __declspec(dllimport) BOOL __stdcall VirtualProtect(VOID *lpAddress, SIZE_T dwSize, DWORD flNewProtect, DWORD *lpflOldProtect); - __declspec(dllimport) BOOL __stdcall VirtualFree (VOID *lpAddress, SIZE_T dwSize, DWORD dwFreeType); - } - - // NOTE: shared/bcrypt.h /////////////////////////////////////////////////////////////////////// - typedef VOID *BCRYPT_ALG_HANDLE; - typedef LONG NTSTATUS; - - extern "C" - { - __declspec(dllimport) NTSTATUS __stdcall BCryptOpenAlgorithmProvider(BCRYPT_ALG_HANDLE *phAlgorithm, const WCHAR *pszAlgId, const WCHAR *pszImplementation, ULONG dwFlags); - __declspec(dllimport) NTSTATUS __stdcall BCryptGenRandom (BCRYPT_ALG_HANDLE hAlgorithm, UCHAR *pbBuffer, ULONG cbBuffer, ULONG dwFlags); - } - - // NOTE: um/shellapi.h ///////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) HINSTANCE __stdcall ShellExecuteA(HWND hwnd, CHAR const *lpOperation, CHAR const *lpFile, CHAR const *lpParameters, CHAR const *lpDirectory, INT nShowCmd); - __declspec(dllimport) HINSTANCE __stdcall ShellExecuteW(HWND hwnd, WCHAR const *lpOperation, WCHAR const *lpFile, WCHAR const *lpParameters, WCHAR const *lpDirectory, INT nShowCmd); - } - - // NOTE: um/debugapi.h ///////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall IsDebuggerPresent(); - } - - // NOTE: um/namedpipeapi.h ///////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall CreatePipe (HANDLE *hReadPipe, HANDLE *hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); - __declspec(dllimport) BOOL __stdcall PeekNamedPipe(HANDLE hNamedPipe, VOID *lpBuffer, DWORD nBufferSize, DWORD *lpBytesRead, DWORD *lpTotalBytesAvail, DWORD *lpBytesLeftThisMessage); - } - - // NOTE: um/handleapi.h //////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); - } - - // NOTE: um/commdlg.h ////////////////////////////////////////////////////////////////////////// - typedef UINT_PTR (__stdcall *LPOFNHOOKPROC)(HWND, UINT, WPARAM, LPARAM); - typedef struct tagOFNW { - DWORD lStructSize; - HWND hwndOwner; - HINSTANCE hInstance; - WCHAR const * lpstrFilter; - LPWSTR lpstrCustomFilter; - DWORD nMaxCustFilter; - DWORD nFilterIndex; - LPWSTR lpstrFile; - DWORD nMaxFile; - LPWSTR lpstrFileTitle; - DWORD nMaxFileTitle; - WCHAR const * lpstrInitialDir; - WCHAR const * lpstrTitle; - DWORD Flags; - WORD nFileOffset; - WORD nFileExtension; - WCHAR const * lpstrDefExt; - LPARAM lCustData; - LPOFNHOOKPROC lpfnHook; - WCHAR const * lpTemplateName; - #ifdef _MAC - LPEDITMENU lpEditInfo; - LPCSTR lpstrPrompt; - #endif - #if (_WIN32_WINNT >= 0x0500) - void * pvReserved; - DWORD dwReserved; - DWORD FlagsEx; - #endif // (_WIN32_WINNT >= 0x0500) - } OPENFILENAMEW, *LPOPENFILENAMEW; - - - #define OFN_READONLY 0x00000001 - #define OFN_OVERWRITEPROMPT 0x00000002 - #define OFN_HIDEREADONLY 0x00000004 - #define OFN_NOCHANGEDIR 0x00000008 - #define OFN_SHOWHELP 0x00000010 - #define OFN_ENABLEHOOK 0x00000020 - #define OFN_ENABLETEMPLATE 0x00000040 - #define OFN_ENABLETEMPLATEHANDLE 0x00000080 - #define OFN_NOVALIDATE 0x00000100 - #define OFN_ALLOWMULTISELECT 0x00000200 - #define OFN_EXTENSIONDIFFERENT 0x00000400 - #define OFN_PATHMUSTEXIST 0x00000800 - #define OFN_FILEMUSTEXIST 0x00001000 - #define OFN_CREATEPROMPT 0x00002000 - #define OFN_SHAREAWARE 0x00004000 - #define OFN_NOREADONLYRETURN 0x00008000 - #define OFN_NOTESTFILECREATE 0x00010000 - #define OFN_NONETWORKBUTTON 0x00020000 - #define OFN_NOLONGNAMES 0x00040000 // force no long names for 4.x modules - #if(WINVER >= 0x0400) - #define OFN_EXPLORER 0x00080000 // new look commdlg - #define OFN_NODEREFERENCELINKS 0x00100000 - #define OFN_LONGNAMES 0x00200000 // force long names for 3.x modules - // OFN_ENABLEINCLUDENOTIFY and OFN_ENABLESIZING require - // Windows 2000 or higher to have any effect. - #define OFN_ENABLEINCLUDENOTIFY 0x00400000 // send include message to callback - #define OFN_ENABLESIZING 0x00800000 - #endif /* WINVER >= 0x0400 */ - #if (_WIN32_WINNT >= 0x0500) - #define OFN_DONTADDTORECENT 0x02000000 - #define OFN_FORCESHOWHIDDEN 0x10000000 // Show All files including System and hidden files - #endif // (_WIN32_WINNT >= 0x0500) - - //FlagsEx Values - #if (_WIN32_WINNT >= 0x0500) - #define OFN_EX_NOPLACESBAR 0x00000001 - #endif // (_WIN32_WINNT >= 0x0500) - - extern "C" - { - __declspec(dllimport) BOOL __stdcall GetSaveFileNameW(LPOPENFILENAMEW); - __declspec(dllimport) BOOL __stdcall GetOpenFileNameW(LPOPENFILENAMEW); - } - - // NOTE: um/shlwapi.h ////////////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) BOOL __stdcall PathRelativePathToW(WCHAR *pszPath, WCHAR const *pszFrom, DWORD dwAttrFrom, WCHAR const *pszTo, DWORD dwAttrTo); - __declspec(dllimport) BOOL __stdcall PathIsRelativeW(WCHAR *pszPath); - } - - // NOTE: um/pathcch.h ////////////////////////////////////////////////////////////////////////// - typedef enum PATHCCH_OPTIONS - { - PATHCCH_NONE = 0x0, - - // This option allows applications to gain access to long paths. It has two - // different behaviors. For process configured to enable long paths it will allow - // the returned path to be longer than the max path limit that is normally imposed. - // For process that are not this option will convert long paths into the extended - // length DOS device form (with \\?\ prefix) when the path is longer than the limit. - // This form is not length limited by the Win32 file system API on all versions of Windows. - // This second behavior is the same behavior for OSes that don't have the long path feature. - // This can not be specified with PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH. - PATHCCH_ALLOW_LONG_PATHS = 0x01, - - // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This - // Forces the API to treat the caller as long path enabled, independent of the - // process's long name enabled state. Cannot be used with PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS. - PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS = 0x02, - - // Can only be used when PATHCCH_ALLOW_LONG_PATHS is specified. This - // Forces the API to treat the caller as long path disabled, independent of the - // process's long name enabled state. Cannot be used with PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS. - PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS = 0x04, - - // Disable the normalization of path segments that includes removing trailing dots and spaces. - // This enables access to paths that win32 path normalization will block. - PATHCCH_DO_NOT_NORMALIZE_SEGMENTS = 0x08, - - // Convert the input path into the extended length DOS device path form (with the \\?\ prefix) - // if not already in that form. This enables access to paths that are otherwise not addressable - // due to Win32 normalization rules (that can strip trailing dots and spaces) and path - // length limitations. This option implies the same behavior of PATHCCH_DO_NOT_NORMALIZE_SEGMENTS. - // This can not be specified with PATHCCH_ALLOW_LONG_PATHS. - PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH = 0x10, - - // When combining or normalizing a path ensure there is a trailing backslash. - PATHCCH_ENSURE_TRAILING_SLASH = 0x020, - - // Convert forward slashes to back slashes and collapse multiple slashes. - // This is needed to to support sub-path or identity comparisons. - PATHCCH_CANONICALIZE_SLASHES = 0x040, - } PATHCCH_OPTIONS; - - extern "C" - { - __declspec(dllimport) HRESULT __stdcall PathCchCanonicalizeEx(PWSTR pszPathOut, size_t cchPathOut, WCHAR const *pszPathIn, ULONG dwFlags); - }; - - // NOTE: um/errhandlingapi.h /////////////////////////////////////////////////////////////////// - extern "C" - { - __declspec(dllimport) VOID __stdcall RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR* lpArguments); - }; - - // NOTE: include/excpt.h /////////////////////////////////////////////////////////////////// - #define EXCEPTION_EXECUTE_HANDLER 1 - #define EXCEPTION_CONTINUE_SEARCH 0 - #define EXCEPTION_CONTINUE_EXECUTION (-1) - - DN_MSVC_WARNING_POP -#endif // !defined(_INC_WINDOWS) diff --git a/readme.md b/readme.md deleted file mode 100644 index a429e70..0000000 --- a/readme.md +++ /dev/null @@ -1,30 +0,0 @@ -# Dqn - -My personal standard library that provides allocator aware data structures, -custom memory allocators and various miscellaneous helpers for prototyping. The -library is a unity-build style library where data structures and functions are -separated by category into files for organisation. You only need to include -`dqn.h` which amalgamates all the files into one translation unit. - -## Build - -To build with this library, copy all the `*.[h|cpp]` files at the root of the -repository to your desired location and compile `dqn.cpp` or include it into one -of your translation units. - -Finally ensure that the compiler has in its search paths for the include -directory where headers are located, e.g. `-I `. - -## Customisation - -The headers provide macros to compile out sections that are not needed. This can -be useful to speed up compile times or reduce binary size if you only need -a particular part of the library. Each header contains a table-of-contents that -denotes the macro to define to disable that section that should be defined -before the header include. - -```cpp -#define DQN_NO_VARRAY // Disable virtual array container -#define DQN_NO_JSON_BUILDER // Disable the JSON string builder -#include "dqn.h" -```