diff --git a/Single-Header/dn_single_header.cpp b/Single-Header/dn_single_header.cpp index 45408a7..6cf20d0 100644 --- a/Single-Header/dn_single_header.cpp +++ b/Single-Header/dn_single_header.cpp @@ -1,10 +1,11 @@ -// Generated by the DN single header generator 2026-06-25 21:37:26 +// Generated by the DN single header generator 2026-07-05 12:47:32 // DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 +// #define DN_WITH_TESTS 1 // #define DN_WITH_OS 1 // #define DN_WITH_NET 1 // #define DN_WITH_NET_CURL 1 +// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 // #include "dn.h" // #endif @@ -18,7 +19,6 @@ enum DN_ArenaUAFCheckReportType_ DN_ArenaUAFCheckReportType_TempEndOutOfOrder, }; - DN_Core *g_dn_; DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) @@ -52,7 +52,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) } { - os->mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_MemFuncsDefault()); + os->mem = DN_MemListFromHeap(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_OS_HeapInitDefault()); os->arena = DN_ArenaFromMemList(&os->mem); #if defined(DN_PLATFORM_WIN32) @@ -93,7 +93,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #endif // NOTE: Initialise thread context - DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); + DN_TCInitFromHeap(&dn->main_tc, DN_OS_ThreadID(), args, DN_OS_HeapInitDefault()); DN_TCEquip(&dn->main_tc); } @@ -102,9 +102,8 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #if DN_WITH_OS // NOTE: Setup the allocation table with allocation tracking turned off on // the arena we're using to initialise the table. - dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault()); - dn->leak.alloc_table_arena = DN_ArenaFromMemList(&dn->leak.alloc_table_mem); - dn->leak.alloc_table = DN_DSMapInit(&dn->leak.alloc_table_arena, 4096, DN_DSMapFlags_Nil); + DN_HTableInitArgs table_args = DN_HTableInitArgsDefault(DN_LeakTrackerKV, dn->leak.alloc_table_kvs, hash, key, value); + dn->leak.alloc_table = DN_HTableInitHeapAssert(table_args, DN_OS_HeapInitBasic()); #endif } @@ -128,12 +127,12 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: "); if (DN_BitIsSet(flags, DN_InitFlags_OS)) { - DN_Arena *arena = dn->main_tc.main_arena; - DN_Str8 mem_funcs = DN_Str8Lit(""); - switch (arena->mem->funcs.type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: mem_funcs = DN_Str8Lit("Heap"); break; - case DN_MemFuncsType_Virtual: mem_funcs = DN_Str8Lit("Virtual"); break; + DN_Arena *arena = dn->main_tc.main_arena; + DN_Str8 heap = DN_Str8Lit(""); + switch (arena->mem->heap.type) { + case DN_HeapType_Nil: break; + case DN_HeapType_Basic: heap = DN_Str8Lit("Basic"); break; + case DN_HeapType_Virtual: heap = DN_Str8Lit("Virtual"); break; } DN_Str8x32 main_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->commit); DN_Str8x32 main_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->reserve); @@ -146,7 +145,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) DN_Str8x32 temp_reserve = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->reserve); DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " T(x%zu) %.*s/%.*s", dn->main_tc.temp_arenas_count, DN_Str8PrintFmt(temp_commit), DN_Str8PrintFmt(temp_reserve)); } - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(mem_funcs)); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(heap)); } else { DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "N/A\n"); } @@ -243,12 +242,10 @@ DN_API bool DN_VerifyArgsF(DN_VerifyType type, bool expr, DN_CallSite call_site, } // NOTE: Log stack trace - if (DN_PARANOIA_LEVEL) { - if (type == DN_VerifyType_Nil) { - DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); - DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); - DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); - } + if (type == DN_VerifyType_Nil && DN_PARANOIA_LEVEL) { + DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); + DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); + DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); } DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, &scratch.arena); @@ -1069,16 +1066,16 @@ DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) return result; } -static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_MemFuncs mem_funcs) +static DN_MemBlock *DN_MemBlockFromHeap_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_Heap heap) { DN_MemBlock *result = nullptr; - switch (mem_funcs.type) { - case DN_MemFuncsType_Nil: + switch (heap.type) { + case DN_HeapType_Nil: break; - case DN_MemFuncsType_Heap: { + case DN_HeapType_Basic: { DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); - result = DN_Cast(DN_MemBlock *) mem_funcs.heap_alloc(reserve); + result = DN_Cast(DN_MemBlock *) heap.basic_alloc(reserve); if (!result) return result; @@ -1087,24 +1084,27 @@ static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bo result->reserve = reserve; } break; - case DN_MemFuncsType_Virtual: { - DN_AssertF(mem_funcs.virtual_page_size, "Page size must be set to a non-zero, power of two value"); - DN_Assert(DN_IsPowerOfTwo(mem_funcs.virtual_page_size)); + case DN_HeapType_Virtual: { + DN_AssertF(heap.virtual_page_size, + "Page size must be set to a non-zero, power of two value. Virtual memory " + "functions are usually initialised by values obtained during initialisation, has " + "DN_Init() been called yet?"); + DN_Assert(DN_IsPowerOfTwo(heap.virtual_page_size)); - DN_USize const page_size = mem_funcs.virtual_page_size; + DN_USize const page_size = heap.virtual_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(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%zu < %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_MemBlock *) mem_funcs.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + result = DN_Cast(DN_MemBlock *) heap.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); if (!result) return result; - if (mem_commit == DN_MemCommit_No && !mem_funcs.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { - mem_funcs.virtual_release(result, real_reserve); + if (mem_commit == DN_MemCommit_No && !heap.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { + heap.virtual_release(result, real_reserve); return result; } @@ -1129,13 +1129,16 @@ static bool DN_ArenaHasPoison_(DN_MemFlags flags) return result; } -static DN_MemBlock *DN_MemBlockFromMemFuncsFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +static DN_MemBlock *DN_MemBlockFromHeapFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) { bool track_alloc = (flags & DN_MemFlags_NoAllocTrack) == 0; bool alloc_can_leak = flags & DN_MemFlags_AllocCanLeak; - DN_MemBlock *result = DN_ArenaBlockFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); - if (result && DN_ArenaHasPoison_(flags)) - DN_ASanPoisonMemoryRegion(DN_Cast(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); + DN_MemBlock *result = DN_MemBlockFromHeap_(reserve, commit, track_alloc, alloc_can_leak, heap); + if (result && DN_ArenaHasPoison_(flags)) { + char *poison = DN_Cast(char *)result + result->used; + DN_USize poison_size = result->commit - result->used; + DN_ASanPoisonMemoryRegion(poison, poison_size); + } return result; } @@ -1180,6 +1183,77 @@ DN_API DN_MemStats DN_MemStatsSumArray(DN_MemStats const *array, DN_USize count) return result; } +DN_API DN_Heap DN_HeapInitBasic(DN_HeapBasicAllocFunc *basic_alloc, DN_HeapBasicDeallocFunc *basic_dealloc) +{ + DN_Heap result = {}; + result.type = DN_HeapType_Basic; + result.basic_alloc = basic_alloc; + result.basic_dealloc = basic_dealloc; + return result; +} + +DN_API DN_Heap DN_HeapInitVirtual(DN_U32 page_size, DN_HeapVirtualReserveFunc *virtual_reserve, DN_HeapVirtualCommitFunc *virtual_commit, DN_HeapVirtualReleaseFunc *virtual_release) +{ + DN_Heap result = {}; + result.type = DN_HeapType_Virtual; + result.virtual_page_size = page_size; + result.virtual_reserve = virtual_reserve; + result.virtual_commit = virtual_commit; + result.virtual_release = virtual_release; + return result; +} + +DN_API void* DN_HeapAlloc(DN_Heap *heap, DN_USize reserve, DN_USize commit) +{ + void *result = nullptr; + switch (heap->type) { + case DN_HeapType_Nil: + break; + + case DN_HeapType_Basic: result = heap->basic_alloc(reserve); break; + case DN_HeapType_Virtual: { + DN_AssertF(heap->virtual_page_size, + "Page size must be set to a non-zero, power of two value. Virtual memory " + "functions are usually initialised by values obtained during initialisation, has " + "DN_Init() been called yet?"); + DN_Assert(DN_IsPowerOfTwo(heap->virtual_page_size)); + + DN_USize const page_size = heap->virtual_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, "%zu < %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_MemBlock *) heap->virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == DN_MemCommit_No && !heap->virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { + heap->virtual_release(result, real_reserve); + return result; + } + } break; + } + + if (result) { + heap->bytes_alloc += reserve; + heap->bytes_alloc_total += reserve; + } + return result; +} + +DN_API void DN_HeapDealloc(DN_Heap *heap, void *ptr, DN_USize size) +{ + heap->bytes_alloc -= size; + heap->bytes_freed += size; + if (heap->type == DN_HeapType_Basic) + heap->basic_dealloc(ptr); + else + heap->virtual_release(ptr, size); +} + DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags flags) { DN_Assert(buffer); @@ -1201,17 +1275,17 @@ DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags return result; } -DN_API DN_MemList DN_MemListFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +DN_API DN_MemList DN_MemListFromHeap(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) { DN_MemList result = {}; - result.funcs = mem_funcs; - result.flags |= flags | DN_MemFlags_MemFuncs; - result.curr = DN_MemBlockFromMemFuncsFlags_(reserve, commit, flags, mem_funcs); + result.heap = heap; + result.flags |= flags | DN_MemFlags_Heap; + result.curr = DN_MemBlockFromHeapFlags_(reserve, commit, flags, heap); DN_MemListOnNewBlock_(&result, result.curr); return result; } -static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) +static void DN_MemBlockDeinit_(DN_MemList *mem, DN_MemBlock *block) { DN_USize release_size = block->reserve; if (DN_BitIsNotSet(mem->flags, DN_MemFlags_NoAllocTrack)) @@ -1220,12 +1294,8 @@ static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) if (DN_ArenaHasPoison_(mem->flags)) DN_ASanUnpoisonMemoryRegion(block, block->commit); - if (mem->flags & DN_MemFlags_MemFuncs) { - if (mem->funcs.type == DN_MemFuncsType_Heap) - mem->funcs.heap_dealloc(block); - else - mem->funcs.virtual_release(block, release_size); - } + if (mem->flags & DN_MemFlags_Heap) + DN_HeapDealloc(&mem->heap, block, release_size); } DN_API void DN_MemListDeinit(DN_MemList *mem) @@ -1258,11 +1328,11 @@ DN_API bool DN_MemListCommitTo(DN_MemList *mem, DN_U64 pos) } // NOTE: Do the commit - DN_Assert(mem->funcs.virtual_page_size); - DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->funcs.virtual_page_size); + DN_Assert(mem->heap.virtual_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->heap.virtual_page_size); DN_USize commit_size = end_commit - curr->commit; char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + if (!mem->heap.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) return false; if (DN_ArenaHasPoison_(mem->flags)) @@ -1287,7 +1357,7 @@ DN_API bool DN_MemListGrow(DN_MemList *mem, DN_U64 reserve, DN_U64 commit) return false; bool result = false; - DN_MemBlock *new_block = DN_MemBlockFromMemFuncsFlags_(reserve, commit, mem->flags, mem->funcs); + DN_MemBlock *new_block = DN_MemBlockFromHeapFlags_(reserve, commit, mem->flags, mem->heap); if (new_block) { result = true; new_block->prev = mem->curr; @@ -1304,7 +1374,7 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem return nullptr; if (!mem->curr) { - mem->curr = DN_MemBlockFromMemFuncsFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->funcs); + mem->curr = DN_MemBlockFromHeapFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->heap); DN_MemListOnNewBlock_(mem, mem->curr); } @@ -1329,15 +1399,14 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem goto try_alloc_again; } - DN_USize prev_arena_commit = curr->commit; if (end_pos > curr->commit) { - DN_Assert(mem->funcs.virtual_page_size); - DN_Assert(mem->funcs.type == DN_MemFuncsType_Virtual); + DN_Assert(mem->heap.virtual_page_size); + DN_Assert(mem->heap.type == DN_HeapType_Virtual); DN_Assert((mem->flags & DN_MemFlags_UserBuffer) == 0); - DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->funcs.virtual_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->heap.virtual_page_size); DN_USize commit_size = end_commit - curr->commit; char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + if (!mem->heap.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) return nullptr; if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); @@ -1354,10 +1423,8 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) DN_ASanUnpoisonMemoryRegion(result, size); - if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) { - DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); - DN_Memset(result, 0, reused_bytes); - } + if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) + DN_Memset(result, 0, size); DN_Assert(mem->stats.hwm.used >= mem->stats.info.used); DN_Assert(mem->stats.hwm.commit >= mem->stats.info.commit); @@ -1546,7 +1613,7 @@ static void DN_ArenaUAFCheck_(DN_Arena *arena, DN_ArenaUAFCheckReportType_ type) DN_Str8 error_msg = {}; if (type == DN_ArenaUAFCheckReportType_AllocViolation) { error_msg = DN_Str8FromFmtAllocator(allocator, - "Arena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " + "\n\nArena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " "shown above) is attempting to allocate memory inside the active temporary region (id: %'u) " "but belongs to a different region (id: %'u). This means when the active temporary region is " "released, this allocation will be released and scrubbed causing a potential UAF.\n\nEnsure " @@ -1604,6 +1671,15 @@ DN_API DN_Arena DN_ArenaFromMemList(DN_MemList *mem) return result; } +DN_API DN_Arena DN_ArenaFromHeap(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) +{ + DN_MemList mem = DN_MemListFromHeap(reserve, commit, flags, heap); + DN_Arena result = {}; + result.flags |= DN_ArenaFlags_OwnsMemList; + result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); + return result; +} + DN_API DN_Arena DN_ArenaTempBeginFromMemList(DN_MemList* mem) { DN_Arena result = DN_ArenaFromMemList(mem); @@ -1686,6 +1762,12 @@ DN_API void *DN_ArenaCopy(DN_Arena *arena, void const *data, DN_U64 size, DN_U8 return result; } +DN_API void DN_ArenaDeinit(DN_Arena *arena) +{ + if (arena->flags & DN_ArenaFlags_OwnsMemList) + DN_MemListDeinit(arena->mem); +} + DN_API DN_Pool DN_PoolFromArena(DN_Arena *arena, DN_U8 align) { DN_Pool result = {}; @@ -2064,15 +2146,15 @@ DN_API DN_TCInitArgs DN_TCInitArgsDefault() return result; } -DN_API void DN_TCInitFromMemFuncs(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs) +DN_API void DN_TCInitFromHeap(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_Heap heap) { DN_Assert(args.temp_count <= DN_ArrayCountU(tc->temp_arenas)); - tc->main_arena_mem_ = DN_MemListFromMemFuncs(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->main_arena_mem_ = DN_MemListFromHeap(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); for (DN_ForIndexU(index, args.temp_count)) { - tc->temp_arena_mems_[index] = DN_MemListFromMemFuncs(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->temp_arena_mems_[index] = DN_MemListFromHeap(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); tc->temp_arenas_[index] = DN_ArenaFromMemList(&tc->temp_arena_mems_[index]); } - tc->err_sink_arena_mem_ = DN_MemListFromMemFuncs(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->err_sink_arena_mem_ = DN_MemListFromHeap(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); tc->main_arena_ = DN_ArenaFromMemList(&tc->main_arena_mem_); tc->err_sink_arena_ = DN_ArenaFromMemList(&tc->err_sink_arena_mem_); DN_TCInit(tc, thread_id, &tc->main_arena_, tc->temp_arenas_, args.temp_count, &tc->err_sink_arena_); @@ -2315,7 +2397,7 @@ DN_API char DN_CharToUpper(char ch) return result; } -DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) +DN_API DN_U64FromResult DN_U64FromStr8Delimiters(DN_Str8 string, DN_Str8 const *delimiters, DN_USize delimiters_count) { // NOTE: Argument check DN_U64FromResult result = {}; @@ -2341,12 +2423,25 @@ DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) // NOTE: Convert the string number to the binary number for (DN_USize index = start_index; index < trim_string.count; index++) { - char ch = trim_string.data[index]; + + // NOTE: Check for presence of the delimiter, we skip the first character as a U64 string + // prefixed with a delimiter is not considered valid. if (index) { - if (separator != 0 && ch == separator) + DN_USize max_delimiter_match = 0; + for (DN_ForItSize(it, DN_Str8 const, delimiters, delimiters_count)) { + DN_Str8 delimiter = *it.data; + DN_Str8 check_slice = DN_Str8Subset(trim_string, index, delimiter.count); + if (DN_Str8EqSensitive(check_slice, delimiter)) + max_delimiter_match = DN_Max(max_delimiter_match, delimiter.count); // Take length, there might be multiple so we keep going + } + + if (max_delimiter_match) { + index += max_delimiter_match - 1; continue; + } } + char ch = trim_string.data[index]; if (!DN_CharIsDigit(ch)) return result; @@ -2359,18 +2454,48 @@ DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) return result; } -DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size, char separator) +DN_API DN_U64FromResult DN_U64FromStr8Delimiter(DN_Str8 string, DN_Str8 delimiter) { - DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); - DN_U64FromResult result = DN_U64FromStr8(str8, separator); + DN_U64FromResult result = DN_U64FromStr8Delimiters(string, &delimiter, 1); return result; } -DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size, char separator) +DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string) { - DN_U64FromResult from = DN_U64FromPtr(data, size, separator); + DN_U64FromResult result = DN_U64FromStr8Delimiters(string, nullptr, 0); + return result; +} + +DN_API DN_U64FromResult DN_U64FromPtrDelimiter(void const *data, DN_USize size, DN_Str8 delimiter) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_U64FromResult result = DN_U64FromStr8Delimiter(str8, delimiter); + return result; +} + + +DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_U64FromResult result = DN_U64FromStr8Delimiters(str8, nullptr, 0); + return result; +} + +DN_API DN_U64 DN_U64FromPtrUnsafeDelimiter(void const *data, DN_USize size, DN_Str8 delimiter) +{ + DN_U64FromResult from = DN_U64FromPtrDelimiter(data, size, delimiter); DN_U64 result = from.value; - DN_Assert(from.success); + DN_VerifyWarning(from.success); + return result; +} + + +DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size) +{ + DN_Str8 str8 = DN_Str8FromPtr(data, size); + DN_U64FromResult from = DN_U64FromStr8Delimiters(str8, nullptr, 0); + DN_U64 result = from.value; + DN_VerifyWarning(from.success); return result; } @@ -3132,7 +3257,7 @@ DN_API DN_Str8BSplitResult DN_Str8BSplitArray(DN_Str8 string, DN_Str8 const *fin for (DN_USize find_index = 0; find_index < find_size; find_index++) { DN_Str8 find_item = find[find_index]; DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.count); - if (DN_Str8Eq(string_slice, find_item)) { + if (DN_Str8EqSensitive(string_slice, find_item)) { result.input_index = find_index; result.lhs.count = index; result.rhs.data = string_slice.data + find_item.count; @@ -3162,7 +3287,7 @@ DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray(DN_Str8 string, DN_Str8 const for (DN_USize find_index = 0; find_index < find_size; find_index++) { DN_Str8 find_item = find[find_index]; DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.count); - if (DN_Str8Eq(string_slice, find_item)) { + if (DN_Str8EqSensitive(string_slice, find_item)) { result.lhs.count = index; result.rhs.data = string_slice.data + find_item.count; result.rhs.count = string.count - (index + find_item.count); @@ -3191,7 +3316,7 @@ DN_API DN_USize DN_Str8Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, bool handle_quotes = DN_BitIsSet(flags, DN_Str8SplitFlags_HandleQuotedStrings); do { DN_Str8 item = {}; - if (handle_quotes && DN_Str8StartsWith(it, DN_Str8Lit("\""))) { + if (handle_quotes && DN_Str8StartsWithSensitive(it, DN_Str8Lit("\""))) { DN_Str8FindResult find = DN_Str8FindStr8(DN_Str8Advance(it, 1), DN_Str8Lit("\""), DN_Str8EqCase_Sensitive); DN_Assert(find.found); item = find.start_to_before_match; @@ -3335,7 +3460,7 @@ DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) bool result = true; switch (eq_case) { case DN_Str8EqCase_Sensitive: { - result = (DN_Memcmp(lhs.data, rhs.data, lhs.count) == 0); + result = DN_MemEqUnsafe(lhs.data, rhs.data, lhs.count); } break; case DN_Str8EqCase_Insensitive: { @@ -3346,6 +3471,12 @@ DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) return result; } +DN_API bool DN_Str8EqSensitive(DN_Str8 lhs, DN_Str8 rhs) +{ + bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Sensitive); + return result; +} + DN_API bool DN_Str8EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) { bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Insensitive); @@ -3359,6 +3490,13 @@ DN_API bool DN_Str8StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_c return result; } +DN_API bool DN_Str8StartsWithSensitive(DN_Str8 string, DN_Str8 prefix) +{ + bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + + DN_API bool DN_Str8StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) { bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Insensitive); @@ -3372,6 +3510,12 @@ DN_API bool DN_Str8EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_cas return result; } +DN_API bool DN_Str8EndsWithSensitive(DN_Str8 string, DN_Str8 suffix) +{ + bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Sensitive); + return result; +} + DN_API bool DN_Str8EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) { bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Insensitive); @@ -3396,6 +3540,18 @@ DN_API DN_Str8 DN_Str8TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase e return result; } +DN_API DN_Str8 DN_Str8TrimPrefixSensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimPrefixInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + DN_API DN_Str8 DN_Str8TrimHexPrefix(DN_Str8 string) { DN_Str8 result = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); @@ -3410,10 +3566,34 @@ DN_API DN_Str8 DN_Str8TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase e return result; } -DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string) +DN_API DN_Str8 DN_Str8TrimSuffixSensitive(DN_Str8 string, DN_Str8 prefix) { - DN_Str8 result = DN_Str8TrimPrefix(string, trim_string); - result = DN_Str8TrimSuffix(result, trim_string); + DN_Str8 result = DN_Str8TrimSuffix(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimSuffixInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimSuffix(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string, DN_Str8EqCase eq_case) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, trim_string, eq_case); + result = DN_Str8TrimSuffix(result, trim_string, eq_case); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAroundSensitive(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8TrimAround(string, trim_string, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAroundInsensitive(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8TrimAround(string, trim_string, DN_Str8EqCase_Insensitive); return result; } @@ -3869,7 +4049,7 @@ DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num if (!print_row_line) print_row_line = DN_BitIsSet(flags, DN_Str8TableFlags_RowLines); - if (print_row_line) { + if (print_row_line && (i != num_rows - 1)) { DN_Str8BuilderAppendF(&builder, "+"); for (DN_USize sub_i = 0; sub_i < num_cols; sub_i++) { for (DN_USize sub_j = 0; sub_j < col_widths[sub_i] + 2; sub_j++) @@ -3892,7 +4072,7 @@ DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num return result; } -#if DN_STR8_AVX512F +#if DN_WITH_STR8_AVX512F DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F(DN_Str8 string, DN_Str8 find) { // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html @@ -4569,7 +4749,7 @@ DN_API bool DN_Str8BuilderPrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char c DN_API bool DN_Str8BuilderErase(DN_Str8Builder *builder, DN_Str8 string) { for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { - if (DN_Str8Eq((*it)->string, string)) { + if (DN_Str8EqSensitive((*it)->string, string)) { *it = (*it)->next; builder->string_size -= string.count; builder->count -= 1; @@ -7988,7 +8168,7 @@ DN_API DN_ArrayEraseResult DN_ArrayEraseRange(void *data, DN_USize *count, DN_US *count -= real_erase_count; } - result.items_erased = erase_count; + result.items_erased = real_erase_count; // NOTE: If we are erasing from the current index of the iterator to the end of the array then // there's no more elements in the array to iterate. So the returned index should b // one-past-last index @@ -8046,7 +8226,7 @@ DN_API void *DN_ArrayAddArrayAssert(void *data, DN_USize *count, DN_USize max, D DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); if (buffer) { @@ -8054,8 +8234,9 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_Memcpy(buffer, *data, bytes_to_copy); DN_PoolDealloc(pool, *data); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8067,7 +8248,7 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); if (buffer) { @@ -8075,8 +8256,9 @@ DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_Memcpy(buffer, *data, bytes_to_copy); DN_PoolDealloc(pool, *data); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8088,15 +8270,16 @@ DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_ArenaNewArray(arena, DN_U8, bytes_to_alloc, DN_ZMem_No); if (buffer) { DN_USize bytes_to_copy = elem_size * DN_Min(*count, new_max); DN_Memcpy(buffer, *data, bytes_to_copy); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8105,23 +8288,26 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, return result; } -DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) +DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max > *max) + if (!max || new_max > *max) { + DN_USize count = 0; result = DN_ArrayResizeFromPool(data, &count, max, elem_size, pool, new_max); + } return result; } -DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) +DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) { bool result = true; - if (new_max > *max) + if (!max || new_max > *max) { + DN_USize count = 0; result = DN_ArrayResizeFromArena(data, &count, max, elem_size, arena, new_max); + } return result; } - DN_API bool DN_ArrayGrowIfNeededFromPool(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count) { bool result = true; @@ -8144,6 +8330,36 @@ DN_API bool DN_ArrayGrowIfNeededFromArena(void **data, DN_USize count, DN_USize return result; } +DN_API DN_USize DN_ArrayCopyPtrArena(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena) +{ + DN_USize result = 0; + if (DN_ArrayGrowFromArena(data, /*max=*/ &result, elem_size, arena, /*new_max=*/ count)) + DN_Memcpy(*data, src, elem_size * count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrArenaAssert(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrArena(data, src, count, elem_size, arena); + DN_AssertCallSiteF(result == count, call_site, "Array copy failed, failed to allocate: %zu items", count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrPool(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool) +{ + DN_USize result = 0; + if (DN_ArrayGrowFromPool(data, /*max=*/ &result, elem_size, pool, /*new_max=*/ count)) + DN_Memcpy(*data, src, elem_size * count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrPoolAssert(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrPool(data, src, count, elem_size, pool); + DN_AssertCallSiteF(result == count, call_site, "Array copy failed, failed to allocate: %zu items", count); + return result; +} + DN_API void *DN_SinglyLLDetach(void **link, void **next) { void *result = *link; @@ -8171,13 +8387,16 @@ DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size) DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size) { - DN_Assert(src_size <= ring->size); + DN_AssertF(src_size <= ring->size, + "Payload to write (%s) to ring exceeds the capacity of the ring (%s)", + DN_Str8x32FromByteCountU64Auto(src_size).data, + DN_Str8x32FromByteCountU64Auto(ring->size).data); DN_U64 offset = ring->write_pos % ring->size; DN_U64 bytes_before_split = ring->size - offset; DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size); DN_U64 post_split_bytes = src_size - pre_split_bytes; void const *pre_split_data = src; - void const *post_split_data = ((char *)src + pre_split_bytes); + void const *post_split_data = (DN_Cast(char *)src + pre_split_bytes); DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes); DN_Memcpy(ring->base, post_split_data, post_split_bytes); ring->write_pos += src_size; @@ -8195,476 +8414,334 @@ DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size) ring->read_pos += dest_size; } -#if defined(__cplusplus) -template -DN_DSMap DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) +DN_API DN_U32 DN_HTableHashFuncDefault(void const *key, DN_USize size) { - DN_AssertF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size); - DN_Assert(arena); - - DN_DSMap result = {}; - if (size <= 0) - return result; - if (!arena) - return result; - - result.arena = arena; - result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); - result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes); - result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot, size, DN_ZMem_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?"); + DN_U32 const DEFAULT_SEED = 0xb255b383; + DN_U32 result = DN_MurmurHash3HashU32FromBytes(key, DN_Cast(int)size, DEFAULT_SEED); return result; } -template -void DN_DSMapDeinit(DN_DSMap *map, DN_ZMem z_mem) +DN_API bool DN_HTableKeyEqFuncMemcmp(void const *lhs, void const *rhs, DN_USize size) { - if (!map) - return; - // TODO(doyle): Use z_mem - (void)z_mem; - DN_MemListDeinit(map->arena->mem); - *map = {}; -} - -template -bool DN_DSMapIsValid(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 + bool result = DN_Memcmp(lhs, rhs, size) == 0; return result; } -template -DN_U32 DN_DSMapHash(DN_DSMap const *map, DN_DSMapKey key) +DN_API bool DN_HTableKeyEqFuncStr8Eq(void const *lhs, void const *rhs, DN_USize) { - DN_U32 result = 0; - if (!map) - return result; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8*) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8*) rhs; + bool result = DN_Str8EqSensitive(lhs_str8, rhs_str8); + return result; +} - if (key.type == DN_DSMapKeyType_U64NoHash) { - result = DN_Cast(DN_U32) key.u64; - return result; - } +DN_API DN_HTableInitArgs DN_HTableInitArgsDefault_(void** kvs, DN_USize size_of_kv, DN_USize offset_of_hash, DN_USize size_of_key, DN_USize offset_of_key, DN_USize size_of_value, DN_USize offset_of_value) +{ + DN_HTableInitArgs result = {}; + DN_AssertF((*kvs) == 0, "The key-value pointer must be null as the hash table will allocate the objects and keep the pointer in sync with the table for you: %p", *kvs); + result.hash_func = DN_HTableHashFuncDefault; + result.key_eq_func = DN_HTableKeyEqFuncMemcmp; + result.load_factor = 0.7f; + result.max = 0; + result.kvs = kvs; + result.size_of_kv = size_of_kv; + result.offset_of_key = offset_of_key; + result.offset_of_hash = offset_of_hash; + result.size_of_key = size_of_key; + result.offset_of_value = offset_of_value; + result.size_of_value = size_of_value; + return result; +} - if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { - result = key.hash; - return result; - } +DN_API DN_HTable DN_HTableInit_(DN_HTableInitArgs args) +{ + DN_HTable result = {}; + result.load_factor01 = args.load_factor; + result.hash_func = args.hash_func; + result.key_eq_func = args.key_eq_func; + result.kvs = args.kvs; + result.size_of_kv = args.size_of_kv; + result.offset_of_key = args.offset_of_key; + result.offset_of_hash = args.offset_of_hash; + result.size_of_key = args.size_of_key; + result.offset_of_value = args.offset_of_value; + result.size_of_value = args.size_of_value; + 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); +DN_API DN_HTableInitResult DN_HTableInitHeap(DN_HTableInitArgs args, DN_Heap heap) +{ + DN_HTableInitResult result = {}; + result.table = DN_HTableInit_(args); + result.table.heap = heap; + result.table.flags |= DN_HTableFlags_UsingHeap; + result.success = DN_HTableResize(&result.table, args.max); + return result; +} + +DN_API DN_HTable DN_HTableInitHeapAssert(DN_HTableInitArgs args, DN_Heap heap) +{ + DN_HTableInitResult init = DN_HTableInitHeap(args, heap); + DN_HTable result = init.table; + DN_Assert(init.success); + return result; +} + +DN_API DN_HTableInitResult DN_HTableInitPool(DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool) +{ + DN_HTableInitResult result = {}; + result.table = DN_HTableInit_(args); + result.table.pool = pool; + result.success = DN_HTableResize(&result.table, args.max); + if (dealloc_pool == DN_HTableDeallocPoolOnDeinit_Yes) + result.table.flags |= DN_HTableFlags_DeallocPoolOnDeinit; + if (!result.success) + result.table = {}; + return result; +} + +DN_API DN_HTable DN_HTableInitPoolAssert(DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool) +{ + DN_HTableInitResult init = DN_HTableInitPool(args, pool, dealloc_pool); + DN_HTable result = init.table; + DN_Assert(init.success); + return result; +} + +DN_API void DN_HTableDeinit(DN_HTable *table) +{ + if ((table->flags & DN_HTableFlags_UsingHeap)) { + DN_HeapDealloc(&table->heap, *table->kvs, table->max * table->size_of_kv); } 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 + DN_PoolDealloc(&table->pool, *table->kvs); + if (table->flags & DN_HTableFlags_DeallocPoolOnDeinit) + DN_ArenaDeinit(table->pool.arena); + } + *table->kvs = nullptr; + *table = {}; +} - 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_AssertInvalidCodePath; /*FALLTHRU*/ - case DN_DSMapKeyType_Invalid: break; +static DN_HTableSlot DN_HTableSlotFromArgs_(void *kvs, DN_USize size_of_kv, DN_USize offset_of_hash, DN_USize offset_of_key, DN_USize offset_of_value, DN_USize index) +{ + DN_HTableSlot result = {}; + result.kvs_index = index; + result.kv_struct = (DN_Cast(char*) kvs) + (index * size_of_kv); + result.key = DN_Cast(char*) result.kv_struct + offset_of_key; + result.value = DN_Cast(char*) result.kv_struct + offset_of_value; + result.hash = DN_Cast(char*) result.kv_struct + offset_of_hash; + result.hash_u32 = *DN_Cast(DN_HTableHashType*) result.hash; + return result; +} - case DN_DSMapKeyType_Buffer: - key_ptr = DN_Cast(char const *) key.buffer_data; - len = key.buffer_size; - break; +DN_API DN_HTableLookupResult DN_HTableLookup(DN_HTable const *table, void const *key, DN_HTableAllowTombstone allow_tombstone) +{ + DN_HTableLookupResult result = {}; + if (table->max == 0 || !key) + return result; - case DN_DSMapKeyType_U64: - key_ptr = DN_Cast(char const *) & key.u64; - len = sizeof(key.u64); - break; + DN_Assert(DN_IsPowerOfTwo(table->max)); + DN_U32 hash = table->hash_func(key, table->size_of_key); + if (hash < DN_HTableHashSentinel_FirstValid) + hash += DN_HTableHashSentinel_FirstValid; + DN_USize const mask = table->max - 1; + DN_USize index = hash & mask; + DN_USize probe_increment = 1; + for (DN_USize offset = 0; offset < table->max; offset++, probe_increment++) { + DN_HTableSlot slot = DN_HTableSlotFromIndex(table, index); + bool matched = slot.hash_u32 == DN_HTableHashSentinel_Empty; + if (!matched && allow_tombstone == DN_HTableAllowTombstone_Yes) + matched = slot.hash_u32 == DN_HTableHashSentinel_Tomb; + if (!matched) + matched = slot.hash_u32 >= DN_HTableHashSentinel_FirstValid && slot.hash_u32 == hash && table->key_eq_func(slot.key, key, table->size_of_key); + if (matched) { + result.slot = slot; + result.key_hash = hash; + 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); + // NOTE: We use triangular number probing, referenced off jblow's implementation. Triangular + // numbers are guaranteed to hit every entry in the table. Triangular probing can cause more + // collisions but avoids the localised clustering of collisions when linear probing. It is less + // cache-friendly than linear but more cache-friendly than double hashing. + // https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ + index = (index + probe_increment) & mask; } return result; } -template -DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) +DN_API DN_HTableSlot DN_HTableFind(DN_HTable const *table, void const *key) { - DN_Assert(key.type != DN_DSMapKeyType_Invalid); - DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; - if (!DN_DSMapIsValid(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); - } + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + DN_HTableSlot result = {}; + if (lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) + result = lookup.slot; + return result; } -template -DN_DSMapResult DN_DSMapFind(DN_DSMap const *map, DN_DSMapKey key) +DN_API void* DN_HTableValueFromFind(DN_HTable const *table, void const *key) { - DN_DSMapResult result = {}; - if (DN_DSMapIsValid(map)) { - DN_U32 index = DN_DSMapHashToSlotIndex(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; + DN_HTableSlot slot = DN_HTableFind(table, key); + void *result = slot.value; // Might be null if the key was not found + return result; +} + +DN_API DN_HTableSlot DN_HTableSlotFromIndex(DN_HTable const* table, DN_USize index) +{ + DN_HTableSlot result = {}; + if (index < table->max) + result = DN_HTableSlotFromArgs_(*table->kvs, table->size_of_kv, table->offset_of_hash, table->offset_of_key, table->offset_of_value, index); + return result; +} + +DN_API bool DN_HTableResize(DN_HTable *table, DN_USize new_max) +{ + if (new_max == 0) + new_max = 32; + if (!DN_IsPowerOfTwo(new_max)) + new_max = DN_AlignUpPowerOfTwoUSize(new_max); + + void* new_kvs = {}; + DN_USize new_kvs_size = new_max * table->size_of_kv; + if (table->flags & DN_HTableFlags_UsingHeap) + new_kvs = DN_HeapAlloc(&table->heap, /*reserve*/ new_kvs_size, /*commit*/ new_kvs_size); + else + new_kvs = DN_PoolAlloc(&table->pool, new_max * table->size_of_kv); + + if (new_kvs) { + // NOTE: Copy over the old table slots into the new table storage + void* old_kvs = *table->kvs; + DN_USize old_max = table->max; + table->count = table->tombs_count = 0; + table->max = new_max; + *table->kvs = new_kvs; + for (DN_ForIndexU(index, old_max)) { + DN_HTableSlot old_slot = DN_HTableSlotFromArgs_(old_kvs, table->size_of_kv, table->offset_of_hash, table->offset_of_key, table->offset_of_value, index); + if (old_slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) + DN_HTableAdd(table, old_slot.key, old_slot.value); } - result.value = &result.slot->value; + + if (old_kvs) { + if (table->flags & DN_HTableFlags_UsingHeap) + DN_HeapDealloc(&table->heap, old_kvs, old_max * table->size_of_kv); + else + DN_PoolDealloc(&table->pool, old_kvs); + } + } + + bool result = new_kvs != nullptr; + return result; +} + +DN_API void DN_HTableClear(DN_HTable *table) +{ + for (DN_ForIndexU(index, table->max)) { + DN_HTableSlot slot = DN_HTableSlotFromIndex(table, index); + DN_Memset(slot.hash, DN_HTableHashSentinel_Empty, sizeof(slot.hash_u32)); + } + table->tombs_count = 0; + table->count = 0; +} + +DN_API DN_HTableResizeResult DN_HTableGrowMaybe(DN_HTable *table, DN_USize add_count) +{ + DN_HTableResizeResult result = {}; + result.success = true; + DN_USize new_count = table->count + table->tombs_count + add_count; + DN_F32 new_load_factor = table->max ? new_count / DN_Cast(DN_F32) table->max : table->load_factor01; + if (new_load_factor >= table->load_factor01) { + // NOTE: In this branch, the total number of actively used slots (including tombstones) and the + // `add_amount` exceeds the load factor of the table. We check if doubling the `count` of just + // the _active slots_ in the table would still fit under the table's load factor. If it does + // then the table is currently mostly tombstones, we can avoid doubling the size of the table by + // rehashing which will wipe out the tombstones. + DN_USize new_max = 0; + if (((table->count * 2) + 1) < (table->max * table->load_factor01)) + new_max = table->max; // "Mostly tombstones" branch + else + new_max = table->max * 2; + + result.needed_resize = true; + result.success = DN_HTableResize(table, new_max); } return result; } -template -DN_DSMapResult DN_DSMapMake(DN_DSMap *map, DN_DSMapKey key) +DN_API DN_HTableAddResult DN_HTableMake(DN_HTable* table, void* key) { - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - DN_U32 index = DN_DSMapHashToSlotIndex(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_DSMapResize(map, map->size * 2)) - return result; - result = DN_DSMapMake(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_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); - } + // NOTE: First do the lookup ignoring tombstones. This probes the chain looking for the existence + // of the value. + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + bool load_factor_is_good = true; + if (lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) { + // NOTE: If it exists, this is the easy path, we can update the slot returned in the lookup. + // + // Note that we do not check if `hash_u32` is the empty value, because that empty value might + // be at the end of a probe chain. If we were to use that slot, this key-value could potentially + // be placed spatially far away from the optimal position (if there were tombstones closer + // that we skipped over, we want to use those). Hence we take the else branch and do the lookup + // again, this time allowing us to match the tombstone or the first empty slot. } 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_DSMapSet(DN_DSMap *map, DN_DSMapKey key, T const &value) -{ - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - result = DN_DSMapMake(map, key); - result.slot->value = value; - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyU64(DN_DSMap const *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key, value); - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyStr8(DN_DSMap const *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key); - return result; -} - -template -bool DN_DSMapResize(DN_DSMap *map, DN_U32 size) -{ - if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size) - return false; - - DN_Arena *prev_arena = map->arena; - DN_MemList *new_mem = prev_arena->mem; - DN_MemList prev_mem = *prev_arena->mem; - prev_arena->mem = &prev_mem; - - *new_mem = {}; - new_mem->funcs = prev_mem.funcs; - new_mem->flags = prev_mem.flags; - - DN_Arena new_arena = {}; - new_arena.mem = new_mem; - - DN_DSMap new_map = DN_DSMapInit(&new_arena, size, map->flags); - if (!DN_DSMapIsValid(&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_DSMapSet(&new_map, old_key, old_slot->value); - } - - if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) - DN_DSMapDeinit(map, DN_ZMem_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_DSMapErase(DN_DSMap *map, DN_DSMapKey key) -{ - if (!DN_DSMapIsValid(map)) - return false; - - DN_U32 index = DN_DSMapHashToSlotIndex(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_PoolDealloc(&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_DSMapHashToSlotIndex(map, last_slot->key); - map->hash_to_slot[hash_to_slot_index] = slot_index; - *last_slot = {}; // TODO: Optional? - } + // NOTE: The value does not exist, look it up again but match on the first tombstone/empty slot + lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_Yes); + bool add_will_use_new_slot = lookup.slot.hash_u32 == DN_HTableHashSentinel_Empty; + if (add_will_use_new_slot) { + DN_HTableResizeResult resize = DN_HTableGrowMaybe(table, 1); + if (resize.success) + lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_Yes); + else + load_factor_is_good = false; } } - 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_DSMapResize(map, map->size / 2); + // NOTE: Update the table slot/kv + DN_HTableAddResult result = {}; + if (load_factor_is_good) { + result.success = true; + result.slot = lookup.slot; + if (result.slot.hash_u32 == DN_HTableHashSentinel_Empty) { + table->count++; + } else if (result.slot.hash_u32 == DN_HTableHashSentinel_Tomb) { + DN_Assert(table->tombs_count); + table->tombs_count--; + table->count++; + } else { + result.existed = true; + } - return true; -} - -template -bool DN_DSMapEraseKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -bool DN_DSMapEraseKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap const *map, void const *data, DN_USize count) -{ - DN_Assert(count > 0 && count <= UINT32_MAX); - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_Buffer; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) count; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_USize count) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_BufferAsU64NoHash; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) count; - DN_Assert(count >= sizeof(result.hash)); - DN_Memcpy(&result.hash, data, sizeof(result.hash)); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyU64(DN_DSMap const *map, DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64; - result.u64 = u64; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap const *map, DN_Str8 string) -{ - DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.count); - return result; -} - -// NOTE: DN_DSMap -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(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_DSMapKeyEquals(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; + // NOTE: Update the slot with the given data + if (result.existed) { + DN_Assert(result.slot.hash_u32 == lookup.key_hash); + } else { + result.slot.hash_u32 = lookup.key_hash; + DN_Assert(result.slot.hash); + DN_Assert(result.slot.key); + DN_Memcpy(result.slot.hash, &lookup.key_hash, sizeof(lookup.key_hash)); + DN_Memcpy(result.slot.key, key, table->size_of_key); } } return result; } -DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) +DN_API DN_HTableAddResult DN_HTableAdd(DN_HTable* table, void* key, void* value) { - bool result = DN_DSMapKeyEquals(lhs, rhs); + DN_HTableAddResult result = DN_HTableMake(table, key); + if (result.success) + DN_Memcpy(result.slot.value, value, table->size_of_value); + return result; +} + +DN_API bool DN_HTableDel(DN_HTable *table, void *key) +{ + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + bool result = lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid; + if (result) { + DN_HTableHashType sentinel = DN_HTableHashSentinel_Tomb; + DN_Memcpy(lookup.slot.hash, &sentinel, sizeof(sentinel)); + table->tombs_count++; + DN_Assert(table->count); + table->count--; + } return result; } -#endif // defined(__cplusplus) DN_API void DN_BinPackU64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item) { @@ -9044,7 +9121,7 @@ DN_API void DN_CSVPackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *v { if (serialise == DN_CSVSerialise_Read) { DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value); DN_Assert(to_u64.success); *value = to_u64.value; } else { @@ -9152,6 +9229,1992 @@ DN_API bool DN_CSVPackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise) return result; } +DN_API DN_TestCore DN_TestInit(DN_Arena *arena) +{ + DN_TestCore result = {}; + result.arena = arena; + result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); + DN_PArrayGrowFromPool(result.groups, &result.groups_max, &result.pool, 32); + return result; +} + +DN_API void DN_TestGroupBeginF(DN_TestCore *test, char const *fmt, ...) +{ + DN_AssertF(!test->curr_group, "Previous test group (%.*s) must be ended before starting a new group", DN_Str8PrintFmt(test->curr_group->name)); + + // NOTE: ALlocate the group + DN_PArrayGrowIfNeededFromPool(test->groups, test->groups_count, &test->groups_max, &test->pool, 1); + DN_TestGroup *group = DN_PArrayMakeZ(test->groups, &test->groups_count, test->groups_max); + DN_PArrayGrowFromPool(group->entries, &group->entries_max, &test->pool, 32); + + // NOTE: Set up the group + va_list args; + va_start(args, fmt); + group->name = DN_Str8FromFmtVArena(test->arena, fmt, args); + va_end(args); + + // NOTE: Mark as active + test->curr_group = group; + group->ts_begin = DN_OS_PerfCounterNow(); +} + +DN_API void DN_TestGroupEnd(DN_TestCore *test) +{ + DN_TestGroup *group = test->curr_group; + DN_AssertF(group, "Test group must be started before attempting to end it"); + group->ts_end = DN_OS_PerfCounterNow(); + test->curr_group = nullptr; +} + +DN_API void DN_TestBeginF(DN_TestCore *test, char const *fmt, ...) +{ + DN_AssertF(test->curr_group, "Test group must be begun first before creating a test"); + + // NOTE:Allocate test entry + DN_TestGroup *group = test->curr_group; + DN_PArrayGrowIfNeededFromPool(group->entries, group->entries_count, &group->entries_max, &test->pool, 1); + DN_AssertF(!group->curr_entry, "Test (%.*s) must be ended before starting another test in the group (%.*s)", DN_Str8PrintFmt(group->curr_entry->name), DN_Str8PrintFmt(group->name)); + DN_TestEntry *entry = DN_PArrayMakeZ(group->entries, &group->entries_count, group->entries_max); + + // NOTE: Fill in test entry + va_list args; + va_start(args, fmt); + entry->name = DN_Str8FromFmtVArena(test->arena, fmt, args); + va_end(args); + + // NOTE: Mark as active + group->curr_entry = entry; + entry->ts_begin = DN_OS_PerfCounterNow(); + test->total_count++; +} + +DN_API void DN_TestEnd(DN_TestCore *test) +{ + DN_TestGroup *group = test->curr_group; + DN_AssertF(group, "Test group must be started before attempting to end a test in it"); + DN_TestEntry *entry = group->curr_entry; + DN_AssertF(entry, "Test must be started before attempting to end a test in group (%.*s)", DN_Str8PrintFmt(group->name)); + + entry->ts_end = DN_OS_PerfCounterNow(); + if (entry->failed) + test->total_failed++; + else + test->total_passed++; + group->curr_entry = nullptr; +} + +DN_API DN_TestDiagnosticRow *DN_TestVerifySetupFmtV(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool verify_failed, char const *fmt, va_list args) +{ + DN_AssertF(test->curr_group, "Test group must be started before attempting to verify a test invariant"); + DN_AssertF(test->curr_group->curr_entry, "Test must be started before attempting to verify a test invariant in a group (%.*s)", DN_Str8PrintFmt(test->curr_group->name)); + DN_TestDiagnosticRow *result = nullptr; + if (verify_failed) { + DN_TestGroup *group = test->curr_group; + DN_TestEntry *entry = group->curr_entry; + entry->failed = true; + + DN_PArrayGrowIfNeededFromPool(entry->diagnostics, entry->diagnostics_count, &entry->diagnostics_max, &test->pool, 1); + result = DN_PArrayMakeZ(entry->diagnostics, &entry->diagnostics_count, entry->diagnostics_max); + result->expr = expr; + result->call_site = call_site; + result->message = DN_Str8FromFmtVArena(test->arena, fmt, args); + } + return result; +} + +DN_API void DN_TestVerifyExprF_(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool expr_result, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, /*verify_failed=*/ !expr_result, fmt, args); + va_end(args); + if (row) + row->invariant = DN_Str8FromFmtArena(test->arena, "%s (expected %s)", expr_result ? "true" : "false", expr_result ? "false" : "true"); +} + +DN_API void DN_TestVerifyF64Fmt(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_F64 val, DN_F64 expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + row->invariant = DN_Str8FromFmtArena(test->arena, "%f %.*s %f (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + } +} + +DN_API void DN_TestVerifyISizeF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_ISize val, DN_ISize expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(4) in call to 'DN_Str8FromFmtArena' must be the address of a string. Actual type: 'int'. + DN_MSVC_WARNING_DISABLE(6328) // Size mismatch: '__int64' passed as _Param_(3) when 'unsigned int' is required in call to 'DN_Str8FromFmtArena' + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'. + row->invariant = DN_Str8FromFmtArena(test->arena, "%'zd %.*s %'zd (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + DN_MSVC_WARNING_POP + } +} + +DN_API void DN_TestVerifyUSizeF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_USize val, DN_USize expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(4) in call to 'DN_Str8FromFmtArena' must be the address of a string. Actual type: 'int'. + DN_MSVC_WARNING_DISABLE(6328) // Size mismatch: '__int64' passed as _Param_(3) when 'unsigned int' is required in call to 'DN_Str8FromFmtArena' + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'. + row->invariant = DN_Str8FromFmtArena(test->arena, "%'zu %.*s %'zu (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + DN_MSVC_WARNING_POP + } +} + +DN_API void DN_TestVerifyStr8F(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 str8, DN_Str8 expect, bool expect_eq, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + if (expect_eq) + verify_failed = !DN_Str8EqSensitive(str8, expect); + else + verify_failed = DN_Str8EqSensitive(str8, expect); + + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, verify_failed, fmt, args); + va_end(args); + if (row) { + if (expect_eq) + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" != \"%.*s\"", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + else + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" == \"%.*s\"", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } +} + +DN_API void DN_TestVerifyBytesF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 bytes, DN_Str8 expect, bool expect_eq, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + if (expect_eq) + verify_failed = !DN_Str8EqSensitive(bytes, expect); + else + verify_failed = DN_Str8EqSensitive(bytes, expect); + + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, verify_failed, fmt, args); + va_end(args); + if (row) { + DN_Str8 bytes_hex = DN_HexFromStr8BytesArena(bytes, test->arena, DN_TrimLeadingZero_No); + DN_Str8 expect_hex = DN_HexFromStr8BytesArena(expect, test->arena, DN_TrimLeadingZero_No); + if (expect_eq) + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" != \"%.*s\"", DN_Str8PrintFmt(bytes_hex), DN_Str8PrintFmt(expect_hex)); + else + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" == \"%.*s\"", DN_Str8PrintFmt(bytes_hex), DN_Str8PrintFmt(expect_hex)); + } +} + +DN_API DN_Str8 DN_Str8FromTestCore(DN_TestCore const *test, DN_Arena *arena, DN_Str8FromTestCoreFlags flags) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_USize padding_count = 100; + DN_Str8 padding_line = DN_Str8FillF(&scratch.arena, padding_count, "."); + if (flags & DN_Str8FromTestCoreFlags_Colour) + padding_line = DN_Str8FromFmtANSIColourU8RGBArena(DN_ANSIColourMode_Fg, 64, 64, 64, &scratch.arena, "%.*s", DN_Str8PrintFmt(padding_line)); + + DN_V3F32 const good_colour = DN_V3F32From3N(0, 255, 0); + DN_V3F32 const bad_colour = DN_V3F32From3N(255, 0, 0); + DN_Str8Builder builder = DN_Str8BuilderFromArena(arena); + for (DN_ForItSize(group_it, DN_TestGroup const, test->groups, test->groups_count)) { + DN_TestGroup const* group = group_it.data; + DN_USize group_successes = 0; + DN_USize group_failures = 0; + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(5) in call to 'DN_Str8BuilderAppendF' must be the address of a string. Actual type: 'const unsigned __int64'. + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8BuilderAppendF'. + DN_Str8BuilderAppendF(&builder, "[%.*s] (%'zu test%s)\n", DN_Str8PrintFmt(group->name), group->entries_count, (group->entries_count > 1) ? "s" : ""); + DN_MSVC_WARNING_POP + for (DN_ForItSize(entry_it, DN_TestEntry const, group->entries, group->entries_count)) { + DN_TestEntry const* entry = entry_it.data; + if (entry->failed) + group_failures++; + else + group_successes++; + + // NOTE: Extract the test name and pad the line with dots (.) + DN_Str8 test_prefix = DN_Str8FromFmtArena(&scratch.arena, " [%zu/%zu] %.*s ", entry_it.index + 1, group->entries_count, DN_Str8PrintFmt(entry->name)); + if (test_prefix.count < padding_line.count) { + DN_USize remaining = padding_line.count - test_prefix.count; + DN_Str8 padder = DN_Str8Subset(padding_line, 0, remaining); + test_prefix = DN_Str8AppendF(&scratch.arena, test_prefix, "%.*s", DN_Str8PrintFmt(padder)); + } + + // NOTE: Construct the test line + DN_Str8 outcome_str8 = entry->failed ? DN_Str8Lit("FAILED") : DN_Str8Lit("OK"); + if (flags & DN_Str8FromTestCoreFlags_Colour) { + if (entry->failed) + outcome_str8 = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, bad_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(outcome_str8)); + else + outcome_str8 = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, good_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(outcome_str8)); + } + + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(entry->ts_begin, entry->ts_end); + DN_Str8BuilderAppendF(&builder, "%.*s %.*s (%.3fms)\n", DN_Str8PrintFmt(test_prefix), DN_Str8PrintFmt(outcome_str8), elapsed_ms); + + // NOTE: Output the test diagnostics, populated on failure + if (entry->failed) { + for (DN_ForItSize(row_it, DN_TestDiagnosticRow, entry->diagnostics, entry->diagnostics_count)) { + DN_USize row_count = 1; + DN_Str8TableFlags table_flags = DN_Str8TableFlags_None; + if (row_it.index == 0) { + row_count++; // Header + table_flags |= DN_Str8TableFlags_HasHeader; + } + + DN_USize col_count = 3; // Expression, Eval, Call site + DN_USize row_index = 0; + DN_Str8 *rows_str8 = DN_ArenaNewArrayZ(&scratch.arena, DN_Str8, row_count * col_count); + if (row_it.index == 0) { + rows_str8[row_index++] = DN_Str8Lit("Expr."); + rows_str8[row_index++] = DN_Str8Lit("Eval."); + rows_str8[row_index++] = DN_Str8Lit("Location"); + } + + DN_TestDiagnosticRow *row = row_it.data; + DN_Str8 file_name = DN_Str8FileNameFromPath(row->call_site.file); + rows_str8[row_index++] = row->expr; + rows_str8[row_index++] = row->invariant; + rows_str8[row_index++] = DN_Str8FromFmtArena(&scratch.arena, "%.*s:%u", DN_Str8PrintFmt(file_name), row->call_site.line); + + DN_Str8 table = DN_Str8Table(rows_str8, row_count, col_count, table_flags, &scratch.arena); + table = DN_Str8PadNewLinesArena(table, DN_Str8Lit(" "), &scratch.arena); + DN_Str8BuilderAppendF(&builder, " %.*s\n", DN_Str8PrintFmt(table)); + if (row->message.count) { + DN_Str8 line_break = DN_Str8LineBreakArena(row->message, 100, DN_Str8Lit("\n "), DN_Str8LineBreakMode_AtWord, &scratch.arena); + DN_Str8BuilderAppendF(&builder, " User Message: %.*s\n", DN_Str8PrintFmt(line_break)); + } + } + DN_Str8BuilderAppendF(&builder, "\n"); + } + } + + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(group->ts_begin, group->ts_end); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(3) in call to 'DN_Str8BuilderAppendF' must be the address of a string. Actual type: 'const unsigned __int64'. + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8BuilderAppendF'. + DN_Str8 group_prefix = DN_Str8FromFmtArena(&scratch.arena, "%'zu/%'zu test%s", group_successes, group->entries_count, group_successes > 1 ? "s" :""); + DN_MSVC_WARNING_POP + if (flags & DN_Str8FromTestCoreFlags_Colour) { + if (group_failures == 0) + group_prefix = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, good_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(group_prefix)); + else + group_prefix = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, bad_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(group_prefix)); + } + DN_Str8BuilderAppendF(&builder, "\n %.*s passed in [%.*s] %.3fms (%zu failed)\n", DN_Str8PrintFmt(group_prefix), DN_Str8PrintFmt(group->name), elapsed_ms, group_failures); + } + + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + return result; +} + +#if DN_WITH_TESTS +#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) +// 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; + DN_U32 f_1_ECX_ = 0; + DN_U32 f_1_EDX_ = 0; + DN_U32 f_7_EBX_ = 0; + DN_U32 f_7_ECX_ = 0; + DN_U32 f_81_ECX_ = 0; + DN_U32 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 cpui[4]; + + __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)); + } + + *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; + + if (result.nIds_ >= 1) { + result.f_1_ECX_ = result.data_[1][2]; + result.f_1_EDX_ = result.data_[1][3]; + } + + if (result.nIds_ >= 7) { + result.f_7_EBX_ = result.data_[7][1]; + result.f_7_ECX_ = result.data_[7][2]; + } + + __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)); + } + + if (result.nExIds_ >= 0x80000001) { + result.f_81_ECX_ = result.extdata_[1][2]; + result.f_81_EDX_ = result.extdata_[1][3]; + } + + 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; +} +#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(6262) // Function uses '23524' bytes of stack. Consider moving some data to heap. +DN_API DN_TestCore DN_TestSuite(DN_Arena *arena_) +{ + DN_TestCore result = DN_TestInit(arena_); + for (DN_TestGroupScopeF(&result, "Base")) { + // NOTE: AgeStr8From + { + for (DN_TestScopeF(&result, "[AgeStr8FromMsU64] '1001' in seconds converts to '1s 1ms'")) { + DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms); + DN_Str8 expect = DN_Str8Lit("1s 1ms"); + DN_TestVerifyStr8Eq(&result, DN_Str8FromStruct(&str8), expect); + } + + for (DN_TestScopeF(&result, "[AgeStr8FromMsU64] '1001' in seconds converts to '1.001s' (fractional)")) { + DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_FractionalSec); + DN_Str8 expect = DN_Str8Lit("1.001s"); + DN_TestVerifyStr8Eq(&result, DN_Str8FromStruct(&str8), expect); + } + } + + // NOTE: TicketMutex + { + for (DN_TestScopeF(&result, "[TicketMutex] Start and stop")) { + DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + DN_TicketMutex_End(&mutex); + DN_TestVerifyUSizeEq(&result, mutex.ticket, mutex.serving); + } + + for (DN_TestScopeF(&result, "[TicketMutex] 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_TestVerifyExpr(&result, DN_Cast(bool) DN_TicketMutex_CanLock(&mutex, ticket_b) == false); + DN_TestVerifyExpr(&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_TestVerifyUSizeEq(&result, mutex.ticket, mutex.serving); + DN_TestVerifyUSizeEq(&result, mutex.ticket, ticket_b + 1); + } + } + + // NOTE: QSort + { + for (DN_TestScopeF(&result, "[QSort] Str8 Lexicographic Ascending")) { + DN_Str8 list[] = { + DN_Str8Lit("z_last"), + DN_Str8Lit("m_middle"), + DN_Str8Lit("a_first"), + DN_Str8Lit("version-1.2.10"), + DN_Str8Lit("version-1.2.2"), + DN_Str8Lit("version-1.10.0"), + }; + + DN_QSortStr8LexicographicAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Insensitive); + + DN_USize list_index = 0; + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("a_first")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("m_middle")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.10.0")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("z_last")); + } + + for (DN_TestScopeF(&result, "[QSort] Str8 Natural")) { + DN_Str8 list[] = { + DN_Str8Lit("item10"), + DN_Str8Lit("item2"), + DN_Str8Lit("item1"), + DN_Str8Lit("item20"), + DN_Str8Lit("item12"), + DN_Str8Lit("Afile"), + DN_Str8Lit("file2"), + DN_Str8Lit("file10"), + DN_Str8Lit("file1"), + DN_Str8Lit("z_last"), + DN_Str8Lit("m_middle"), + DN_Str8Lit("a_first"), + DN_Str8Lit("version-1.2.10"), + DN_Str8Lit("version-1.2.2"), + DN_Str8Lit("version-1.10.0"), + }; + + DN_QSortStr8NaturalAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Sensitive); + + DN_USize list_index = 0; + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("Afile")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("a_first")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file1")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item1")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item12")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item20")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("m_middle")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.10.0")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("z_last")); + } + } + + // NOTE: CPUID + #if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + { + for (DN_TestScopeF(&result, "[CPU] Query CPUID")) { + DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); + DN_CPUReport cpu_report = DN_CPUGetReport(); + + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); + } + } + #endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + + // NOTE: FmtAppendTruncate + { + for (DN_TestScopeF(&result, "[Fmt] FmtAppendTruncate truncates with 3 dots")) { + char buf[8] = {}; + DN_USize buf_size = 0; + DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1)); + DN_Str8 expect = DN_Str8Lit("This..."); + DN_TestVerifyExpr(&result, buf_str8.truncated); + DN_TestVerifyStr8EqF(&result, buf_str8.str8, expect, "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect)); + } + } + + // NOTE: Atomics + { + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(28113) + DN_MSVC_WARNING_DISABLE(28112) + { + for (DN_TestScopeF(&result, "[Atomics] AtomicAddU32")) { + DN_U32 val = 0; + DN_AtomicAddU32(&val, 1); + DN_TestVerifyUSizeEq(&result, val, 1); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicAddU64")) { + uint64_t val = 0; + DN_AtomicAddU64(&val, 1); + DN_TestVerifyExprF(&result, val == 1, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSubU32")) { + DN_U32 val = 1; + DN_AtomicSubU32(&val, 1); + DN_TestVerifyUSizeEq(&result, val, 0); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSubU64")) { + uint64_t val = 1; + DN_AtomicSubU64(&val, 1); + DN_TestVerifyExprF(&result, val == 0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSetValue32")) { + DN_U32 a = 0; + DN_U32 b = 111; + DN_AtomicSetValue32(&a, b); + DN_TestVerifyUSizeEq(&result, a, b); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSetValue64")) { + int64_t a = 0; + int64_t b = 111; + DN_AtomicSetValue64(DN_Cast(uint64_t *) & a, b); + DN_TestVerifyExprF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); + } + + for (DN_TestScopeF(&result, "[Intrinsics] CPUGetTSC compile check")) { + DN_CPUGetTSC(); + } + + for (DN_TestScopeF(&result, "[Intrinsics] CompilerReadBarrierAndCPUReadFence compile check")) { + DN_CompilerReadBarrierAndCPUReadFence; + } + + for (DN_TestScopeF(&result, "[Intrinsics] CompilerWriteBarrierAndCPUWriteFence compile check")) { + DN_CompilerWriteBarrierAndCPUWriteFence; + } + } + DN_MSVC_WARNING_POP + } + + // NOTE: Arena + { + for (DN_TestScopeF(&result, "[Arena] Reused memory is zeroed out")) { + uint8_t alignment = 1; + DN_USize alloc_size = DN_Kilobytes(128); + DN_MemList mem = DN_MemListFromHeap(0, 0, DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + uintptr_t first_ptr_address = 0; + { + DN_U64 mem_p = DN_MemListPos(arena.mem); + void *ptr = DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); + first_ptr_address = DN_Cast(uintptr_t) ptr; + DN_Memset(ptr, 'z', alloc_size); + DN_MemListPopTo(arena.mem, mem_p); + } + + char *ptr = DN_Cast(char *) DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); + + DN_TestVerifyUSizeEq(&result, first_ptr_address, DN_Cast(uintptr_t) ptr); + + for (DN_USize i = 0; i < alloc_size; i++) + DN_TestVerifyExpr(&result, ptr[i] == 0); + } + + for (DN_TestScopeF(&result, "[Arena] Grows naturally, 1mb + 4mb")) { + DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + char *ptr_1mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(1), DN_ZMem_Yes); + char *ptr_4mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(4), DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_1mb != nullptr); + DN_TestVerifyExpr(&result, ptr_4mb != nullptr); + + DN_MemBlock const *block_4mb_begin = arena.mem->curr; + char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev; + DN_TestVerifyExprF(&result, block_1mb_begin != nullptr, "New block should have been allocated"); + char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_TestVerifyExprF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_TestVerifyExprF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_TestVerifyExprF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + + for (DN_TestScopeF(&result, "[Arena] Grows naturally, 1mb, temp memory 4mb")) { + DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + char *ptr_1mb = DN_Cast(char *) DN_ArenaAlloc(&arena, DN_Megabytes(1), 1, DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_1mb != nullptr); + + DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); + { + char *ptr_4mb = DN_ArenaNewArray(&temp, char, DN_Megabytes(4), DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_4mb != nullptr); + + DN_MemBlock const *block_4mb_begin = arena.mem->curr; + char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev; + char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_TestVerifyExprF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_TestVerifyExprF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_TestVerifyExprF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + DN_ArenaTempEnd(&temp, DN_ArenaReset_Yes); + DN_TestVerifyExpr(&result, arena.mem->curr->prev == nullptr); + DN_TestVerifyExprF(&result, + arena.mem->curr->reserve >= DN_Megabytes(1), + "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", + (arena.mem->curr->reserve / 1024 / 1024), + arena.mem->curr->reserve, + DN_Megabytes(1)); + } + } + + // NOTE: BytesHex + { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_DEFER { DN_TCScratchEnd(&scratch); }; + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0x123")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x123")); + DN_TestVerifyExprF(&result, val == 0x123, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0xFFFF")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xFFFF")); + DN_TestVerifyExprF(&result, val == 0xFFFF, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert FFFF")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("FFFF")); + DN_TestVerifyExprF(&result, val == 0xFFFF, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert abCD")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("abCD")); + DN_TestVerifyExprF(&result, val == 0xabCD, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0xabCD")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xabCD")); + DN_TestVerifyExprF(&result, val == 0xabCD, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0x")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x")); + DN_TestVerifyExprF(&result, val == 0x0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0X")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0X")); + DN_TestVerifyExprF(&result, val == 0x0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 3")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("3")); + DN_TestVerifyExprF(&result, val == 3, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert f")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("f")); + DN_TestVerifyExpr(&result, res.success); + DN_TestVerifyExpr(&result, res.value == 0xf); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert g")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("g")); + DN_TestVerifyExpr(&result, !res.success); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert -0x3")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("-0x3")); + DN_TestVerifyExpr(&result, !res.success); + } + + DN_U32 number = 0xd095f6; + for (DN_TestScopeF(&result, "[BytesHex] Convert %x to string", number)) { + DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, number_hex, DN_Str8Lit("f695d000"), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); + } + + number = 0xf6ed00; + for (DN_TestScopeF(&result, "[BytesHex] Convert %x to string", number)) { + DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, number_hex, DN_Str8Lit("00edf600"), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); + } + + DN_Str8 hex = DN_Str8Lit("0xf6ed00"); + for (DN_TestScopeF(&result, "[BytesHex] Convert %.*s to bytes", DN_Str8PrintFmt(hex))) { + DN_Str8 bytes = DN_BytesFromHexArena(hex, &scratch.arena); + DN_TestVerifyStr8EqF(&result, + bytes, + DN_Str8Lit("\xf6\xed\x00"), + "number_hex=%.*s", + DN_Str8PrintFmt(DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No))); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert empty bytes to string")) { + DN_Str8 bytes = DN_Str8Lit(""); + DN_Str8 as_hex = DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, as_hex, DN_Str8Lit(""), "as_hex=%.*s", DN_Str8PrintFmt(as_hex)); + } + } + + // NOTE: BinarySearch + { + for (DN_TestScopeF(&result, "[BinarySearch] Search array of 1 item")) { + DN_U32 array[] = {1}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + } + + for (DN_TestScopeF(&result, "[BinarySearch] Search array of 3 items")) { + DN_U32 array[] = {1, 2, 3}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + } + + for (DN_TestScopeF(&result, "[BinarySearch] Search array with duplicate items")) { + DN_U32 array[] = {1, 1, 2, 2, 3}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + } + } + + // NOTE: IArray + { + for (DN_TestScopeF(&result, "[IArray] Make item")) { + struct CustomArray { + int *data; + DN_USize count; + DN_USize max; + }; + + int array_buffer[16]; + CustomArray array = {}; + array.data = array_buffer; + array.max = DN_ArrayCountU(array_buffer); + + int *item = DN_IArrayMake(&array, DN_ZMem_Yes); + DN_TestVerifyExpr(&result, item != nullptr); + DN_TestVerifyUSizeEq(&result, array.count, 1); + } + } + + // NOTE: Array + { + for (DN_TestScopeF(&result, "[Array] Positive count, middle of array, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 2); + DN_TestVerifyUSizeEq(&result, erase.it_index, 2); + DN_TestVerifyUSizeEq(&result, size, 8); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Negative count, middle of array, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Stable); + int expected[] = {0, 1, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 3); + DN_TestVerifyUSizeEq(&result, erase.it_index, 1); + DN_TestVerifyUSizeEq(&result, size, 7); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] count = -1, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -1, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 1); + DN_TestVerifyUSizeEqF(&result, erase.it_index, 3, "lhs=%zu", erase.it_index); + DN_TestVerifyUSizeEq(&result, size, 9); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Positive count, unstable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Unstable); + int expected[] = {0, 1, 2, 8, 9, 5, 6, 7}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 2); + DN_TestVerifyUSizeEq(&result, erase.it_index, 2); + DN_TestVerifyUSizeEq(&result, size, 8); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Negative count, unstable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Unstable); + int expected[] = {0, 1, 7, 8, 9, 5, 6}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 3); + DN_TestVerifyUSizeEq(&result, erase.it_index, 1); + DN_TestVerifyUSizeEq(&result, size, 7); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Edge case - begin_index at start, negative count")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 0, -2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 0); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Edge case - begin_index at end, positive count")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 9, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 1); + DN_TestVerifyUSizeEq(&result, erase.it_index, 9); + DN_TestVerifyUSizeEq(&result, size, 9); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - count = 0")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 0, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - null data")) { + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(nullptr, &size, sizeof(int), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 10); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - null size")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, NULL, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - empty array")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 0; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 0); + } + + for (DN_TestScopeF(&result, "[Array] Out-of-bounds begin_index")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 15, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 9); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + } + + // NOTE: M4 + { + for (DN_TestScopeF(&result, "[M4] Simple translate and scale matrix")) { + DN_M4 translate = DN_M4TranslateF(1, 2, 3); + DN_M4 scale = DN_M4ScaleF(2, 2, 2); + DN_M4 mul_result = DN_M4Mul(translate, scale); + + const DN_M4 EXPECT = { + { + {2, 0, 0, 0}, + {0, 2, 0, 0}, + {0, 0, 2, 0}, + {1, 2, 3, 1}, + } + }; + + DN_TestVerifyExprF(&result, + memcmp(mul_result.columns, EXPECT.columns, sizeof(EXPECT)) == 0, + "\nresult =\n%s\nexpected =\n%s", + DN_M4ColumnMajorString(mul_result).data, + DN_M4ColumnMajorString(EXPECT).data); + } + } + + // NOTE: OS + #if DN_WITH_OS + { + for (DN_TestScopeF(&result, "[OS] Generate secure RNG 32 bytes")) { + char const ZERO[32] = {}; + char buf[32] = {}; + DN_OS_GenBytesSecure(buf, DN_ArrayCountU(buf)); + DN_TestVerifyExpr(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0); + } + + for (DN_TestScopeF(&result, "[OS] Query executable directory")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 os_result = DN_OS_EXEDir(&scratch.arena); + DN_TestVerifyExpr(&result, os_result.count > 0); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(os_result), "result(%zu): %.*s", os_result.count, DN_Str8PrintFmt(os_result)); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[OS] DN_OS_PerfCounterNow")) { + uint64_t os_result = DN_OS_PerfCounterNow(); + DN_TestVerifyExpr(&result, os_result != 0); + } + + for (DN_TestScopeF(&result, "[OS] Consecutive ticks are ordered")) { + uint64_t a = DN_OS_PerfCounterNow(); + uint64_t b = DN_OS_PerfCounterNow(); + DN_TestVerifyExprF(&result, b >= a, "a: %" PRIu64 ", b: %" PRIu64, a, b); + } + + for (DN_TestScopeF(&result, "[OS] 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_TestVerifyF64LessThanEq(&result, s, ms); + DN_TestVerifyF64LessThanEq(&result, ms, us); + DN_TestVerifyF64LessThanEq(&result, us, ns); + } + + for (DN_TestScopeF(&result, "[OS] Make directory recursive \"abcd/efgh\"")) { + DN_TestVerifyExprF(&result, DN_OS_PathMakeDir(DN_Str8Lit("abcd/efgh")), "Failed to make directory"); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd")), "Directory was not made"); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd/efgh")), "Subdirectory was not made"); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd")) == false, "This function should only return true for files"); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd/efgh")) == false, "This function should only return true for files"); + DN_TestVerifyExprF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd/efgh")), "Failed to delete directory"); + DN_TestVerifyExprF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd")), "Failed to cleanup directory"); + } + + for (DN_TestScopeF(&result, "[OS] File write, read, copy, move and delete")) { + DN_Str8 const SRC_FILE = DN_Str8Lit("dn_result_file"); + DN_B32 write_result = DN_OS_FileWriteAll(SRC_FILE, DN_Str8Lit("1234"), nullptr); + DN_TestVerifyExpr(&result, write_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(SRC_FILE)); + + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 read_file = DN_OS_FileReadAllArena(&scratch.arena, SRC_FILE, nullptr); + DN_TestVerifyExprF(&result, read_file.count > 0, "Failed to load file"); + DN_TestVerifyExprF(&result, read_file.count == 4, "File read wrong amount of bytes (%zu)", read_file.count); + DN_TestVerifyStr8EqF(&result, read_file, DN_Str8Lit("1234"), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.count, DN_Str8PrintFmt(read_file)); + + DN_Str8 const COPY_FILE = DN_Str8Lit("dn_result_file_copy"); + DN_B32 copy_result = DN_OS_FileCopy(SRC_FILE, COPY_FILE, true, nullptr); + DN_TestVerifyExpr(&result, copy_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(COPY_FILE)); + + DN_Str8 const MOVE_FILE = DN_Str8Lit("dn_result_file_move"); + DN_B32 move_result = DN_OS_FileMove(COPY_FILE, MOVE_FILE, true, nullptr); + DN_TestVerifyExpr(&result, move_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(MOVE_FILE)); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(COPY_FILE) == false, "Moving a file should remove the original"); + + DN_B32 delete_src_file = DN_OS_PathDelete(SRC_FILE); + DN_B32 delete_moved_file = DN_OS_PathDelete(MOVE_FILE); + DN_TestVerifyExpr(&result, delete_src_file); + DN_TestVerifyExpr(&result, delete_moved_file); + + 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_TestVerifyExpr(&result, delete_non_existent_moved_file == false); + DN_TestVerifyExpr(&result, delete_non_existent_src_file == false); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[OS] Wait timeout")) { + DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); + DN_DEFER { DN_OS_SemaphoreDeinit(&sem); }; + + DN_U64 begin = DN_OS_PerfCounterNow(); + DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 100); + DN_U64 end = DN_OS_PerfCounterNow(); + DN_TestVerifyUSizeEqF(&result, wait_result, DN_OSSemaphoreWaitResult_Timeout, "Received wait result %zu", wait_result); + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); + DN_TestVerifyExprF(&result, elapsed_ms >= 80 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); + } + + for (DN_TestScopeF(&result, "[OS] Wait success")) { + DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); + DN_DEFER { DN_OS_SemaphoreDeinit(&sem); }; + + DN_OS_SemaphoreIncrement(&sem, 1); + DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 0); + DN_TestVerifyUSizeEqF(&result, wait_result, DN_OSSemaphoreWaitResult_Success, "Received wait result %zu", wait_result); + } + + for (DN_TestScopeF(&result, "[OS] Lock mutex")) { + DN_OSMutex mutex = DN_OS_MutexInit(); + DN_DEFER { DN_OS_MutexDeinit(&mutex); }; + + DN_OS_MutexLock(&mutex); + DN_OS_MutexUnlock(&mutex); + } + + for (DN_TestScopeF(&result, "[OS] Lock and timeout condition variable")) { + DN_OSMutex mutex = DN_OS_MutexInit(); + DN_OSConditionVariable cv = DN_OS_ConditionVariableInit(); + DN_DEFER { + DN_OS_MutexDeinit(&mutex); + DN_OS_ConditionVariableDeinit(&cv); + }; + + DN_U64 begin = DN_OS_PerfCounterNow(); + DN_OS_ConditionVariableWait(&cv, &mutex, 100); + DN_U64 end = DN_OS_PerfCounterNow(); + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); + DN_TestVerifyExprF(&result, elapsed_ms >= 99 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); + } + } + #endif // #if DN_WITH_OS + + // NOTE: Rect + { + for (DN_TestScopeF(&result, "[Rect] No intersection")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From1N(0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(200, 0), DN_V2F32From2N(200, 200)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A's min intersects B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B's min intersects A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A's max intersects B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B's max intersects A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B contains A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A contains B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A equals B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = a; + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + } + + // NOTE: Strings + { + for (DN_TestScopeF(&result, "[Strings] Str8 literal")) { + DN_Str8 string = DN_Str8Lit("AB"); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TestVerifyExprF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_TestVerifyExprF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + } + + for (DN_TestScopeF(&result, "[Strings] C-string length")) { + DN_USize size = DN_CStr8Count("hello"); + DN_TestVerifyUSizeEqF(&result, size, 5, "size=%zu", size); + } + + char arena_base[512]; + for (DN_TestScopeF(&result, "[Strings] Format from arena")) { + DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5); + DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); + DN_TestVerifyStr8EqF(&result, str8, expect, "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Format from pool")) { + DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_Pool pool = DN_PoolFromArena(&arena, 0); + DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5); + DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); + DN_TestVerifyStr8EqF(&result, str8, expect, "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Str8x32 from U64")) { + DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' '); + DN_Str8 expect = DN_Str8Lit("123 456"); + DN_TestVerifyStr8EqF(&result, DN_Str8FromStruct(&str8), expect, "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Initialise with format string")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8FromFmtArena(&scratch.arena, "%s", "AB"); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TestVerifyExprF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_TestVerifyExprF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + DN_TestVerifyExprF(&result, string.data[2] == 0, "string[2]: %c", string.data[2]); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Copy string")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8Lit("AB"); + DN_Str8 copy = DN_Str8FromStr8Arena(string, &scratch.arena); + DN_TestVerifyUSizeEqF(&result, copy.count, 2, "size: %zu", copy.count); + DN_TestVerifyExprF(&result, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]); + DN_TestVerifyExprF(&result, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]); + DN_TestVerifyExprF(&result, copy.data[2] == 0, "copy[2]: %c", copy.data[2]); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Trim whitespace around string")) { + DN_Str8 string = DN_Str8TrimWhitespaceAround(DN_Str8Lit(" AB ")); + DN_TestVerifyStr8EqF(&result, string, DN_Str8Lit("AB"), "[string=%.*s]", DN_Str8PrintFmt(string)); + } + + for (DN_TestScopeF(&result, "[Strings] Allocate string from arena")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8AllocArena(2, DN_ZMem_No, &scratch.arena); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix with matching prefix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(input, DN_Str8Lit("nft/")); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("abc"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix with non matching prefix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(input, DN_Str8Lit(" ft/")); + DN_TestVerifyStr8EqF(&result, str_result, input, "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim suffix with matching suffix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimSuffixSensitive(input, DN_Str8Lit("abc")); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("nft/"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim suffix with non matching suffix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimSuffixSensitive(input, DN_Str8Lit("ab")); + DN_TestVerifyStr8EqF(&result, str_result, input, "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on non-digit string")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("@123string"), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, str_result == false); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on nullptr")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(nullptr, 0), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, str_result == false); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on string w/ 0 size")) { + char const buf[] = "@123string"; + DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(buf, 0), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, !str_result); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits success")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23"), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, DN_Cast(bool) str_result == true); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on whitespace")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23 "), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, DN_Cast(bool) str_result == false); + } + + // NOTE: DN_Str8BSplit + { + DN_Str8 delimiter = DN_Str8Lit("/"); + DN_Str8 input = DN_Str8Lit("abcdef"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("abcdef"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit(""), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + input = DN_Str8Lit("abc/def"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("abc"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("def"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + input = DN_Str8Lit("/abcdef"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit(""), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("abcdef"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + delimiter = DN_Str8Lit("-=-"); + input = DN_Str8Lit("123-=-456"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("123"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("456"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + } + + // NOTE: DN_I64FromStr8 + for (DN_TestScopeF(&result, "[Strings] To I64: Convert empty string")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit(""), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 0); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"-0\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-0"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 0); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"-1\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-1"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == -1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1.2\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1.2"), 0); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1,234\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,234"), ','); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1234); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1,2\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,2"), ','); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 12); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"12a3\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("12a3"), 0); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 12); + } + + // NOTE: DN_U64FromStr8 + for (DN_TestScopeF(&result, "[Strings] To U64: Convert empty string")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"-0\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-0")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"-1\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-1")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1.2\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1.2")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1,234\"")) { + DN_U64FromResult str_result = DN_U64FromStr8Delimiter(DN_Str8Lit("1,234"), DN_Str8Lit(",")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1,2\"")) { + DN_U64FromResult str_result = DN_U64FromStr8Delimiter(DN_Str8Lit("1,2"), DN_Str8Lit(",")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"12a3\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("12a3")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + // NOTE: DN_Str8Find + for (DN_TestScopeF(&result, "[Strings] Find char is not in buffer")) { + DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_Str8Lit("2"); + DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_TestVerifyExpr(&result, !str_result.found); + DN_TestVerifyUSizeEq(&result, str_result.index, 0); + DN_TestVerifyExpr(&result, str_result.match.data == nullptr); + DN_TestVerifyUSizeEq(&result, str_result.match.count, 0); + } + + for (DN_TestScopeF(&result, "[Strings] Find char is in buffer")) { + DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_Str8Lit("6"); + DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_TestVerifyExpr(&result, str_result.found); + DN_TestVerifyUSizeEq(&result, str_result.index, 2); + DN_TestVerifyExpr(&result, str_result.match.data[0] == '6'); + } + + // NOTE: DN_Str8FileNameFromPath + for (DN_TestScopeF(&result, "[Strings] File name from Windows path")) { + DN_Str8 buf = DN_Str8Lit("C:\\ABC\\str_result.exe"); + DN_Str8 str_result = DN_Str8FileNameFromPath(buf); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("str_result.exe"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] File name from Linux path")) { + DN_Str8 buf = DN_Str8Lit("/ABC/str_result.exe"); + DN_Str8 str_result = DN_Str8FileNameFromPath(buf); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("str_result.exe"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix")) { + DN_Str8 prefix = DN_Str8Lit("@123"); + DN_Str8 buf = DN_Str8Lit("@123string"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(buf, prefix); + DN_TestVerifyStr8Eq(&result, str_result, DN_Str8Lit("string")); + } + + // NOTE: DN_Str8TruncMiddle + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Short string is not truncated")) { + DN_Str8 str = DN_Str8Lit("Hello"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Exact boundary is not truncated")) { + DN_Str8 str = DN_Str8Lit("HelloWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 10); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("HelloWorld"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Long string is truncated in the middle")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Empty truncator concatenates head and tail")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit(""); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 10); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("HelloWorld"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: side_size of 0 returns just truncator")) { + DN_Str8 str = DN_Str8Lit("HelloWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 0, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 3); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("..."), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Null dest calculates size without writing")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyExpr(&result, res.str8.data == nullptr); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: count_req is consistent between dry-run and actual")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult dry = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); + char dest[64] = {}; + DN_Str8TruncResult actual = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyUSizeEq(&result, dry.count_req, actual.count_req); + DN_TestVerifyExpr(&result, dry.truncated == actual.truncated); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Minimum buffer size is sufficient")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[14] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Single character side size")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 1, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("H...d"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Large side_size falls back to copy")) { + DN_Str8 str = DN_Str8Lit("Hello"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 100, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddle: Arena wrapper allocates and truncates correctly")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult res = DN_Str8TruncMiddle(str, 5, trunc, &scratch.arena); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + DN_TestVerifyExpr(&result, res.str8.data[res.str8.count] == '\0'); + DN_TCScratchEnd(&scratch); + } + } + + // NOTE: Win + #if defined(DN_PLATFORM_WIN32) + { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 input8 = DN_Str8Lit("String"); + DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; + + for (DN_TestScopeF(&result, "[Win] Str8 to Str16")) { + DN_Str16 str_result = DN_OS_W32Str8ToStr16(&scratch.arena, input8); + DN_TestVerifyExpr(&result, DN_Str16Eq(str_result, input16)); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8")) { + DN_Str8 str_result = DN_OS_W32Str16ToStr8(&scratch.arena, input16); + DN_TestVerifyStr8Eq(&result, str_result, input8); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8: Null terminates string")) { + int size_required = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); + char *string = DN_ArenaNewArray(&scratch.arena, char, size_required + 1, DN_ZMem_No); + + DN_Memset(string, 'Z', size_required + 1); + + int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, string, size_required + 1); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_TestVerifyUSizeEqF(&result, size_required, size_returned, "string_size: %d, result: %d", size_required, size_returned); + DN_TestVerifyUSizeEqF(&result, size_returned, DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DN_ArrayCountU(EXPECTED) - 1); + DN_TestVerifyExpr(&result, DN_Memcmp(EXPECTED, string, sizeof(EXPECTED)) == 0); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8: Arena null terminates string")) { + DN_Str8 string8 = DN_OS_W32Str16ToStr8(&scratch.arena, input16); + int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_TestVerifyUSizeEqF(&result, DN_Cast(int) string8.count, size_returned, "string_size: %d, result: %d", DN_Cast(int) string8.count, size_returned); + DN_TestVerifyUSizeEqF(&result, DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1); + DN_TestVerifyExpr(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); + } + + DN_TCScratchEnd(&scratch); + } + #endif // DN_PLATFORM_WIN32 + + // NOTE: NET + #if DN_WITH_NET + { + struct NETEntry + { + DN_Str8 label; + DN_NETInterface net_interface; + }; + + NETEntry entries[2] = {}; + DN_USize entries_count = 0; + + { + #if defined(DN_PLATFORM_EMSCRIPTEN) + NETEntry *entry = entries + entries_count++; + entry->label = DN_Str8Lit("[NET] Emscripten"); + entry->net_interface = DN_NET_EmcInterface(); + #endif + } + + { + #if DN_WITH_NET_CURL + NETEntry *entry = entries + entries_count++; + entry->label = DN_Str8Lit("[NET] CURL"); + entry->net_interface = DN_NET_CurlInterface(); + #endif + } + + for (DN_ForItSize(entry_it, NETEntry, entries, entries_count)) { + NETEntry *entry = entry_it.data; + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org"); + DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com"); + + DN_USize net_base_size = DN_Megabytes(1); + char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes); + DN_NETCore net = {}; + DN_NETInterface *net_interface = &entry->net_interface; + net_interface->init(&net, net_base, net_base_size); + + DN_U64 arena_reset_p = DN_MemListPos(arena.mem); + for (DN_TestScopeF(&result, "%.*s: WaitForResponse HTTP GET request", DN_Str8PrintFmt(entry->label))) { + DN_NETRequestHandle request = net_interface->do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); + DN_NETResponse response = net_interface->wait_for_response(request, &arena, UINT32_MAX); + DN_TestVerifyUSizeNotEq (&result, response.http_status, 0); + DN_TestVerifyUSizeEq (&result, response.state, DN_NETResponseState_HTTP); + DN_TestVerifyUSizeEq (&result, response.error_str8.count, 0); + DN_TestVerifyUSizeGreaterThan(&result, response.body.count, 0); + } + + for (DN_TestScopeF(&result, "%.*s: WaitForResponse HTTP POST request", DN_Str8PrintFmt(entry->label))) { + net_interface->do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); + DN_NETResponse response = net_interface->wait_for_any_response(&net, &arena, UINT32_MAX); + DN_TestVerifyUSizeNotEq (&result, response.http_status, 0); + DN_TestVerifyUSizeEq (&result, response.state, DN_NETResponseState_HTTP); + DN_TestVerifyUSizeEq (&result, response.error_str8.count, 0); + DN_TestVerifyUSizeGreaterThan(&result, response.body.count, 0); + } + + for (DN_TestScopeF(&result, "%.*s: WaitForResponse WS request", DN_Str8PrintFmt(entry->label))) { + DN_NETRequestHandle request = net_interface->do_ws(&net, remote_ws_server_url); + DN_USize const WS_TIMEOUT_MS = 16; + + // NOTE: Wait for WS connection to open + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSOpen, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + done = true; + } + + // NOTE: Receive the initial text from the echo server + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSText, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + net_interface->do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close); + done = true; + } + + // NOTE: Expect to hear the close + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + if (response.state == DN_NETResponseState_Error) + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSClose, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + done = true; + } + } + net_interface->deinit(&net); + DN_MemListDeinit(arena.mem); + } + } + #endif // #if DN_WITH_NET + } + return result; +} +DN_MSVC_WARNING_POP +#endif // #if DN_WITH_TESTS + DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) { if (!ptr) @@ -9159,11 +11222,12 @@ DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, b DN_TicketMutex_Begin(&leak->alloc_table_mutex); - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapMakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); - DN_LeakAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { + DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_HTable* alloc_table = &leak->alloc_table; + DN_UPtr uptr = DN_Cast(DN_UPtr)ptr; + DN_HTableAddResult alloc_entry = DN_HTableMake(alloc_table, &uptr); + DN_LeakAlloc* alloc = DN_Cast(DN_LeakAlloc *)alloc_entry.slot.value; + if (alloc_entry.existed) { if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); DN_Str8x32 new_alloc_size = DN_Str8x32FromByteCountU64Auto(size); @@ -9212,16 +11276,16 @@ DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) DN_TicketMutex_Begin(&leak->alloc_table_mutex); - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapFindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); - DN_AssertAlwaysF(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_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_HTable* alloc_table = &leak->alloc_table; + DN_UPtr uptr = DN_Cast(DN_UPtr)ptr; + DN_LeakAlloc* alloc = DN_Cast(DN_LeakAlloc *)DN_HTableValueFromFind(alloc_table, &uptr); + DN_AssertAlwaysF(alloc, + "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_LeakAlloc *alloc = alloc_entry.value; if (alloc->flags & DN_LeakAllocFlag_Freed) { DN_Str8x32 freed_size = DN_Str8x32FromByteCountU64Auto(alloc->freed_size); DN_AssertAlwaysF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, @@ -9258,11 +11322,14 @@ DN_API void DN_LeakDump_(DN_LeakTracker *leak) { DN_U64 leak_count = 0; DN_U64 leaked_bytes = 0; - for (DN_USize index = 1; index < leak->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = leak->alloc_table.slots + index; - DN_LeakAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); + for (DN_ForItSize(it, DN_LeakTrackerKV, leak->alloc_table_kvs, leak->alloc_table.max)) { + DN_LeakTrackerKV *kv = it.data; + if (kv->hash < DN_HTableHashSentinel_FirstValid) + continue; + + DN_LeakAlloc *alloc = &kv->value; + bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); if (alloc_leaked && !leak_permitted) { leaked_bytes += alloc->size; leak_count++; @@ -9288,127 +11355,7 @@ DN_API void DN_LeakDump_(DN_LeakTracker *leak) #include // getpagesize #endif -static void *DN_OS_MemFuncsHeapAllocShim_(DN_USize size) -{ - void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsFromType(DN_MemFuncsType type) -{ - DN_MemFuncs result = {}; - result.type = type; - switch (type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: { - result.heap_alloc = DN_OS_MemFuncsHeapAllocShim_; - result.heap_dealloc = DN_OS_MemDealloc; - } break; - - case DN_MemFuncsType_Virtual: { - DN_Core *dn = DN_Get(); - DN_Assert(dn->init_flags & DN_InitFlags_OS); - result.virtual_page_size = dn->os.page_size; - result.virtual_reserve = DN_OS_MemReserve; - result.virtual_commit = DN_OS_MemCommit; - result.virtual_release = DN_OS_MemRelease; - } break; - } - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsDefault() -{ - DN_Core *dn = DN_Get(); - DN_MemFuncsType type = DN_MemFuncsType_Heap; - if (dn->os_init) { -#if !defined(DN_PLATFORM_EMSCRIPTEN) - type = DN_MemFuncsType_Virtual; -#endif - } - DN_MemFuncs result = DN_MemFuncsFromType(type); - return result; -} - -DN_API DN_MemList DN_MemListFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Heap); - DN_MemList result = DN_MemListFromMemFuncs(size, size, flags, mem_funcs); - return result; -} - -DN_API DN_MemList DN_MemListFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Virtual); - DN_MemList result = DN_MemListFromMemFuncs(reserve, commit, flags, mem_funcs); - return result; -} - -DN_API DN_Arena DN_ArenaFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromHeap(size, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromVMem(reserve, commit, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API void DN_ArenaDeinit(DN_Arena *arena) -{ - if (arena->flags & DN_ArenaFlags_OwnsMemList) - DN_MemListDeinit(arena->mem); -} - -DN_API DN_Str8 DN_Str8FromHeapF(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_USize count = DN_FmtVCount(fmt, args); - DN_Str8 result = DN_Str8FromHeap(count, DN_ZMem_No); - DN_VSNPrintF(result.data, DN_Cast(int)(result.count + 1), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8FromHeap(DN_USize count, DN_ZMem z_mem) -{ - DN_Str8 result = {}; - result.data = DN_Cast(char *)DN_OS_MemAlloc(count + 1, z_mem); - if (result.data) { - result.count = count; - result.data[result.count] = 0; - } - return result; -} - -DN_API DN_Str8 DN_Str8PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) -{ - // TODO: Implement this without requiring TLS so it can go into base strings - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_Str8BSplitResult split = DN_Str8BSplit(src, DN_Str8Lit("\n")); - while (split.lhs.count) { - DN_Str8BuilderAppendRef(&builder, pad); - DN_Str8BuilderAppendRef(&builder, split.lhs); - split = DN_Str8BSplit(split.rhs, DN_Str8Lit("\n")); - if (split.lhs.count) - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); - } - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap(DN_Str8Builder const *builder) +DN_API DN_Str8 DN_OS_Str8FromStr8BuilderHeap(DN_Str8Builder const *builder) { DN_Str8 result = DN_ZeroInit; if (!builder || builder->string_size <= 0 || builder->count <= 0) @@ -9521,6 +11468,44 @@ DN_API void DN_OS_SetLogPrintFuncToOS() DN_LogSetPrintFunc(DN_OS_LogPrint, &dn->os); } + +DN_API void *DN_OS_HeapBasicAlloc(DN_USize size) +{ + void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); + return result; +} + +DN_API void DN_OS_HeapBasicDealloc(void *ptr) +{ + DN_OS_MemDealloc(ptr); +} + +DN_API DN_Heap DN_OS_HeapInitBasic() +{ + DN_Heap result = DN_HeapInitBasic(DN_OS_HeapBasicAlloc, DN_OS_HeapBasicDealloc); + return result; +} + +DN_API DN_Heap DN_OS_HeapInitVirtual() +{ + DN_Core *dn = DN_Get(); + DN_Assert(dn->init_flags & DN_InitFlags_OS); + DN_Assert(dn->os.page_size); + DN_Heap result = DN_HeapInitVirtual(dn->os.page_size, DN_OS_MemReserve, DN_OS_MemCommit, DN_OS_MemRelease); + return result; +} + +DN_API DN_Heap DN_OS_HeapInitDefault() +{ + DN_Heap result = {}; + #if defined(DN_PLATFORM_EMSCRIPTEN) + result = DN_OS_HeapInitBasic(); + #else + result = DN_OS_HeapInitVirtual(); + #endif + return result; +} + // NOTE: Date DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8(DN_Date time, char date_separator, char hms_separator) { @@ -10016,7 +12001,7 @@ DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *a static void DN_OS_ThreadExecute_(void *user_context) { DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context; - DN_TCInitFromMemFuncs(&thread->context, thread->thread_id, thread->tc_init_args, DN_MemFuncsDefault()); + DN_TCInitFromHeap(&thread->context, thread->thread_id, thread->tc_init_args, DN_OS_HeapInitDefault()); DN_TCEquip(&thread->context); if (thread->is_lane_set) { DN_OS_TCThreadLaneEquip(thread->lane); @@ -10028,6 +12013,19 @@ static void DN_OS_ThreadExecute_(void *user_context) thread->func(thread); } +DN_API DN_OSThreadInitArgs DN_OS_ThreadInitArgsDefault() +{ + DN_OSThreadInitArgs result = {}; + result.tc_args = DN_TCInitArgsDefault(); + return result; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadInitArgs init_args, void *user_context) +{ + bool result = DN_OS_ThreadInitLane(thread, func, nullptr, init_args, user_context); + return result; +} + DN_API void DN_OS_ThreadSetNameFmt(char const *fmt, ...) { DN_TCCore *tls = DN_TCGet(); @@ -10122,11 +12120,11 @@ DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena(DN_USize threads_count, D return result; } -DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context) +DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_OSThreadInitArgs init_args, void *user_context) { for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) { DN_OSThreadLane lane = DN_OS_ThreadLaneInit(it.index, laneway->threads_count, laneway->barrier, laneway->shared_mem); - DN_OS_ThreadInit(it.data, entry_point, &lane, tc_init_args, user_context); + DN_OS_ThreadInitLane(it.data, entry_point, &lane, init_args, user_context); } } @@ -10207,7 +12205,7 @@ DN_API void DN_OS_AsyncInit(DN_OSAsyncCore *async, char *base, DN_USize base_siz async->threads = threads; for (DN_ForIndexU(index, async->thread_count)) { DN_OSThread *thread = async->threads + index; - DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, /*lane=*/ nullptr, DN_TCInitArgsDefault(), async); + DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, DN_OS_ThreadInitArgsDefault(), async); } } @@ -10596,11 +12594,11 @@ DN_API DN_Str8 DN_Str8FromStackTraceNowArena(DN_Arena *arena, DN_U16 limit, DN_U DN_API DN_Str8 DN_Str8FromStackTraceNowHeap(DN_U16 limit, DN_U16 skip) { - DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_MemFlags_NoAllocTrack); + DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_Kilobytes(64), DN_MemFlags_NoAllocTrack, DN_OS_HeapInitBasic()); DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); DN_StackTrace walk = DN_StackTraceFromArena(&arena, limit); DN_StackTraceAddToStr8Builder_(&walk, &builder, skip); - DN_Str8 result = DN_Str8BuilderBuildFromHeap(&builder); + DN_Str8 result = DN_OS_Str8FromStr8BuilderHeap(&builder); DN_ArenaDeinit(&arena); return result; } @@ -11975,7 +13973,7 @@ static void *DN_OS_ThreadFunc_(void *user_context) return nullptr; } -DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context) +DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context) { bool result = false; if (!thread) @@ -11985,7 +13983,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh thread->user_context = user_context; thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); thread->lane = *lane; - thread->tc_init_args = tc_init_args; + thread->tc_init_args = init_args.tc_args; // TODO(doyle): Check if semaphore is valid // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and @@ -12001,6 +13999,9 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh pthread_attr_t attribs = {}; pthread_attr_init(&attribs); + if (init_args.stack_size) + pthread_attr_setstacksize(&attribs, init_args.stack_size); + result = pthread_create(&p_thread, &attribs, DN_OS_ThreadFunc_, thread) == 0; pthread_attr_destroy(&attribs); @@ -12109,21 +14110,21 @@ DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus() DN_Memcpy(result.name, str8.data, result.name_size); } else if (DN_Str8StartsWith(line, PID, DN_Str8EqCase_Insensitive)) { DN_Str8 str8 = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, PID.count, line.count)); - DN_U64FromResult to_u64 = DN_U64FromStr8(str8, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(str8); result.pid = to_u64.value; DN_Assert(to_u64.success); } else if (DN_Str8StartsWith(line, VM_SIZE, DN_Str8EqCase_Insensitive)) { DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_SIZE.count, line.count)); DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB"))); DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs; - DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size); result.vm_size = DN_Kilobytes(to_u64.value); DN_Assert(to_u64.success); } else if (DN_Str8StartsWith(line, VM_PEAK, DN_Str8EqCase_Insensitive)) { DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_PEAK.count, line.count)); DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB"))); DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs; - DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size); result.vm_peak = DN_Kilobytes(to_u64.value); DN_Assert(to_u64.success); } @@ -12138,8 +14139,7 @@ DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus() #define DN_OS_W32_CPP // DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_H_WITH_CORE 1 -// #define DN_H_WITH_OS 1 +// #define DN_WITH_OS 1 // #include "../dn.h" // #include "dn_os_w32.h" // #endif @@ -12236,13 +14236,15 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZMem z_mem) { - DN_Core *dn = DN_Get(); - DN_AssertRaw(dn->init_flags & DN_InitFlags_OS && "DN must be initialised with the OS flag"); DN_U32 flags = z_mem == DN_ZMem_Yes ? HEAP_ZERO_MEMORY : 0; DN_Assert(size <= DN_Cast(DWORD)(-1)); void *result = HeapAlloc(GetProcessHeap(), flags, DN_Cast(DWORD) size); - DN_AtomicAddU64(&dn->os.mem_allocs_total, 1); - DN_AtomicAddU64(&dn->os.mem_allocs_frame, 1); + + DN_Core *dn = DN_Get(); + if (dn) { + DN_AtomicAddU64(&dn->os.mem_allocs_total, 1); + DN_AtomicAddU64(&dn->os.mem_allocs_frame, 1); + } return result; } @@ -12774,10 +14776,10 @@ DN_API bool DN_OS_PathIterateDir(DN_Str8 path, DN_OSDirIterator *it) if (it->handle) { wide_it.handle = it->handle; } else { - bool needs_asterisks = DN_Str8EndsWith(path, DN_Str8Lit("\\")) || - DN_Str8EndsWith(path, DN_Str8Lit("/")); - bool has_glob = DN_Str8EndsWith(path, DN_Str8Lit("\\*")) || - DN_Str8EndsWith(path, DN_Str8Lit("/*")); + bool needs_asterisks = DN_Str8EndsWithSensitive(path, DN_Str8Lit("\\")) || + DN_Str8EndsWithSensitive(path, DN_Str8Lit("/")); + bool has_glob = DN_Str8EndsWithSensitive(path, DN_Str8Lit("\\*")) || + DN_Str8EndsWithSensitive(path, DN_Str8Lit("/*")); DN_Str8 adjusted_path = path; if (!has_glob) { @@ -13364,14 +15366,13 @@ DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) } } -// 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, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context) +DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context) { bool result = false; if (!thread) @@ -13380,7 +15381,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh thread->func = func; thread->user_context = user_context; thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - thread->tc_init_args = tc_init_args; + thread->tc_init_args = init_args.tc_args; if (lane) { thread->is_lane_set = true; thread->lane = *lane; @@ -13390,7 +15391,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh DWORD thread_id = 0; SECURITY_ATTRIBUTES security_attribs = {}; thread->handle = CreateThread(&security_attribs, - 0 /*stack_size*/, + init_args.stack_size, DN_OS_ThreadFunc_, thread, 0 /*creation_flags*/, @@ -13659,10 +15660,10 @@ DN_API DN_Str8 DN_OS_W32Str16ToStr8FromHeap(DN_Str16 src) 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_Str8FromHeap(required_size, DN_ZMem_No); - if (buffer.count == 0) + DN_Str8 buffer = {}; + buffer.data = DN_Cast(char *) DN_OS_MemAlloc(required_size + 1, DN_ZMem_No); + buffer.count = required_size; + if (!buffer.data) return result; int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_Cast(int) buffer.count, nullptr, nullptr); @@ -13830,3143 +15831,6 @@ DN_API bool DN_OS_W32DirWIterate(DN_Str16 path, DN_OSW32FolderIteratorW *it) #endif #endif // DN_WITH_OS -#if DN_WITH_TESTS -// DN: Single header generator commented out => #include "Extra/dn_tests.cpp" -// DN: Single header generator commented out => #if defined(_CLANGD) -// #define DN_WITH_OS 1 -// #include "dn.h" -// #include "../Standalone/dn_utest.h" -// -// #define DN_UNIT_TESTS_WITH_KECCAK -// #define DN_UNIT_TESTS_WITH_NET -// #define DN_UNIT_TESTS_WITH_CURL -// #define DN_SHA3_IMPLEMENTATION -// #endif - -#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_TSTResult -{ - bool passed; - int total_tests; - int total_good_tests; -}; - -enum DN_TSTPrint -{ - DN_TSTPrint_No, - DN_TSTPrint_OnFailure, - DN_TSTPrint_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; - DN_U32 f_1_ECX_ = 0; - DN_U32 f_1_EDX_ = 0; - DN_U32 f_7_EBX_ = 0; - DN_U32 f_7_ECX_ = 0; - DN_U32 f_81_ECX_ = 0; - DN_U32 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_TST_Base() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Base\n"); - { - #if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) - DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); - for (DN_UT_Test(&result, "Query CPUID")) { - DN_CPUReport cpu_report = DN_CPUGetReport(); - - // NOTE: Sanity check our report against MSDN's example //////////////////////////////////////// - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); - - // NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example ///////////// - /* - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE()); - */ - } - #endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) - - for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1s 1ms (seconds and ms)")) { - DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms); - DN_Str8 expect = DN_Str8Lit("1s 1ms"); - DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.count, expect.data, expect.count), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1.001s (fractional)")) { - DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_FractionalSec); - DN_Str8 expect = DN_Str8Lit("1.001s"); - DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.count, expect.data, expect.count), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "FmtAppendTruncate: String truncates with 3 dots")) { - char buf[8] = {}; - DN_USize buf_size = 0; - DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1)); - DN_Str8 expect = DN_Str8Lit("This..."); // 7 characters long, 1 byte reserved for null-terminator - DN_UT_Assert(&result, buf_str8.truncated); - DN_UT_AssertF(&result, DN_Str8Eq(buf_str8.str8, expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "TicketMutex: 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); - } - - for (DN_UT_Test(&result, "TicketMutex: 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); - } - - for (DN_UT_Test(&result, "QSort String (Natural)")) { - DN_Str8 list[] = { - DN_Str8Lit("item10"), - DN_Str8Lit("item2"), - DN_Str8Lit("item1"), - DN_Str8Lit("item20"), - DN_Str8Lit("item12"), - DN_Str8Lit("Afile"), - DN_Str8Lit("file2"), - DN_Str8Lit("file10"), - DN_Str8Lit("file1"), - DN_Str8Lit("z_last"), - DN_Str8Lit("m_middle"), - DN_Str8Lit("a_first"), - DN_Str8Lit("version-1.2.10"), - DN_Str8Lit("version-1.2.2"), - DN_Str8Lit("version-1.10.0"), - }; - - DN_QSortStr8NaturalAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Sensitive); - - DN_USize list_index = 0; - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("Afile")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("a_first")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file1")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item1")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item12")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item20")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("m_middle")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.10.0")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("z_last")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - } - - for (DN_UT_Test(&result, "QSort String (Lexicographic)")) { - DN_Str8 list[] = { - DN_Str8Lit("z_last"), - DN_Str8Lit("m_middle"), - DN_Str8Lit("a_first"), - DN_Str8Lit("version-1.2.10"), - DN_Str8Lit("version-1.2.2"), - DN_Str8Lit("version-1.10.0"), - }; - - DN_QSortStr8LexicographicAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Insensitive); - - DN_USize list_index = 0; - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("a_first")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("m_middle")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.10.0")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("z_last")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - } - - // 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_PUSH - 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. - { - // 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. - for (DN_UT_Test(&result, "AtomicAddU32")) { - DN_U32 val = 0; - DN_AtomicAddU32(&val, 1); - DN_UT_AssertF(&result, val == 1, "val: %u", val); - } - - for (DN_UT_Test(&result, "AtomicAddU64")) { - uint64_t val = 0; - DN_AtomicAddU64(&val, 1); - DN_UT_AssertF(&result, val == 1, "val: %" PRIu64, val); - } - - for (DN_UT_Test(&result, "AtomicSubU32")) { - DN_U32 val = 1; - DN_AtomicSubU32(&val, 1); - DN_UT_AssertF(&result, val == 0, "val: %u", val); - } - - for (DN_UT_Test(&result, "AtomicSubU64")) { - uint64_t val = 1; - DN_AtomicSubU64(&val, 1); - DN_UT_AssertF(&result, val == 0, "val: %" PRIu64, val); - } - - for (DN_UT_Test(&result, "AtomicSetValue32")) { - DN_U32 a = 0; - DN_U32 b = 111; - DN_AtomicSetValue32(&a, b); - DN_UT_AssertF(&result, a == b, "a: %ld, b: %ld", a, b); - } - - for (DN_UT_Test(&result, "AtomicSetValue64")) { - int64_t a = 0; - int64_t b = 111; - DN_AtomicSetValue64(DN_Cast(uint64_t *) & a, b); - DN_UT_AssertF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); - } - - DN_UT_BeginF(&result, "CPUGetTSC: Compile check"); - DN_CPUGetTSC(); - DN_UT_End(&result); - - DN_UT_BeginF(&result, "CompilerReadBarrierAndCPUReadFence: Compile check"); - DN_CompilerReadBarrierAndCPUReadFence; - DN_UT_End(&result); - - DN_UT_BeginF(&result, "CompilerWriteBarrierAndCPUWriteFence: Compile check"); - DN_CompilerWriteBarrierAndCPUWriteFence; - DN_UT_End(&result); - } - DN_MSVC_WARNING_POP - } - return result; -} - -static DN_UTCore DN_TST_BaseArena() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Arena\n"); - { - for (DN_UT_Test(&result, "Reused memory is zeroed out")) { - uint8_t alignment = 1; - DN_USize alloc_size = DN_Kilobytes(128); - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena - uintptr_t first_ptr_address = 0; - { - DN_U64 mem_p = DN_MemListPos(arena.mem); - void *ptr = DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); - first_ptr_address = DN_Cast(uintptr_t) ptr; - DN_Memset(ptr, 'z', alloc_size); - DN_MemListPopTo(arena.mem, mem_p); - } - - // NOTE: Reallocate 128 kilobytes - char *ptr = DN_Cast(char *) DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_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); - } - - for (DN_UT_Test(&result, "Test arena grows naturally, 1mb + 4mb")) { - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - char *ptr_1mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(1), DN_ZMem_Yes); - char *ptr_4mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(4), DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_1mb); - DN_UT_Assert(&result, ptr_4mb); - - DN_MemBlock const *block_4mb_begin = arena.mem->curr; - char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; - - DN_MemBlock 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"); - } - - for (DN_UT_Test(&result, "Test arena grows naturally, 1mb, temp memory 4mb")) { - DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DN_Cast(char *) DN_ArenaAlloc(&arena, DN_Megabytes(1), 1 /*align*/, DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_1mb); - - DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); - { - char *ptr_4mb = DN_ArenaNewArray(&temp, char, DN_Megabytes(4), DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_4mb); - - DN_MemBlock const *block_4mb_begin = arena.mem->curr; - char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; - - DN_MemBlock 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_ArenaTempEnd(&temp, DN_ArenaReset_Yes); - DN_UT_Assert(&result, arena.mem->curr->prev == nullptr); - DN_UT_AssertF(&result, - arena.mem->curr->reserve >= DN_Megabytes(1), - "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", - (arena.mem->curr->reserve / 1024 / 1024), - arena.mem->curr->reserve, - DN_Megabytes(1)); - } - } - return result; -} - -static DN_UTCore DN_TST_BaseBytesHex() -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_UTCore test = DN_UT_Init(); - DN_UT_LogF(&test, "Bytes <-> Hex\n"); - { - for (DN_UT_Test(&test, "Convert 0x123")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x123")); - DN_UT_AssertF(&test, result == 0x123, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0xFFFF")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xFFFF")); - DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert FFFF")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("FFFF")); - DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert abCD")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("abCD")); - DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0xabCD")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xabCD")); - DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0x")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x")); - DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0X")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0X")); - DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 3")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("3")); - DN_UT_AssertF(&test, result == 3, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert f")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("f")); - DN_UT_Assert(&test, result.success); - DN_UT_Assert(&test, result.value == 0xf); - } - - for (DN_UT_Test(&test, "Convert g")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("g")); - DN_UT_Assert(&test, !result.success); - } - - for (DN_UT_Test(&test, "Convert -0x3")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("-0x3")); - DN_UT_Assert(&test, !result.success); - } - - DN_U32 number = 0xd095f6; - for (DN_UT_Test(&test, "Convert %x to string", number)) { - DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("f695d000")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); - } - - number = 0xf6ed00; - for (DN_UT_Test(&test, "Convert %x to string", number)) { - DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("00edf600")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); - } - - DN_Str8 hex = DN_Str8Lit("0xf6ed00"); - for (DN_UT_Test(&test, "Convert %.*s to bytes", DN_Str8PrintFmt(hex))) { - DN_Str8 bytes = DN_BytesFromHexArena(hex, &scratch.arena); - DN_UT_AssertF(&test, - DN_Str8Eq(bytes, DN_Str8Lit("\xf6\xed\x00")), - "number_hex=%.*s", - DN_Str8PrintFmt(DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No))); - } - - for (DN_UT_Test(&test, "Convert empty bytes to string", number)) { - DN_Str8 bytes = DN_Str8Lit(""); - DN_Str8 as_hex = DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(as_hex, DN_Str8Lit("")), "as_hex=%.*s", DN_Str8PrintFmt(as_hex)); - } - } - DN_TCScratchEnd(&scratch); - return test; -} - -#if DN_H_WITH_HELPERS -static DN_UTCore DN_TST_BinarySearch() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_BinarySearch\n"); - { - for (DN_UT_Test(&result, "Search array of 1 item")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 2 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 3 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 4 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array with duplicate items")) { - DN_U32 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; -} -#endif // DN_H_WITH_HELPERS - -static DN_UTCore DN_TST_BaseDSMap() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_DSMap\n"); - { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - { - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_U32 const MAP_SIZE = 64; - DN_DSMap map = DN_DSMapInit(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER - { - DN_DSMapDeinit(&map, DN_ZMem_Yes); - }; - - for (DN_UT_Test(&result, "Find non-existent value")) { - DN_DSMapResult find = DN_DSMapFindKeyStr8(&map, DN_Str8Lit("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_DSMapKeyCStr8(&map, "Bar"); - for (DN_UT_Test(&result, "Insert value and lookup")) { - uint64_t desired_value = 0xF00BAA; - uint64_t *slot_value = DN_DSMapSet(&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_DSMapFind(&map, key).value; - DN_UT_Assert(&result, value); - DN_UT_Assert(&result, *value == desired_value); - } - - for (DN_UT_Test(&result, "Remove key")) { - DN_DSMapErase(&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_Str8Lit("Set"); break; - case DSMapTestType_MakeSlot: prefix = DN_Str8Lit("Make slot"); break; - } - - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_U32 const MAP_SIZE = 64; - DN_DSMap map = DN_DSMapInit(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER - { - DN_DSMapDeinit(&map, DN_ZMem_Yes); - }; - - for (DN_UT_Test(&result, "%.*s: Test growing", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, !DN_DSMapFind(&map, key).found); - DN_DSMapResult make_result = {}; - if (result_type == DSMapTestType_Set) - make_result = DN_DSMapSet(&map, key, value); - else - make_result = DN_DSMapMake(&map, key); - DN_UT_Assert(&result, !make_result.found); - DN_UT_Assert(&result, DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_DSMapResult make_result = {}; - if (result_type == DSMapTestType_Set) - make_result = DN_DSMapSet(&map, key, value); - else - make_result = DN_DSMapMake(&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); - } - } - - for (DN_UT_Test(&result, "%.*s: Check the sentinel is present", DN_Str8PrintFmt(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); - } - - for (DN_UT_Test(&result, "%.*s: Recheck all the hash tables values after growing", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value_result); - DN_UT_Assert(&result, DN_DSMapKeyEquals(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_DSMapHash(&map, slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMapFind(&map, slot->key); - DN_UT_Assert(&result, slot->value == *check.value); - } - } - - for (DN_UT_Test(&result, "%.*s: Test shrinking", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, DN_DSMapFind(&map, key).found); - DN_DSMapErase(&map, key); - DN_UT_Assert(&result, !DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_DSMapErase(&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_DSMapKeyU64(&map, value_result); - - // NOTE: Validate each slot value - DN_DSMapResult find_result = DN_DSMapFind(&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_DSMapHash(&map, find_result.slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, DN_DSMapFind(&map, key).found); - DN_DSMapErase(&map, key); - DN_UT_Assert(&result, !DN_DSMapFind(&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*/); - } - } - DN_TCScratchEnd(&scratch); - } - return result; -} - -static DN_UTCore DN_TST_BaseIArray() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_IArray\n"); - { - struct CustomArray - { - int *data; - DN_USize count; - DN_USize max; - }; - - int array_buffer[16]; - CustomArray array = {}; - array.data = array_buffer; - array.max = DN_ArrayCountU(array_buffer); - - for (DN_UT_Test(&result, "Make item")) { - int *item = DN_IArrayMake(&array, DN_ZMem_Yes); - DN_UT_Assert(&result, item && array.count == 1); - } - } - return result; -} - -static DN_UTCore DN_TST_BaseArray() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_Array\n"); - { - for (DN_UT_Test(&result, "Positive count, middle of array, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 2); - DN_UT_Assert(&result, erase.it_index == 2); - DN_UT_Assert(&result, size == 8); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Negative count, middle of array, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Stable); - int expected[] = {0, 1, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 3); - DN_UT_Assert(&result, erase.it_index == 1); - DN_UT_Assert(&result, size == 7); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "count = -1, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -1, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 1); - DN_UT_AssertF(&result, erase.it_index == 3, "lhs=%zu", erase.it_index); - DN_UT_Assert(&result, size == 9); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Positive count, unstable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Unstable); - int expected[] = {0, 1, 2, 8, 9, 5, 6, 7}; - DN_UT_Assert(&result, erase.items_erased == 2); - DN_UT_Assert(&result, erase.it_index == 2); - DN_UT_Assert(&result, size == 8); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Negative count, unstable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Unstable); - int expected[] = {0, 1, 7, 8, 9, 5, 6}; - DN_UT_Assert(&result, erase.items_erased == 3); - DN_UT_Assert(&result, erase.it_index == 1); - DN_UT_Assert(&result, size == 7); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Edge case - begin_index at start, negative count")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 0, -2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 0); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Edge case - begin_index at end, positive count")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 9, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; - DN_UT_Assert(&result, erase.items_erased == 1); - DN_UT_Assert(&result, erase.it_index == 9); - DN_UT_Assert(&result, size == 9); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Invalid input - count = 0")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 0, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Invalid input - null data")) { - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(nullptr, &size, sizeof(int), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 10); - } - - for (DN_UT_Test(&result, "Invalid input - null size")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, NULL, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - } - - for (DN_UT_Test(&result, "Invalid input - empty array")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 0; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 0); - } - - for (DN_UT_Test(&result, "Out-of-bounds begin_index")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 15, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 9); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - } - 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_SHA3_IMPLEMENTATION - // DN: Single header generator commented out => #include "../Standalone/dn_sha3.h" -#if !defined(DN_SHA3_H) -#define DN_SHA3_H - -// NOTE: DN_Sha3 -- FIPS202 SHA3 + non-finalized SHA3 (aka. Keccak) hashing algorithms -// -// Overview -// Single header file 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). -// -// Configuration -// Define this in one and only one C++ file to enable the implementation code of the header file. -// -// #define DN_SHA3_IMPLEMENTATION -// -// License -// 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. - -#include -#if !defined(DN_SHA3Memcpy) - #include - #define DN_SHA3Memcpy(dest, src, count) memcpy(dest, src, count) -#endif - -#if !defined(DN_SHA3Memcmp) - #include - #define DN_SHA3Memcmp(dest, src, count) memcmp(dest, src, count) -#endif - -#if !defined(DN_SHA3Memset) - #include - #define DN_SHA3Memset(dest, byte, count) memset(dest, byte, count) -#endif - -#if !defined(DN_SHA3Assert) - #if defined(NDEBUG) - #define DN_SHA3Assert(expr) - #else - #define DN_SHA3Assert(expr) \ - do { \ - if (!(expr)) { \ - (*(volatile int *)0) = 0; \ - } \ - } while (0) - #endif -#endif - -typedef struct DN_SHA3U8x28 { char data[28]; } DN_SHA3U8x28; // 224 bit -typedef struct DN_SHA3U8x32 { char data[32]; } DN_SHA3U8x32; // 256 bit -typedef struct DN_SHA3U8x48 { char data[48]; } DN_SHA3U8x48; // 384 bit -typedef struct DN_SHA3U8x64 { char data[64]; } DN_SHA3U8x64; // 512 bit -typedef struct DN_SHA3Str8x56 { char data[(sizeof(DN_SHA3U8x28) * 2) + 1]; } DN_SHA3Str8x56; -typedef struct DN_SHA3Str8x64 { char data[(sizeof(DN_SHA3U8x32) * 2) + 1]; } DN_SHA3Str8x64; -typedef struct DN_SHA3Str8x96 { char data[(sizeof(DN_SHA3U8x48) * 2) + 1]; } DN_SHA3Str8x96; -typedef struct DN_SHA3Str8x128 { char data[(sizeof(DN_SHA3U8x64) * 2) + 1]; } DN_SHA3Str8x128; - -#define DN_SHA3_LANE_SIZE_U64 5 -typedef struct DN_SHA3State { - size_t hash_size_bits; // The size of the hash the context was initialised for in bits - size_t state_size; // The number of bytes written to the state - size_t absorb_size; // The amount of bytes to absorb/sponge in/from the state - uint8_t state[DN_SHA3_LANE_SIZE_U64 * DN_SHA3_LANE_SIZE_U64 * sizeof(uint64_t)]; - char delimited_suffix; // The delimited suffix of the current hash -} DN_SHA3State; - -enum DN_SHA3Family -{ - DN_SHA3Family_SHA3, // FIPS 202 SHA3 (delimited suffix is 0x6) - DN_SHA3Family_Keccak, // Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6) -}; - -// hash_size_bits: Number of bits to hash to. Available sizes are 224, 256, 384 and 512. -DN_SHA3State DN_SHA3FamilyInit (DN_SHA3Family type, size_t hash_size_bits); -DN_SHA3State DN_SHA3FamilyInitSHA3 (size_t hash_size_bits); -DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits); -void DN_SHA3FamilyUpdate (DN_SHA3State *sha3, void const *data, size_t data_size); -void DN_SHA3FamilyFinish (DN_SHA3State *sha3, void *dest, size_t dest_size); -void DN_SHA3FamilyHash (DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, int dest_size); - -void DN_SHA3Hash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x28 DN_SHA3Hash224b (void const *src, size_t src_size); -void DN_SHA3Hash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x32 DN_SHA3Hash256b (void const *src, size_t src_size); -void DN_SHA3Hash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x48 DN_SHA3Hash384b (void const *src, size_t src_size); -void DN_SHA3Hash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x64 DN_SHA3Hash512b (void const *src, size_t src_size); - -void DN_KeccakHash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x28 DN_KeccakHash224b (void const *src, size_t src_size); -void DN_KeccakHash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x32 DN_KeccakHash256b (void const *src, size_t src_size); -void DN_KeccakHash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x48 DN_KeccakHash384b (void const *src, size_t src_size); -void DN_KeccakHash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x64 DN_KeccakHash512b (void const *src, size_t src_size); - -void DN_SHA3HexFromBytes (void const *src, uint64_t src_size, char *dest, uint64_t dest_size); -DN_SHA3Str8x56 DN_SHA3HexFromU8x28 (DN_SHA3U8x28 const *bytes); -DN_SHA3Str8x64 DN_SHA3HexFromU8x32 (DN_SHA3U8x32 const *bytes); -DN_SHA3Str8x96 DN_SHA3HexFromU8x48 (DN_SHA3U8x48 const *bytes); -DN_SHA3Str8x128 DN_SHA3HexFromU8x64 (DN_SHA3U8x64 const *bytes); -bool DN_SHA3U8x28Eq (DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b); -bool DN_SHA3U8x32Eq (DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b); -bool DN_SHA3U8x48Eq (DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b); -bool DN_SHA3U8x64Eq (DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b); -#endif // DN_SHA3_H - -#if defined(DN_SHA3_IMPLEMENTATION) -uint64_t const DN_SHA3_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_SHA3_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_SHA3_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate))))) -static void DN_SHA3FamilyPermute_(void *state) -{ - uint64_t *lanes_u64 = (uint64_t *)state; - for (int round_index = 0; round_index < 24; round_index++) { - #define DN_SHA3_LANE_INDEX(x, y) ((x) + ((y) * DN_SHA3_LANE_SIZE_U64)) - // ?? step - #if 1 - uint64_t c[DN_SHA3_LANE_SIZE_U64]; - for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) - c[x] = lanes_u64[DN_SHA3_LANE_INDEX(x, 0)] ^ - lanes_u64[DN_SHA3_LANE_INDEX(x, 1)] ^ - lanes_u64[DN_SHA3_LANE_INDEX(x, 2)] ^ - lanes_u64[DN_SHA3_LANE_INDEX(x, 3)] ^ - lanes_u64[DN_SHA3_LANE_INDEX(x, 4)]; - - uint64_t d[DN_SHA3_LANE_SIZE_U64]; - for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) - d[x] = c[(x + 4) % DN_SHA3_LANE_SIZE_U64] ^ DN_SHA3_ROL64(c[(x + 1) % DN_SHA3_LANE_SIZE_U64], 1); - - for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++) - for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) - lanes_u64[DN_SHA3_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_SHA3_ROL64(c[1], 1); - d[1] = c[0] ^ DN_SHA3_ROL64(c[2], 1); - d[2] = c[1] ^ DN_SHA3_ROL64(c[3], 1); - d[3] = c[2] ^ DN_SHA3_ROL64(c[4], 1); - d[4] = c[3] ^ DN_SHA3_ROL64(c[0], 1); - #endif - - // ?? and ?? steps - uint64_t b[DN_SHA3_LANE_SIZE_U64 * DN_SHA3_LANE_SIZE_U64]; - for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++) { - for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) { - uint64_t lane = lanes_u64[DN_SHA3_LANE_INDEX(x, y)]; - uint64_t rotate_count = DN_SHA3_ROTATIONS[x][y]; - b[DN_SHA3_LANE_INDEX(y, (2 * x + 3 * y) % 5)] = DN_SHA3_ROL64(lane, rotate_count); - } - } - - // ?? step - for (int y = 0; y < DN_SHA3_LANE_SIZE_U64; y++) { - for (int x = 0; x < DN_SHA3_LANE_SIZE_U64; x++) { - uint64_t rhs = ~b[DN_SHA3_LANE_INDEX((x + 1) % 5, y)] & b[DN_SHA3_LANE_INDEX((x + 2) % 5, y)]; - - lanes_u64[DN_SHA3_LANE_INDEX(x, y)] = b[DN_SHA3_LANE_INDEX(x, y)] ^ rhs; - } - } - - // ?? step - lanes_u64[DN_SHA3_LANE_INDEX(0, 0)] ^= DN_SHA3_ROUNDS[round_index]; - #undef DN_SHA3_LANE_INDEX - #undef DN_SHA3_ROL64 - } -} - -DN_SHA3State DN_SHA3FamilyInit(DN_SHA3Family type, size_t hash_size_bits) -{ - DN_SHA3Assert(hash_size_bits == 224 || - hash_size_bits == 256 || - hash_size_bits == 384 || - hash_size_bits == 512); - - char const SHA3_DELIMITED_SUFFIX = 0x06; - char const KECCAK_DELIMITED_SUFFIX = 0x01; - size_t const bitrate = 1600 - (hash_size_bits * 2); - - #if defined(__cplusplus) - DN_SHA3State result = {}; - #else - DN_SHA3State result = {0}; - #endif - result.hash_size_bits = hash_size_bits; - result.absorb_size = bitrate / 8; - result.delimited_suffix = type == DN_SHA3Family_SHA3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX; - DN_SHA3Assert(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600); - return result; -} - -DN_SHA3State DN_SHA3FamilyInitSHA3(size_t hash_size_bits) -{ - DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_SHA3, hash_size_bits); - return result; -} - -DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits) -{ - DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_Keccak, hash_size_bits); - return result; -} - -void DN_SHA3FamilyUpdate(DN_SHA3State *sha3, void const *data, size_t data_size) -{ - uint8_t *state = sha3->state; - uint8_t const *ptr = (uint8_t *)data; - size_t ptr_size = data_size; - while (ptr_size > 0) { - size_t space = sha3->absorb_size - sha3->state_size; - int bytes_to_absorb = (int)(space < ptr_size ? space : ptr_size); - - for (int index = 0; index < bytes_to_absorb; index++) - state[sha3->state_size + index] ^= ptr[index]; - - ptr += bytes_to_absorb; - sha3->state_size += bytes_to_absorb; - ptr_size -= bytes_to_absorb; - - if (sha3->state_size >= sha3->absorb_size) { - DN_SHA3Assert(sha3->state_size == sha3->absorb_size); - DN_SHA3FamilyPermute_(state); - sha3->state_size = 0; - } - } -} - -void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size) -{ - DN_SHA3Assert(dest_size >= (size_t)(sha3->hash_size_bits / 8)); - - // Sponge Finalization Step: Final padding bit - size_t const INDEX_OF_0X80_BYTE = sha3->absorb_size - 1; - size_t const delimited_suffix_index = sha3->state_size; - DN_SHA3Assert(delimited_suffix_index < sha3->absorb_size); - - uint8_t *state = sha3->state; - state[delimited_suffix_index] ^= sha3->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_SHA3FamilyPermute_(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 / sha3->absorb_size; - size_t squeeze_index = 0; - for (; squeeze_index < squeeze_count; squeeze_index++) { - if (squeeze_index) - DN_SHA3FamilyPermute_(state); - DN_SHA3Memcpy(dest_u8, state, sha3->absorb_size); - dest_u8 += sha3->absorb_size; - } - - // Squeeze Finalisation Step: Remainder bytes in hash - size_t const remainder = dest_size % sha3->absorb_size; - if (remainder) { - if (squeeze_index) - DN_SHA3FamilyPermute_(state); - DN_SHA3Memcpy(dest_u8, state, remainder); - } -} - -void DN_SHA3FamilyHash(DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3State state = DN_SHA3FamilyInit(type, hash_size_bits); - DN_SHA3FamilyUpdate(&state, src, src_size); - DN_SHA3FamilyFinish(&state, dest, dest_size); -} - -void DN_SHA3Hash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); -} - -DN_SHA3U8x28 DN_SHA3Hash224b(void const *src, size_t src_size) -{ - DN_SHA3U8x28 result = {}; - DN_SHA3Hash224bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_SHA3Hash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); -} - -DN_SHA3U8x32 DN_SHA3Hash256b(void const *src, size_t src_size) -{ - DN_SHA3U8x32 result = {}; - DN_SHA3Hash256bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_SHA3Hash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); -} - -DN_SHA3U8x48 DN_SHA3Hash384b(void const *src, size_t src_size) -{ - DN_SHA3U8x48 result = {}; - DN_SHA3Hash384bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_SHA3Hash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); -} - -DN_SHA3U8x64 DN_SHA3Hash512b(void const *src, size_t src_size) -{ - DN_SHA3U8x64 result = {}; - DN_SHA3Hash512bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_KeccakHash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); -} - -DN_SHA3U8x28 DN_KeccakHash224b(void const *src, size_t src_size) -{ - DN_SHA3U8x28 result = {}; - DN_KeccakHash224bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_KeccakHash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); -} - -DN_SHA3U8x32 DN_KeccakHash256b(void const *src, size_t src_size) -{ - DN_SHA3U8x32 result = {}; - DN_KeccakHash256bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_KeccakHash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); -} - -DN_SHA3U8x48 DN_KeccakHash384b(void const *src, size_t src_size) -{ - DN_SHA3U8x48 result = {}; - DN_KeccakHash384bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_KeccakHash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) -{ - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); -} - -DN_SHA3U8x64 DN_KeccakHash512b(void const *src, size_t src_size) -{ - DN_SHA3U8x64 result = {}; - DN_KeccakHash512bPtr(src, src_size, result.data, sizeof(result.data)); - return result; -} - -void DN_SHA3HexFromBytes(void const *src, size_t src_size, char *dest, size_t dest_size) -{ - (void)src_size; - (void)dest_size; - DN_SHA3Assert(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_SHA3Str8x56 DN_SHA3HexFromU8x28(DN_SHA3U8x28 const *bytes) -{ - DN_SHA3Str8x56 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_SHA3Str8x64 DN_SHA3HexFromU8x32(DN_SHA3U8x32 const *bytes) -{ - DN_SHA3Str8x64 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_SHA3Str8x96 DN_SHA3HexFromU8x48(DN_SHA3U8x48 const *bytes) -{ - DN_SHA3Str8x96 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -DN_SHA3Str8x128 DN_SHA3HexFromU8x64(DN_SHA3U8x64 const *bytes) -{ - DN_SHA3Str8x128 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); - result.data[sizeof(result.data) - 1] = 0; - return result; -} - -bool DN_SHA3U8x32Eq(DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b) -{ - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -bool DN_SHA3U8x32Eq(DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b) -{ - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -bool DN_SHA3U8x48Eq(DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b) -{ - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; - return result; -} - -bool DN_SHA3U8x64Eq(DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b) -{ - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; - return result; -} -#endif // DN_SHA3_IMPLEMENTATION - - #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_TST__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_Str8Lit(string), - DN_UT_HASH_X_MACRO - #undef DN_UT_HASH_X_ENTRY -}; - -void DN_TST_KeccakDispatch_(DN_UTCore *test, int hash_type, DN_Str8 input) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 input_hex = DN_HexFromPtrBytesArena(input.data, input.count, &scratch.arena, DN_TrimLeadingZero_No); - - switch (hash_type) { - case Hash_SHA3_224: { - DN_SHA3U8x28 hash = DN_SHA3Hash224b(input.data, input.count); - DN_SHA3U8x28 expect; - DN_RefImpl_FIPS202_SHA3_224_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_256: { - DN_SHA3U8x32 hash = DN_SHA3Hash256b(input.data, input.count); - DN_SHA3U8x32 expect; - DN_RefImpl_FIPS202_SHA3_256_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_384: { - DN_SHA3U8x48 hash = DN_SHA3Hash384b(input.data, input.count); - DN_SHA3U8x48 expect; - DN_RefImpl_FIPS202_SHA3_384_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_512: { - DN_SHA3U8x64 hash = DN_SHA3Hash512b(input.data, input.count); - DN_SHA3U8x64 expect; - DN_RefImpl_FIPS202_SHA3_512_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_224: { - DN_SHA3U8x28 hash = DN_KeccakHash224b(input.data, input.count); - DN_SHA3U8x28 expect; - DN_RefImpl_Keccak_(1152, 448, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_256: { - DN_SHA3U8x32 hash = DN_KeccakHash256b(input.data, input.count); - DN_SHA3U8x32 expect; - DN_RefImpl_Keccak_(1088, 512, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_384: { - DN_SHA3U8x48 hash = DN_KeccakHash384b(input.data, input.count); - DN_SHA3U8x48 expect; - DN_RefImpl_Keccak_(832, 768, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_512: { - DN_SHA3U8x64 hash = DN_KeccakHash512b(input.data, input.count); - DN_SHA3U8x64 expect; - DN_RefImpl_Keccak_(576, 1024, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - } - DN_TCScratchEnd(&scratch); -} -#endif // defined(DN_UNIT_TESTS_WITH_KECCAK) - -DN_UTCore DN_TST_Keccak() -{ - DN_UTCore result = DN_UT_Init(); - #if defined(DN_UNIT_TESTS_WITH_KECCAK) - DN_Str8 const INPUTS[] = { - DN_Str8Lit("abc"), - DN_Str8Lit(""), - DN_Str8Lit("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), - DN_Str8Lit("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno" - "pqrstnopqrstu"), - }; - - DN_UT_LogF(&result, "DN_KC\n"); - { - for (int hash_type = 0; hash_type < Hash_Count; hash_type++) { - DN_PCG32 rng = DN_PCG32Init(0xd48e'be21'2af8'733d); - for (DN_Str8 input : INPUTS) { - DN_UT_BeginF(&result, "%.*s - Input: %.*s", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]), DN_Cast(int) DN_Min(input.count, 54), input.data); - DN_TST_KeccakDispatch_(&result, hash_type, input); - DN_UT_End(&result); - } - - DN_UT_BeginF(&result, "%.*s - Deterministic random inputs", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type])); - for (DN_USize index = 0; index < 128; index++) { - char src[4096] = {}; - DN_U32 src_size = DN_PCG32Range(&rng, 0, sizeof(src)); - - for (DN_USize src_index = 0; src_index < src_size; src_index++) - src[src_index] = DN_Cast(char) DN_PCG32Range(&rng, 0, 255); - - DN_Str8 input = DN_Str8FromPtr(src, src_size); - DN_TST_KeccakDispatch_(&result, hash_type, input); - } - DN_UT_End(&result); - } - } - #endif - return result; -} - -static DN_UTCore DN_TST_M4() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_M4\n"); - { - for (DN_UT_Test(&result, "Simple translate and scale matrix")) { - DN_M4 translate = DN_M4TranslateF(1, 2, 3); - DN_M4 scale = DN_M4ScaleF(2, 2, 2); - DN_M4 mul_result = DN_M4Mul(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_M4ColumnMajorString(mul_result).data, - DN_M4ColumnMajorString(EXPECT).data); - } - } - return result; -} - -static DN_UTCore DN_TST_OS() -{ - DN_UTCore result = DN_UT_Init(); - -#if defined(DN_OS_INC_CPP) || 1 - DN_UT_LogF(&result, "DN_OS\n"); - { - for (DN_UT_Test(&result, "Generate secure RNG 32 bytes")) { - char const ZERO[32] = {}; - char buf[32] = {}; - DN_OS_GenBytesSecure(buf, DN_ArrayCountU(buf)); - DN_UT_Assert(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0); - } - - for (DN_UT_Test(&result, "Query executable directory")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 os_result = DN_OS_EXEDir(&scratch.arena); - DN_UT_Assert(&result, os_result.count); - DN_UT_AssertF(&result, DN_OS_PathIsDir(os_result), "result(%zu): %.*s", os_result.count, DN_Str8PrintFmt(os_result)); - DN_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "DN_OS_PerfCounterNow")) { - uint64_t os_result = DN_OS_PerfCounterNow(); - DN_UT_Assert(&result, os_result != 0); - } - - for (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); - } - - for (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"); - { - for (DN_UT_Test(&result, "Make directory recursive \"abcd/efgh\"")) { - DN_UT_AssertF(&result, DN_OS_PathMakeDir(DN_Str8Lit("abcd/efgh")), "Failed to make directory"); - DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd")), "Directory was not made"); - DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd/efgh")), "Subdirectory was not made"); - DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd")) == false, "This function should only return true for files"); - DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd/efgh")) == false, "This function should only return true for files"); - DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd/efgh")), "Failed to delete directory"); - DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd")), "Failed to cleanup directory"); - } - - for (DN_UT_Test(&result, "File write, read, copy, move and delete")) { - // NOTE: Write step - DN_Str8 const SRC_FILE = DN_Str8Lit("dn_result_file"); - DN_B32 write_result = DN_OS_FileWriteAll(SRC_FILE, DN_Str8Lit("1234"), nullptr); - DN_UT_Assert(&result, write_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(SRC_FILE)); - - // NOTE: Read step - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 read_file = DN_OS_FileReadAllArena(&scratch.arena, SRC_FILE, nullptr); - DN_UT_AssertF(&result, read_file.count, "Failed to load file"); - DN_UT_AssertF(&result, read_file.count == 4, "File read wrong amount of bytes (%zu)", read_file.count); - DN_UT_AssertF(&result, DN_Str8Eq(read_file, DN_Str8Lit("1234")), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.count, DN_Str8PrintFmt(read_file)); - - // NOTE: Copy step - DN_Str8 const COPY_FILE = DN_Str8Lit("dn_result_file_copy"); - DN_B32 copy_result = DN_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr); - DN_UT_Assert(&result, copy_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(COPY_FILE)); - - // NOTE: Move step - DN_Str8 const MOVE_FILE = DN_Str8Lit("dn_result_file_move"); - DN_B32 move_result = DN_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); - DN_UT_Assert(&result, move_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(MOVE_FILE)); - DN_UT_AssertF(&result, DN_OS_PathIsFile(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); - DN_TCScratchEnd(&scratch); - } - } - - DN_UT_LogF(&result, "\nSemaphore\n"); - { - DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); - - for (DN_UT_Test(&result, "Wait timeout")) { - DN_U64 begin = DN_OS_PerfCounterNow(); - DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 100 /*timeout_ms*/); - DN_U64 end = DN_OS_PerfCounterNow(); - DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Timeout, "Received wait result %zu", wait_result); - DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); - DN_UT_AssertF(&result, elapsed_ms >= 80 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); - } - - for (DN_UT_Test(&result, "Wait success")) { - DN_OS_SemaphoreIncrement(&sem, 1); - DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 0 /*timeout_ms*/); - DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Success, "Received wait result %zu", wait_result); - } - - DN_OS_SemaphoreDeinit(&sem); - } - - DN_UT_LogF(&result, "\nMutex\n"); - { - DN_OSMutex mutex = DN_OS_MutexInit(); - for (DN_UT_Test(&result, "Lock")) { - DN_OS_MutexLock(&mutex); - DN_OS_MutexUnlock(&mutex); - } - DN_OS_MutexDeinit(&mutex); - } - - DN_UT_LogF(&result, "\nCondition Variable\n"); - { - DN_OSMutex mutex = DN_OS_MutexInit(); - DN_OSConditionVariable cv = DN_OS_ConditionVariableInit(); - for (DN_UT_Test(&result, "Lock and timeout")) { - DN_U64 begin = DN_OS_PerfCounterNow(); - DN_OS_ConditionVariableWait(&cv, &mutex, 100 /*sleep_ms*/); - DN_U64 end = DN_OS_PerfCounterNow(); - DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); - DN_UT_AssertF(&result, elapsed_ms >= 99 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); - } - DN_OS_MutexDeinit(&mutex); - DN_OS_ConditionVariableDeinit(&cv); - } -#endif - - return result; -} - -static DN_UTCore DN_TST_Rect() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_Rect\n"); - { - for (DN_UT_Test(&result, "No intersection")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From1N(0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(200, 0), DN_V2F32From2N(200, 200)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A's min intersects B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B's min intersects A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A's max intersects B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B's max intersects A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B contains A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A contains B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A equals B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = a; - DN_Rect ab = DN_RectIntersection(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_TST_BaseStrings() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Strings\n"); - { - for (DN_UT_Test(&result, "Str8 literal")) { - DN_Str8 string = DN_Str8Lit("AB"); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - 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]); - } - - for (DN_UT_Test(&result, "C-string length")) { - DN_USize size = DN_CStr8Count("hello"); - DN_UT_AssertF(&result, size == 5, "size=%zu", size); - } - - char arena_base[512]; - for (DN_UT_Test(&result, "Str8 format from arena")) { - DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5); - DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); - DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Str8 format from pool")) { - DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Pool pool = DN_PoolFromArena(&arena, 0); - DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5); - DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); - DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Str8x32 from U64")) { - DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' '); - DN_Str8 expect = DN_Str8Lit("123 456"); - DN_UT_AssertF(&result, DN_Str8Eq(DN_Str8FromStruct(&str8), expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Initialise with format string")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8FromFmtArena(&scratch.arena, "%s", "AB"); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - 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_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "Copy string")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8Lit("AB"); - DN_Str8 copy = DN_Str8FromStr8Arena(string, &scratch.arena); - DN_UT_AssertF(&result, copy.count == 2, "size: %zu", copy.count); - 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_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "Trim whitespace around string")) { - DN_Str8 string = DN_Str8TrimWhitespaceAround(DN_Str8Lit(" AB ")); - DN_UT_AssertF(&result, DN_Str8Eq(string, DN_Str8Lit("AB")), "[string=%.*s]", DN_Str8PrintFmt(string)); - } - - for (DN_UT_Test(&result, "Allocate string from arena")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8AllocArena(2, DN_ZMem_No, &scratch.arena); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - DN_TCScratchEnd(&scratch); - } - - // NOTE: TrimPrefix/Suffix ///////////////////////////////////////////////////////////////////// - for (DN_UT_Test(&result, "Trim prefix with matching prefix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit("nft/")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("abc")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim prefix with non matching prefix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit(" ft/")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim suffix with matching suffix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("abc")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("nft/")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim suffix with non matching suffix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("ab")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result)); - } - - // NOTE: DN_Str8IsAllDigits ////////////////////////////////////////////////////////////// - for (DN_UT_Test(&result, "Is all digits fails on non-digit string")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("@123string"), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, str_result == false); - } - - for (DN_UT_Test(&result, "Is all digits fails on nullptr")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(nullptr, 0), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, str_result == false); - } - - for (DN_UT_Test(&result, "Is all digits fails on string w/ 0 size")) { - char const buf[] = "@123string"; - DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(buf, 0), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, !str_result); - } - - for (DN_UT_Test(&result, "Is all digits success")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23"), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, DN_Cast(bool) str_result == true); - } - - for (DN_UT_Test(&result, "Is all digits fails on whitespace")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23 "), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, DN_Cast(bool) str_result == false); - } - - // NOTE: DN_Str8BSplit /////////////////////////////////////////////////////////////////// - { - { - char const *TEST_FMT = "Binary split \"%.*s\" with \"%.*s\""; - DN_Str8 delimiter = DN_Str8Lit("/"); - DN_Str8 input = DN_Str8Lit("abcdef"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abcdef")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - - input = DN_Str8Lit("abc/def"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abc")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("def")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - - input = DN_Str8Lit("/abcdef"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("abcdef")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - } - - { - DN_Str8 delimiter = DN_Str8Lit("-=-"); - DN_Str8 input = DN_Str8Lit("123-=-456"); - for (DN_UT_Test(&result, "Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("123")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("456")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - } - } - - // NOTE: DN_I64FromStr8 - for (DN_UT_Test(&result, "To I64: Convert empty string")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit(""), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 0); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"-0\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-0"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 0); - } - - for (DN_UT_Test(&result, "To I64: Convert \"-1\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == -1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1.2\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1.2"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_Assert(&result, str_result.value == 1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1,234\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,234"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 1234); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1,2\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,2"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 12); - } - - for (DN_UT_Test(&result, "To I64: Convert \"12a3\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("12a3"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_Assert(&result, str_result.value == 12); - } - - // NOTE: DN_U64FromStr8 - for (DN_UT_Test(&result, "To U64: Convert empty string")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit(""), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"-0\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-0"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"-1\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-1"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1.2\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1.2"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1,234\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,234"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1,2\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,2"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"12a3\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("12a3"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); - } - - // NOTE: DN_Str8Find - for (DN_UT_Test(&result, "Find: String (char) is not in buffer")) { - DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_Str8Lit("2"); - DN_Str8FindResult str_result = DN_Str8FindStr8(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.count == 0); - } - - for (DN_UT_Test(&result, "Find: String (char) is in buffer")) { - DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_Str8Lit("6"); - DN_Str8FindResult str_result = DN_Str8FindStr8(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_Str8FileNameFromPath - for (DN_UT_Test(&result, "File name from Windows path")) { - DN_Str8 buf = DN_Str8Lit("C:\\ABC\\str_result.exe"); - DN_Str8 str_result = DN_Str8FileNameFromPath(buf); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "File name from Linux path")) { - DN_Str8 buf = DN_Str8Lit("/ABC/str_result.exe"); - DN_Str8 str_result = DN_Str8FileNameFromPath(buf); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - // NOTE: DN_Str8TrimPrefix - for (DN_UT_Test(&result, "Trim prefix")) { - DN_Str8 prefix = DN_Str8Lit("@123"); - DN_Str8 buf = DN_Str8Lit("@123string"); - DN_Str8 str_result = DN_Str8TrimPrefix(buf, prefix, DN_Str8EqCase_Sensitive); - DN_UT_Assert(&result, DN_Str8Eq(str_result, DN_Str8Lit("string"))); - } - - // NOTE: DN_Str8TruncMiddle - { - for (DN_UT_Test(&result, "TruncMiddlePtr: Short string is not truncated")) { - DN_Str8 str = DN_Str8Lit("Hello"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 5); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Exact boundary (2*side_size) is not truncated")) { - DN_Str8 str = DN_Str8Lit("HelloWorld"); // 10 chars - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 10); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Long string is truncated in the middle")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); // 5 + 3 + 5 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Empty truncator concatenates head and tail")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit(""); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 10); // 5 + 0 + 5 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: side_size of 0 returns just truncator")) { - DN_Str8 str = DN_Str8Lit("HelloWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 0, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 3); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("...")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Null dest calculates size without writing")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_Assert(&result, res.str8.data == nullptr); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: count_req is consistent between dry-run and actual")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult dry = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); - char dest[64] = {}; - DN_Str8TruncResult actual = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, dry.count_req == actual.count_req); - DN_UT_Assert(&result, dry.truncated == actual.truncated); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Minimum buffer size (2*side_size + trunc.size + 1) is sufficient")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[14] = {}; // Exactly 2*5 + 3 + 1 - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Single character side size")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 1, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 5); // 1 + 3 + 1 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("H...d")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Large side_size falls back to copy")) { - DN_Str8 str = DN_Str8Lit("Hello"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 100, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 5); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - // NOTE: DN_Str8TruncMiddle (arena wrapper) - for (DN_UT_Test(&result, "TruncMiddle: Arena wrapper allocates and truncates correctly")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult res = DN_Str8TruncMiddle(str, 5, trunc, &scratch.arena); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - DN_UT_Assert(&result, res.str8.data[res.str8.count] == '\0'); - DN_TCScratchEnd(&scratch); - } - } - } - return result; -} - -static DN_UTCore DN_TST_Win() -{ - DN_UTCore result = DN_UT_Init(); - #if defined(DN_PLATFORM_WIN32) - DN_UT_LogF(&result, "OS Win32\n"); - { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 input8 = DN_Str8Lit("String"); - DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; - - for (DN_UT_Test(&result, "Str8 to Str16")) { - DN_Str16 str_result = DN_OS_W32Str8ToStr16(&scratch.arena, input8); - DN_UT_Assert(&result, DN_Str16Eq(str_result, input16)); - } - - for (DN_UT_Test(&result, "Str16 to Str8")) { - DN_Str8 str_result = DN_OS_W32Str16ToStr8(&scratch.arena, input16); - DN_UT_Assert(&result, DN_Str8Eq(str_result, input8)); - } - - for (DN_UT_Test(&result, "Str16 to Str8: Null terminates string")) { - int size_required = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); - char *string = DN_ArenaNewArray(&scratch.arena, char, size_required + 1, DN_ZMem_No); - - // Fill the string with error sentinels - DN_Memset(string, 'Z', size_required + 1); - - int size_returned = DN_OS_W32Str16ToStr8Buffer(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); - } - - for (DN_UT_Test(&result, "Str16 to Str8: Arena null terminates string")) { - DN_Str8 string8 = DN_OS_W32Str16ToStr8(&scratch.arena, input16); - int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); - char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; - - DN_UT_AssertF(&result, DN_Cast(int) string8.count == size_returned, "string_size: %d, result: %d", DN_Cast(int) string8.count, size_returned); - DN_UT_AssertF(&result, DN_Cast(int) string8.count == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1); - DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); - } - DN_TCScratchEnd(&scratch); - } - #endif // DN_PLATFORM_WIN32 - return result; -} - -static DN_UTCore DN_TST_Net() -{ - DN_UTCore result = DN_UT_Init(); -#if defined(DN_UNIT_TESTS_WITH_NET) - DN_Str8 label = {}; - DN_NETInterface net_interface = {}; -#if defined(DN_PLATFORM_EMSCRIPTEN) - net_interface = DN_NET_EmcInterface(); - label = DN_Str8Lit("Emscripten"); -#elif defined(DN_UNIT_TESTS_WITH_CURL) - net_interface = DN_NET_CurlInterface(); - label = DN_Str8Lit("CURL"); -#endif - - if (label.count) { - DN_UT_LogF(&result, "DN_NET\n"); - - DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(4), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org"); - DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com"); - - DN_USize net_base_size = DN_Megabytes(1); - char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes); - DN_NETCore net = {}; - net_interface.init(&net, net_base, net_base_size); - - DN_U64 arena_reset_p = DN_MemListPos(arena.mem); - for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) { - DN_NETRequestHandle request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); - DN_NETResponse response = net_interface.wait_for_response(request, &arena, UINT32_MAX); - DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); - DN_UT_AssertF(&result, response.error_str8.count == 0, "%.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_Assert(&result, response.body.count); - } - - for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) { - net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); - DN_NETResponse response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX); - DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); - DN_UT_AssertF(&result, response.error_str8.count == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_Assert(&result, response.body.count); - } - - for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) { - DN_NETRequestHandle request = net_interface.do_ws(&net, remote_ws_server_url); - DN_USize const WS_TIMEOUT_MS = 16; - - // NOTE: Wait for WS connection to open - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSOpen, "state=%d", response.state); - done = true; - } - - // NOTE: Receive the initial text from the echo server - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSText, "state=%d", response.state); - // NOTE: Send the close signal - net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close); - done = true; - } - - // NOTE: Expect to hear the close - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSClose, "state=%d"); - done = true; - } - } - net_interface.deinit(&net); - DN_MemListDeinit(arena.mem); - } -#endif // defined(DN_UNIT_TESTS_WITH_NET) - return result; -} - -DN_TSTResult DN_TST_RunSuite(DN_TSTPrint print) -{ - DN_UTCore tests[] = - { - DN_TST_Base(), - DN_TST_BaseArena(), - DN_TST_BaseStrings(), - DN_TST_BaseBytesHex(), - #if DN_H_WITH_HELPERS - DN_TST_BinarySearch(), - #endif - DN_TST_BaseDSMap(), - DN_TST_BaseIArray(), - DN_TST_BaseArray(), - DN_TST_Keccak(), - DN_TST_M4(), - DN_TST_OS(), - DN_TST_Rect(), - DN_TST_Win(), - DN_TST_Net(), - }; - - DN_TSTResult 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) { - if (test.num_tests_in_group <= 0) - continue; - bool do_print = print == DN_TSTPrint_Yes; - if (print == DN_TSTPrint_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; -} -#endif - #if DN_WITH_NET DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) { @@ -17030,15 +15894,14 @@ DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena * { DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - bool resp_failed = DN_NET_ResponseHasFailed(resp); - DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); + DN_Str8BuilderAppendF(&builder, "Request (%s", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); if (resp->type == DN_NETRequestType_HTTP) { if (resp->http_status) DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); } DN_Str8BuilderAppendF(&builder, ")"); if (resp->body.count || resp->error_str8.count) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" reported: ")); if (resp->body.count) DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); if (resp->error_str8.count) @@ -17064,7 +15927,7 @@ DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_ DN_Assert(request); if (request) { if (!request->mem.curr) - request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); + request->mem = DN_MemListFromHeap(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); request->arena = DN_ArenaTempBeginFromMemList(&request->mem); request->type = type; request->gen = DN_Max(request->gen + 1, 1); @@ -17528,7 +16391,7 @@ void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); DN_FmtAppend(curl->thread.name.data, &curl->thread.name.count, sizeof(curl->thread.name.data), "NET (CURL)"); - DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net); + DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, DN_OS_ThreadInitArgsDefault(), net); } void DN_NET_CurlDeinit(DN_NETCore *net) diff --git a/Single-Header/dn_single_header.h b/Single-Header/dn_single_header.h index 245c57f..7e3646e 100644 --- a/Single-Header/dn_single_header.h +++ b/Single-Header/dn_single_header.h @@ -1,212 +1,236 @@ -// Generated by the DN single header generator 2026-06-25 21:37:26 +// Generated by the DN single header generator 2026-07-05 12:47:31 #if !defined(DN_H) #define DN_H // NOTE: DN -// NOTE: Getting Started -// - Copy the entire DN folder to your project and include `dn.h` alongside your headers and -// include `dn.cpp` in one of your implementation files that has `dn.h` included in its scope, -// an example. There are #defines to selectively enable features of the library (see the -// configuration section for more info), example: +// Getting Started +// Copy the entire DN folder to your project and include `dn.h` alongside your headers and include +// `dn.cpp` in one of your implementation files that has `dn.h` included in its scope. +// There are #defines to selectively enable/disable features of the library. +// +// For this example, we enable _every_ feature possible to show all the possibilities upfront. +// All the #defines can be omitted and a default value will be set (in general everything defaults +// to off/disabled or fairly conservative). For more information see the configuration section. /* - #define DN_WITH_OS 1 // Enable OS features (like virtual mem, TLS, file system, threads) - #include "dn.h" + #define DN_WITH_OS 1 // Enable OS features (e.g.: virtual mem, TLS, file system, threads) + #define DN_WITH_STR8_AVX512F 1 // Enable AVX512F accelerated string functions + #define DN_WITH_NET 0 // Enable NET interface for HTTP/WS requests functionality. Requires CURL or Emscrpten + #define DN_WITH_NET_CURL 0 // Enable NET interface implementation using CURL's networking APIs (you must link libcurl to this TU unit) + #define DN_WITH_NET_EMSCRIPTEN 0 // Enable NET interface implementation using Emscripten's networking APIs (you must link emscripten to this TU unit) + #define DN_WITH_TESTS 1 // Enable the unit tests that can be run with `DN_TestSuite(arena)` + #define DN_ASAN_POISON 0 // Poison uncommitted memory in an Arena with Address Sanitizer's poisoning API + #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD // Temporary memory freed by an arena is scrubbed with this value to help catch UAFs + #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 // Enable Arenas to detect UAF when using temporary memory regions + #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 // Record stack traces when a UAF guard on an arena is created for better UAF crash diagnostics + #define DN_PARANOIA_LEVEL 1 // Enables various aforementioned debug functionality automatically, supported values [0..2] + #include "dn.h" + #include "dn.cpp" - #define DN_CPP_WITH_TESTS 1 - #include "dn.cpp" - - int main() - { - DN_Core core = {}; - DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); - return 0; - } + int main(int argc, char **argv) + { + DN_Core core = {}; + DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); + return 0; + } */ -// - `DN/Standalone` contains utilities that are self-contained and can be used without `dn.h` in -// a similar manner, typically a single header and single implementation file. +// `DN/Standalone` contains utilities that are self-contained and can be used without `dn.h` in +// a similar manner, typically a single header and single implementation file. -// NOTE: Configuration +// Configuration -// NOTE: OS layer -// Enable the operating system layer which enables thread context, file system, threads, e.t.c: +// OS layer +// Enable the operating system layer which enables thread context, file system, threads, e.t.c +// (default: 0) // -// #define DN_WITH_OS 1 +// #define DN_WITH_OS 1 -// NOTE: Networking layer -// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending -// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. +// Str8 AVX512F Layer +// AVX512 string functions can be compiled and made available (default: 0) // -// #define DN_WITH_NET 1 -// #define DN_WITH_NET_CURL 1 -// #define DN_WITH_NET_EMSCRIPTEN 1 -// -// For CURL, ensure that you the target application links to a libcurl and that the -// #include is visible before this translation unit. -// -// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. +// #define DN_WITH_STR8_AVX512F 1 -// NOTE: Platform Target -// Define one of the following directives to configure this library to compile for that -// platform. By default, the library will auto-detect the current host platform and select that -// as the target platform. +// Unit Tests +// Unit tests can be compiled and run using the `DN_TestSuite(...)` function (default: 0) // -// DN_PLATFORM_EMSCRIPTEN -// DN_PLATFORM_POSIX -// DN_PLATFORM_WIN32 -// -// For example -// -// #define DN_PLATFORM_WIN32 -// -// Will ensure that is included and the OS layer is implemented using Win32 -// primitives. +// #define DN_WITH_TESTS 1 -// NOTE: Static functions -// All public functions in the DN library are prefixed with the macro '#define DN_API'. By -// default 'DN_API' is not defined to anything. Define +// Networking layer +// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending +// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. // -// DN_STATIC_API +// #define DN_WITH_NET 1 +// #define DN_WITH_NET_CURL 1 +// #define DN_WITH_NET_EMSCRIPTEN 1 // -// To replace all the functions prefixed with DN_API to be prefixed with 'static' ensuring that -// the functions in the library do not export an entry into the linking table. -// translation units. +// For CURL, ensure that you the target application links to a libcurl and that the +// #include is visible before this translation unit. +// +// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. -// NOTE: Disabling the in-built (if #define DN_WITH_OS 1) -// If you are building DN for the Windows platform, is a large legacy header that -// applications have to include to use Windows APIs. By default this library uses a replacement -// header for all the Windows functions that it uses in the OS layer removing the need to -// include to improve compilation times. This mini header will conflict with -// if it needs to be included in your project. The mini header can be disabled by -// defining: +// Platform Target +// Define one of the following directives to configure this library to compile for that +// platform. By default, the library will auto-detect the current host platform and select that +// as the target platform. // -// DN_NO_WINDOWS_H_REPLACEMENT_HEADER +// DN_PLATFORM_EMSCRIPTEN +// DN_PLATFORM_POSIX +// DN_PLATFORM_WIN32 // -// To instead use . DN automatically detects if is included in an earlier -// translation unit and will automatically disable the in-built replacement header in which case -// this does not need to be defined. +// For example +// +// #define DN_PLATFORM_WIN32 +// +// Will ensure that is included and the OS layer is implemented using Win32 +// primitives. -// NOTE: Freestanding -// The base layer can be used without an OS implementation by defining DN_FREESTANDING like: +// Static functions +// All public functions in the DN library are prefixed with the macro '#define DN_API'. By +// default 'DN_API' is not defined to anything (default: nil) // -// #define DN_FREESTANDING +// #define DN_API // -// This means functionality that relies on the OS like printing, memory allocation, stack traces -// and so forth are disabled. +// To replace all the functions prefixed with DN_API to be prefixed with 'static' ensuring that +// the functions in the library do not export an entry into the linking table. +// translation units. +// +// #define DN_API static -// NOTE: ASAN Arena Poisoning -// When compiled with address sanitizer (.e.g -fsanitize=address) you can optionally enable -// memory region poisoning on the inbuilt arena's to catch in certain scenarios, use-after-free +// Disabling the in-built (if #define DN_WITH_OS 1) +// If you are building DN for the Windows platform, is a large legacy header that +// applications have to include to use Windows APIs. By default this library uses a replacement +// header for all the Windows functions that it uses in the OS layer removing the need to +// include to improve compilation times. This mini header will conflict with +// if it needs to be included in your project. The mini header can be disabled by +// defining: // -// #define DN_ASAN_POISON 1 +// #define DN_NO_WINDOWS_H_REPLACEMENT_HEADER // -// Since arenas manage their own block of memory it does not automatically benefit from ASAN's -// memory markup that ASAN does and so it is implemented manually by using the ASAN user-level -// poisoning APIs. Similarly, since the arena recycles its own memory rather than release back -// to the OS, poisoning is not as effective for arenas but every little bit helps. +// To instead use . DN automatically detects if is included in an earlier +// translation unit and will automatically disable the in-built replacement header in which case +// this does not need to be defined. -// NOTE: Scrub Uninitialised Memory -// If this macro is defined, temp memory that is returned to an arena, or allocations freed by -// a pool are scrubbed to this specified byte, in absence of this bytes returned to the -// allocators are left as-is or memset to 0. For example to scrub bytes to 0xCD (MSVC's -// pattern) define as follows: +// Freestanding +// TODO: This is not really supported or dealt with properly. Re-review it +// The base layer can be used without an OS implementation by defining DN_FREESTANDING like: // -// #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD +// #define DN_FREESTANDING // -// Due to the recycling of memory in arenas and pool, similarly to ASAN poisoning this reduces -// the window in which a use-after-free can be detected using this guard, however every little -// bit helps. +// This means functionality that relies on the OS like printing, memory allocation, stack traces +// and so forth are disabled. -// NOTE: Arena temp memory use-after-free (UAF) tooling -// UAF Guard -// Set the following preprocessor value to 1 to enable UAF protection when using -// scratch/temporary memory functionality. Defaults to off, or 0 if not specified +// ASAN Arena Poisoning +// When compiled with address sanitizer (.e.g -fsanitize=address) you can optionally enable +// memory region poisoning on the inbuilt arena's to catch in certain scenarios, use-after-free +// (default: ON if PARANOIA level is >=1, OFF otherwise) // -// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 +// #define DN_ASAN_POISON 1 // -// This enables arenas to markup itself with the active memory region and subsequent -// allocations check if the allocation belongs to the same or different region. Different -// regions cause a UAF violation. -// -// More detailed diagnostics can be enabled by setting the flag DN_ArenaFlags_TempMemUAFGuard -// on the affected arenas. Note that this incurs a performance penalty as each memory region -// will store a stacktrace of its creation. It's recommended in development builds to always -// run with temp-memory guarding, if a violation occurs, then enable tracing on the arena to -// pinpoint the issue. -// -// Enabling memory guard incurs additional memory requirements from the arena's backing -// memory block and additional book-keeping fields on each arena and their temp memory -// instances. -// -// NOTE: UAF Tracing -// Set the following preprocessor value to 1 to enable tracing when the UAF guard triggers. -// Defaults to off, or 0 if not specified. -// -// #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 -// -// This opts in all arenas to tracing functionality by default globally. Arenas can opt out -// by setting the flag DN_ArenaFlags_TempMemUAFTraceDisable on the arena. The disable flag -// takes precedence over all other settings including the global preprocessor macro and the -// enablement flag (DN_ArenaFlags_TempMemUAFTrace). -// -// Tracing incurs an additional much heavier performance penalty than the UAF guard due to -// the stacktrace that is stored per region to report to the user when a UAF guard violation -// occurs. +// Since arenas manage their own block of memory it does not automatically benefit from ASAN's +// memory markup that ASAN does and so it is implemented manually by using the ASAN user-level +// poisoning APIs. Similarly, since the arena recycles its own memory rather than release back +// to the OS, poisoning is not as effective for arenas but every little bit helps. -// NOTE: Paranoia Level -// Set the `DN_PARANOIA_LEVEL` to an integer value to enable various validation layers and -// error checking mechanisms in the codebase and primitives exposed by the library. Defaults to -// paranoia level 0 in release builds and level 1 for debug. +// Scrub Uninitialised Memory +// If this macro is defined, temp memory that is returned to an arena, or allocations freed by +// a pool are scrubbed to this specified byte, in absence of this bytes returned to the +// allocators are left as-is or memset to 0. For example to scrub bytes to 0xCD (MSVC's +// pattern) (default: 0xCD if PARANOIA level is >=1, 0x0 otherwise) // -// #define DN_PARANOIA_LEVEL 1 +// #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD // -// Each level activates the following debug mechanisms. Note that any of the following #defines -// enabled by a paranoia level can be overridden by defining the preprocessor definition before -// the inclusion of this file. +// Due to the recycling of memory in arenas and pool, similarly to ASAN poisoning this reduces +// the window in which a use-after-free can be detected using this guard, however every little +// bit helps. + +// Arena temp memory use-after-free (UAF) tooling +// UAF Guard +// Set the following preprocessor value to 1 to enable UAF protection when using +// scratch/temporary memory functionality (default: 1 if PARANOIA level >=1, 0 otherwise) // -// Level 0 -// `DN_Assert` calls are compiled out +// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 // -// `DN_Verify` calls logs an error and continues +// This enables arenas to markup itself with the active memory region and subsequent +// allocations check if the allocation belongs to the same or different region. Different +// regions cause a UAF violation. // -// `DN_VerifyWarning` calls logs a warning and continues +// More detailed diagnostics can be enabled by setting the flag DN_ArenaFlags_TempMemUAFGuard +// on the affected arenas. Note that this incurs a performance penalty as each memory region +// will store a stacktrace of its creation. It's recommended in development builds to always +// run with temp-memory guarding, if a violation occurs, then enable tracing on the arena to +// pinpoint the issue. // -// Level 1 -// `DN_Assert` calls are compiled in +// Enabling memory guard incurs additional memory requirements from the arena's backing +// memory block and additional book-keeping fields on each arena and their temp memory +// instances. // -// `DN_Verify` calls a debug trap rather than just logging and continuing +// UAF Tracing +// Set the following preprocessor value to 1 to enable tracing when the UAF guard triggers +// (default: 1 if PARANOIA level >=2, 0 otherwise) // -// `DN_Verify` calls dump a stack trace when triggered +// #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 // -// `DN_ASAN_POISON` is set. When an arena allocates memory unallocated bytes from the -// memory owned by the arena are manually poisoned using ASAN. A fault will be triggered if -// the memory is written to (UAF e.g. use-after-free). Address sanitizer must be enabled or -// otherwise this is a no-op. This incurs a performance penalty on-top of the overhead of -// running ASAN on your binary as recycling memory calls into ASAN to poison the region. +// This opts in all arenas to tracing functionality by default globally. Arenas can opt out +// by setting the flag DN_ArenaFlags_TempMemUAFTraceDisable on the arena. The disable flag +// takes precedence over all other settings including the global preprocessor macro and the +// enablement flag (DN_ArenaFlags_TempMemUAFTrace). // -// `DN_ARENA_TEMP_MEM_UAF_GUARD` is set. When an arena uses temporary memory it will record -// the active temporary memory region and compare them when allocating to ensure that -// memory is allocated in the active region otherwise a UAF fault is triggered. This has a -// small runtime performance penalty. +// Tracing incurs an additional much heavier performance penalty than the UAF guard due to +// the stacktrace that is stored per region to report to the user when a UAF guard violation +// occurs. + +// Paranoia Level +// Set the `DN_PARANOIA_LEVEL` to an integer value to enable various validation layers and +// error checking mechanisms in the codebase and primitives exposed by the library +// (default: PARANOID level 1 in debug builds, 0 otherwise) +// paranoia level 0 in release builds and level 1 for debug. // -// `DN_SCRUB_UNINIT_MEM_BYTE` is set to `0xCD`. When memory is cleared in an arena or a -// pool backed by an arena upon deallocation if the `DN_ZMem_Yes` flag is passed then the -// bytes are scrubbed to this byte to make UAF more salient. +// #define DN_PARANOIA_LEVEL 1 // -// Level 2 -// `DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT` is set. When an arena uses temporary memory -// regions that region's a stack trace of the call site is recorded. This is very expensive -// but when a temporary memory region is used after it has been deallocated, a full stack -// trace diagnostic is available of where the various regions where created and freed. +// Each level activates the following debug mechanisms. Note that any of the following #defines +// enabled by a paranoia level can be overridden by defining the preprocessor definition before +// the inclusion of this file. // -// NOTE: Str8 AVX512F variants -// We have some AVX512 string functions that can be enabled by defining the following +// Level 0 +// `DN_Assert` calls are compiled out +// +// `DN_Verify` calls logs an error and continues +// +// `DN_VerifyWarning` calls logs a warning and continues +// +// Level 1 +// `DN_Assert` calls are compiled in +// +// `DN_Verify` calls a debug trap rather than just logging and continuing +// +// `DN_Verify` calls dump a stack trace when triggered +// +// `DN_ASAN_POISON` is set. When an arena allocates memory unallocated bytes from the +// memory owned by the arena are manually poisoned using ASAN. A fault will be triggered if +// the memory is written to (UAF e.g. use-after-free). Address sanitizer must be enabled or +// otherwise this is a no-op. This incurs a performance penalty on-top of the overhead of +// running ASAN on your binary as recycling memory calls into ASAN to poison the region. +// +// `DN_ARENA_TEMP_MEM_UAF_GUARD` is set. When an arena uses temporary memory it will record +// the active temporary memory region and compare them when allocating to ensure that +// memory is allocated in the active region otherwise a UAF fault is triggered. This has a +// small runtime performance penalty. +// +// `DN_SCRUB_UNINIT_MEM_BYTE` is set to `0xCD`. When memory is cleared in an arena or a +// pool backed by an arena upon deallocation if the `DN_ZMem_Yes` flag is passed then the +// bytes are scrubbed to this byte to make UAF more salient. +// +// Level 2 +// `DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT` is set. When an arena uses temporary memory +// regions that region's a stack trace of the call site is recorded. This is very expensive +// but when a temporary memory region is used after it has been deallocated, a full stack +// trace diagnostic is available of where the various regions where created and freed. // -// #define DN_STR8_AVX512F 1 // DN: Single header generator commented out => #if defined(_CLANGD) // #define DN_WITH_OS 1 +// #define DN_WITH_TESTS 1 // #define DN_STR8_AVX512F 1 // #define DN_PARANOIA_LEVEL 1 // #endif @@ -306,10 +330,8 @@ #define DN_FORCE_INLINE inline __attribute__((always_inline)) #endif -// NOTE: Function/Variable Annotations -#if defined(DN_STATIC_API) - #define DN_API static -#else +// NOTE: Function Prefix +#if !defined(DN_API) #define DN_API #endif @@ -1090,28 +1112,33 @@ enum DN_MemPage_ #define DN_ARENA_COMMIT_SIZE DN_Kilobytes(64) #endif -enum DN_MemFuncsType +enum DN_HeapType { - DN_MemFuncsType_Nil, - DN_MemFuncsType_Heap, - DN_MemFuncsType_Virtual, + DN_HeapType_Nil, + DN_HeapType_Basic, + DN_HeapType_Virtual, }; -typedef void *(DN_MemHeapAllocFunc)(DN_USize count); -typedef void (DN_MemHeapDeallocFunc)(void *ptr); -typedef void *(DN_MemVirtualReserveFunc)(DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); -typedef bool (DN_MemVirtualCommitFunc)(void *ptr, DN_USize count, DN_U32 page_flags); -typedef void (DN_MemVirtualReleaseFunc)(void *ptr, DN_USize count); -struct DN_MemFuncs +typedef void *(DN_HeapBasicAllocFunc) (DN_USize count); +typedef void (DN_HeapBasicDeallocFunc)(void *ptr); + +typedef void *(DN_HeapVirtualReserveFunc)(DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); +typedef bool (DN_HeapVirtualCommitFunc)(void *ptr, DN_USize count, DN_U32 page_flags); +typedef void (DN_HeapVirtualReleaseFunc)(void *ptr, DN_USize count); +struct DN_Heap { - DN_MemFuncsType type; - DN_MemHeapAllocFunc *heap_alloc; - DN_MemHeapDeallocFunc *heap_dealloc; + DN_HeapType type; + DN_USize bytes_alloc_total; + DN_USize bytes_alloc; + DN_USize bytes_freed; + + DN_HeapBasicAllocFunc* basic_alloc; + DN_HeapBasicDeallocFunc* basic_dealloc; DN_U32 virtual_page_size; - DN_MemVirtualReserveFunc *virtual_reserve; - DN_MemVirtualCommitFunc *virtual_commit; - DN_MemVirtualReleaseFunc *virtual_release; + DN_HeapVirtualReserveFunc *virtual_reserve; + DN_HeapVirtualCommitFunc *virtual_commit; + DN_HeapVirtualReleaseFunc *virtual_release; }; struct DN_MemBlock @@ -1167,15 +1194,15 @@ enum DN_MemFlags_ DN_MemFlags_TempMemUAFTraceDisable = 1 << 6, // NOTE: Internal flags. Do not use - DN_MemFlags_UserBuffer = 1 << 7, - DN_MemFlags_MemFuncs = 1 << 8, + DN_MemFlags_UserBuffer = 1 << 7, + DN_MemFlags_Heap = 1 << 8, }; struct DN_MemList { DN_MemBlock* curr; DN_MemFlags flags; - DN_MemFuncs funcs; + DN_Heap heap; DN_MemStats stats; DN_Str8 label; @@ -1835,60 +1862,109 @@ struct DN_ArrayFindResult }; typedef bool (DN_ArrayFindEqFunc)(void const *lhs, void const *find); -enum DN_DSMapKeyType +enum DN_HTableDeallocPoolOnDeinit { - // 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 + DN_HTableDeallocPoolOnDeinit_No, + DN_HTableDeallocPoolOnDeinit_Yes, }; -struct DN_DSMapKey +enum DN_HTableHashSentinel { - 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; + DN_HTableHashSentinel_Empty = 0, + DN_HTableHashSentinel_Tomb, + DN_HTableHashSentinel_FirstValid, }; -template -struct DN_DSMapSlot +enum DN_HTableFlags_ { - DN_DSMapKey key; // Hash table lookup key - T value; // Hash table value + DN_HTableFlags_DeallocPoolOnDeinit = 1 << 0, + DN_HTableFlags_UsingHeap = 1 << 1, }; -typedef DN_U32 DN_DSMapFlags; -enum DN_DSMapFlags_ +typedef DN_U32 DN_HTableHashType; +typedef DN_U32 DN_HTableFlags; + +typedef DN_U32(DN_HTableHashFunc) (void const *key, DN_USize size); +typedef bool (DN_HTableKeyEqFunc)(void const *lhs, void const *rhs, DN_USize size); +struct DN_HTable { - DN_DSMapFlags_Nil = 0, - DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, + // NOTE: The total number of slots used by the table is defined as `count` + `tombs_count` + DN_USize count; // Count of `kvs` that are filled with active values (e.g. hash >= `FirstValid`) + DN_USize tombs_count; // Count of `kvs` that are tombstoned (e.g. hash == `Tomb`) + DN_USize max; // Max capacity of `kvs` + DN_F32 load_factor01; + DN_HTableHashFunc* hash_func; + DN_HTableKeyEqFunc* key_eq_func; + + DN_HTableFlags flags; + DN_Heap heap; + DN_Pool pool; + + // NOTE: Metadata for handling genericism (in C that means type-erasure) + void** kvs; + DN_USize offset_of_hash; + DN_USize size_of_kv; + DN_USize offset_of_key; + DN_USize size_of_key; + DN_USize offset_of_value; + DN_USize size_of_value; }; -using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); -template struct DN_DSMap +struct DN_HTableInitArgs { - 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; + DN_F32 load_factor; + DN_HTableHashFunc* hash_func; + DN_HTableKeyEqFunc* key_eq_func; + DN_USize max; + void** kvs; + DN_USize offset_of_hash; + DN_USize size_of_kv; + DN_USize offset_of_key; + DN_USize size_of_key; + DN_USize offset_of_value; + DN_USize size_of_value; }; -template struct DN_DSMapResult +struct DN_HTableSlot { - bool found; - DN_DSMapSlot *slot; - T *value; + // NOTE: Type-erased lookup of the {hash, key, value} object in the hash table + DN_HTableHashType hash_u32; // Hash copied out to a U32 for convenience + DN_USize kvs_index; // Index into `kvs` that the following {hash, key, value} pointers are pointing to + void* kv_struct; // Pointer to the matched kv struct + void* hash; // Pointer to the hash embedded in the user's `kvs` struct + void* key; // Pointer to the key embedded in the user's `kvs` struct + void* value; // Pointer to the value embedded in the user's `kvs` struct +}; + +struct DN_HTableAddResult +{ + bool success; // Adding fails if adding required a table resize and resizing failed (out-of-memory) + bool existed; // Set if the item you attempted to add, already existed in the table and it was updated instead + DN_HTableSlot slot; // Table slot containing the value you just added, zero initialised struct on failure +}; + +struct DN_HTableLookupResult +{ + DN_HTableHashType key_hash; // hash(key) that was used to look up `slot` + DN_HTableSlot slot; // KV object from `kvs`, always set (either empty, tombed, or active slot) +}; + +struct DN_HTableResizeResult +{ + bool success; + bool needed_resize; +}; + +struct DN_HTableInitResult +{ + DN_HTable table; + bool success; // Fails if the allocator could not allocate memory for the table +}; + +enum DN_HTableAllowTombstone +{ + DN_HTableAllowTombstone_No, + DN_HTableAllowTombstone_Yes, }; enum DN_LeakAllocFlag @@ -1911,13 +1987,19 @@ struct DN_LeakAlloc // expensive. Enforce that there is no unexpected padding. DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively +struct DN_LeakTrackerKV +{ + DN_HTableHashType hash; + DN_UPtr key; + DN_LeakAlloc value; +}; + struct DN_LeakTracker { - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_MemList alloc_table_mem; - DN_Arena alloc_table_arena; - DN_U64 alloc_table_bytes_allocated_for_stack_traces; + DN_LeakTrackerKV *alloc_table_kvs; + DN_HTable alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; }; typedef DN_USize DN_InitFlags; @@ -1934,6 +2016,103 @@ enum DN_InitFlags_ DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, }; +enum DN_BinPackMode +{ + DN_BinPackMode_Serialise, + DN_BinPackMode_Deserialise, +}; + +struct DN_BinPack +{ + DN_Str8Builder writer; + DN_Str8 read; + DN_USize read_index; +}; + +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; +}; + +struct DN_TestDiagnosticRow +{ + DN_Str8 expr; + DN_Str8 invariant; + DN_CallSite call_site; + DN_Str8 message; +}; + +struct DN_TestEntry +{ + bool failed; + DN_Str8 name; + DN_TestDiagnosticRow* diagnostics; + DN_USize diagnostics_count; + DN_USize diagnostics_max; + DN_U64 ts_begin; + DN_U64 ts_end; +}; + +struct DN_TestGroup +{ + DN_Str8 name; + DN_TestEntry* entries; // Array of tests cases that have been executed/are being executed + DN_USize entries_count; + DN_USize entries_max; + DN_U64 ts_begin; // Timestamp of when the test started (using DN_OS_PerfCounter) + DN_U64 ts_end; // Timestamp of when the test ended + DN_TestEntry* curr_entry; // Active test being executed and results should be updated into +}; + +struct DN_TestCore +{ + DN_Arena* arena; + DN_Pool pool; + DN_USize total_passed; // Total number of tests (`TestEntry`) across all groups that passed + DN_USize total_failed; // Total number of tests (`TestEntry`) across all groups that failed + DN_USize total_count; // Total number of tests (`TestEntry`) across all groups + + DN_TestGroup* groups; + DN_USize groups_count; + DN_USize groups_max; + + DN_TestGroup* curr_group; // Active test group being executed and test entries are being added to +}; + +enum DN_TestLogic +{ + DN_TestLogic_GreaterThan, + DN_TestLogic_GreaterThanEq, + DN_TestLogic_LessThan, + DN_TestLogic_LessThanEq, + DN_TestLogic_Eq, + DN_TestLogic_NotEq, +}; + +typedef DN_U32 DN_Str8FromTestCoreFlags; +enum DN_Str8FromTestCoreFlags_ +{ + DN_Str8FromTestCoreFlags_Nil, + DN_Str8FromTestCoreFlags_Colour, +}; + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC @@ -4062,8 +4241,18 @@ struct DN_OSConditionVariable DN_U64 handle; }; -// NOTE: DN_OSThread typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); +struct DN_OSThreadInitArgs +{ + // NOTE: Customise much memory the thread's TLS context will be initialised with which contains + // persistent/temporary allocators and misc facilities that each thread has exclusive access to. + DN_TCInitArgs tc_args; + + // NOTE: Set how much stack space in bytes the thread will have. Leave this value to 0 to defer + // to the OS's default stack size. + DN_USize stack_size; +}; + struct DN_OSThreadLane { DN_USize index; @@ -4190,14 +4379,26 @@ struct DN_Core #endif }; -// NOTE: Library initialisation. This must be called before using the library once to setup TLS and -// query the OS for information (such as page size) for tuning allocations and so forth. The caller -// should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// NOTE: Library initialisation +// Overview +// You must initialise the library to setup pre-requisite data structures that some APIs in the +// library take advantage of. The `DN_Core` object must remain valid for the lifetime of the +// library. // -// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used -// internally by the library. If you have an application that has the concept of frames, you may -// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it tracks -// the number of memory allocations for the current frame and that counter can be reset. +// API +// DN_Init +// This must be called before using the library once to setup TLS and query the OS for +// information (such as page size) for tuning allocations and so forth. The +// caller should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// +// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used +// internally by the library. If you have an application that has the concept of frames, you may +// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it +// tracks the number of memory allocations for the current frame and that counter can be reset. +// +// For convenience you can opt into some sane defaults for the `DN_TCInitArgs` which customises +// how thread-local storage is setup with `DN_TCInitArgsDefault()` which produces a +// `DN_TCInitArgs` object you can pass in by value. DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); DN_API void DN_Set (DN_Core *dn); DN_API DN_Core* DN_Get (); @@ -4348,20 +4549,48 @@ DN_API DN_F32 DN_EpsilonClampF32 DN_API DN_MemStats DN_MemStatsSum (DN_MemStats lhs, DN_MemStats rhs); DN_API DN_MemStats DN_MemStatsSumArray (DN_MemStats const *array, DN_USize count); +// NOTE: Heap +// Overview +// Generic heap allocators that can do arbitrary memory allocations and deallocations. It's a very +// thin wrapper that tracks the number of bytes allocated and deallocated and is accepted by some +// data structures in the codebase that opt out or have an alternative to arena allocations (of +// which arenas are the predominant where it makes sense to). +// +// Basic heap takes an API for routines in the likes of malloc/free (CRT/POSIX) or +// HeapAlloc/HeapFree (Win32) which take a size and a pointer to free. +// +// Virtual heap takes an API for routines in the likes of VirtualAlloc (Win32) or mmap (POSIX) and +// those family of functions. + +// API +// DN_HeapAlloc +// DN_HeapDealloc +// Allocate and deallocate memory. Note that `DN_HeapAlloc` takes reserve and commit even for +// the basic allocator. In the basic allocator which does not have a concept of reserving and +// committing, the `commit` value is ignored and the `reserve` amount is the amount that will be +// allocated. +DN_API DN_Heap DN_HeapInitBasic (DN_HeapBasicAllocFunc *basic_alloc, DN_HeapBasicDeallocFunc *basic_dealloc); +DN_API DN_Heap DN_HeapInitVirtual (DN_U32 page_size, DN_HeapVirtualReserveFunc *virtual_reserve, DN_HeapVirtualCommitFunc *virtual_commit, DN_HeapVirtualReleaseFunc *virtual_release); +DN_API void* DN_HeapAlloc (DN_Heap *heap, DN_USize reserve, DN_USize commit); +DN_API void DN_HeapDealloc (DN_Heap *heap, void *ptr, DN_USize size); + // NOTE: MemList // Overview // `MemList` is an implementation of a classical `Arena` (e.g. bump allocator, can dynamically // grow, frees by bumping pointer back, sub-divides a block of memory). The term `Arena` is // reserved as a thin-layer over the functionality here to provide some use-after-free protection. // See `Arena` for more info. +// +// In general for 99% of use-cases you want an `Arena` and if some API is not exposed on the +// `Arena` to then use the `MemList` API on the embedded `mem_list` backing the Arena. DN_API DN_MemList DN_MemListFromBuffer (void *buffer, DN_USize size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs); +DN_API DN_MemList DN_MemListFromHeap (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap); DN_API void DN_MemListDeinit (DN_MemList *mem); DN_API bool DN_MemListCommit (DN_MemList *mem, DN_U64 size); DN_API bool DN_MemListCommitTo (DN_MemList *mem, DN_U64 pos); DN_API bool DN_MemListGrow (DN_MemList *mem, DN_U64 reserve, DN_U64 commit); -DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); -DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); +DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem zmem); +DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem zmem); DN_API void * DN_MemListCopy (DN_MemList *mem, void const *data, DN_U64 size, uint8_t align); DN_API void DN_MemListPopTo (DN_MemList *mem, DN_U64 init_used); DN_API void DN_MemListPop (DN_MemList *mem, DN_U64 amount); @@ -4401,10 +4630,10 @@ DN_API void DN_MemListTempEnd // To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` // field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you // want to forgo any of this infrastructure store and use the `MemList` directly in your codebase. -// + // UAF Example /* - DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitDefault()); DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); { // NOTE: You can also `TempBegin` with `&temp`, either is valid. They both have pointers to @@ -4416,8 +4645,9 @@ DN_API void DN_MemListTempEnd // the same underlying memory block sitting in `arena`. // // But the intent here is that the caller is resetting `nested_temp`'s allocations and not - // `temp` hence the UAF protection triggers. - DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); + // `temp` hence the UAF protection triggers. The only permissible way to allocate in this + // region is to allocate from `nested_temp` (the active region). + DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); DN_ArenaTempEnd(&nested_temp); } @@ -4425,12 +4655,13 @@ DN_API void DN_MemListTempEnd DN_ArenaDeinit(&arena); // Frees the memory */ DN_API DN_Arena DN_ArenaFromMemList (DN_MemList *mem); +DN_API DN_Arena DN_ArenaFromHeap (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap); DN_API DN_Arena DN_ArenaTempBeginFromMemList (DN_MemList *mem); DN_API DN_Arena DN_ArenaTempBeginFromArena (DN_Arena *arena); DN_API void DN_ArenaTempEnd (DN_Arena *arena, DN_ArenaReset reset); -DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem); -DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_arena); -DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); +DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem); +DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_arena); +DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, DN_U8 align); DN_API void DN_ArenaDeinit (DN_Arena *arena); #define DN_ArenaNew(arena, T, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), zmem) @@ -4477,7 +4708,7 @@ DN_API void DN_ErrSinkAppendF_ DN_API DN_TCInitArgs DN_TCInitArgsDefault (); DN_API void DN_TCInit (DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena); -DN_API void DN_TCInitFromMemFuncs (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs); +DN_API void DN_TCInitFromHeap (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_Heap heap); DN_API void DN_TCDeinit (DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas); DN_API void DN_TCEquip (DN_TCCore *tc); DN_API DN_TCCore* DN_TCGet (); @@ -4502,9 +4733,13 @@ DN_API bool DN_CharIsHex DN_API char DN_CharToLower (char ch); DN_API char DN_CharToUpper (char ch); -DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string, char separator); -DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size, char separator); -DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size, char separator); +DN_API DN_U64FromResult DN_U64FromStr8Delimiters (DN_Str8 string, DN_Str8 const *delimiters, DN_USize delimiters_count); +DN_API DN_U64FromResult DN_U64FromStr8Delimiter (DN_Str8 string, DN_Str8 delimiter); +DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string); +DN_API DN_U64FromResult DN_U64FromPtrDelimiter (void const *data, DN_USize size, DN_Str8 delimiter); +DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size); +DN_API DN_U64 DN_U64FromPtrUnsafeDelimiter (void const *data, DN_USize size, DN_Str8 delimiter); +DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size); DN_API DN_U64FromResult DN_U64FromHexPtr (void const *hex, DN_USize hex_count); DN_API DN_U64 DN_U64FromHexPtrUnsafe (void const *hex, DN_USize hex_count); DN_API DN_U64FromResult DN_U64FromHexStr8 (DN_Str8 hex); @@ -4609,18 +4844,27 @@ DN_API DN_Str8FindResult DN_Str8FindStr8 DN_API DN_Str8FindResult DN_Str8Find (DN_Str8 string, DN_Str8FindFlag flags); DN_API DN_Str8 DN_Str8Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); DN_API DN_Str8 DN_Str8ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); +DN_API bool DN_Str8EqSensitive (DN_Str8 lhs, DN_Str8 rhs); DN_API bool DN_Str8EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); -DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API bool DN_Str8StartsWithSensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8StartsWithInsensitive (DN_Str8 string, DN_Str8 prefix); -DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API bool DN_Str8EndsWithSensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8EndsWithInsensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8HasChar (DN_Str8 string, char ch); -DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimPrefixSensitive (DN_Str8 string, DN_Str8 prefix); +DN_API DN_Str8 DN_Str8TrimPrefixInsensitive (DN_Str8 string, DN_Str8 prefix); DN_API DN_Str8 DN_Str8TrimHexPrefix (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimSuffixSensitive (DN_Str8 string, DN_Str8 suffix); +DN_API DN_Str8 DN_Str8TrimSuffixInsensitive (DN_Str8 string, DN_Str8 suffix); +DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimAroundSensitive (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8TrimAroundInsensitive (DN_Str8 string, DN_Str8 trim_string); DN_API DN_Str8 DN_Str8TrimHeadWhitespace (DN_Str8 string); DN_API DN_Str8 DN_Str8TrimTailWhitespace (DN_Str8 string); DN_API DN_Str8 DN_Str8TrimWhitespaceAround (DN_Str8 string); @@ -4649,7 +4893,7 @@ DN_API DN_Str8 DN_Str8LineBreakAllocator DN_API DN_Str8 DN_Str8LineBreakArena (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena); DN_API DN_Str8 DN_Str8Table (DN_Str8 const* rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena); -#if DN_STR8_AVX512F +#if DN_WITH_STR8_AVX512F DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F (DN_Str8 string, DN_Str8 find); @@ -5422,10 +5666,14 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_TArrayFindMemEq(ptr, size, ptr_find) #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_TArrayResizeFromPool(&(ptr), ptr_size, ptr_max, pool, new_max) #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_TArrayResizeFromArena(&(ptr), ptr_size, ptr_max, arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), size, ptr_max, pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), size, ptr_max, arena, new_max) - #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) - #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) + #define DN_PArrayGrowFromPool(ptr, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), ptr_max, pool, new_max) + #define DN_PArrayGrowFromArena(ptr, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), ptr_max, arena, new_max) + #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(&(ptr), size, ptr_max, pool, add_count) + #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(&(ptr), size, ptr_max, arena, add_count) + #define DN_PArrayCopyPtrPool(dest, src, count_, pool) DN_TArrayCopyPtrPool(&(dest), src, count_, pool) + #define DN_PArrayCopyPtrPoolAssert(dest, src, count_, pool) DN_TArrayCopyPtrPoolAssert(&(dest), src, count_, pool, DN_CallSiteNow) + #define DN_PArrayCopyPtrArena(dest, src, count_, arena) DN_TArrayCopyPtrArena(&(dest), src, count_, arena) + #define DN_PArrayCopyPtrArenaAssert(dest, src, count_, arena) DN_TArrayCopyPtrArenaAssert(&(dest), src, count_, arena, DN_CallSiteNow) #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, count, z_mem) #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_Yes) @@ -5460,10 +5708,14 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_ArrayFindMemEq(ptr, size, sizeof(*(ptr)), ptr_find) #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_ArrayResizeFromPool((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), pool, new_max) #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_ArrayResizeFromArena((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), arena, new_max) + #define DN_PArrayGrowFromPool(ptr, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), ptr_max, sizeof((ptr)[0]), pool, new_max) + #define DN_PArrayGrowFromArena(ptr, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), ptr_max, sizeof((ptr)[0]), arena, new_max) #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_ArrayGrowIfNeededFromPool((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), pool, add_count) #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_ArrayGrowIfNeededFromArena((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), arena, add_count) + #define DN_PArrayCopyPtrPool(ptr_array, src, count_, pool) DN_ArrayCopyPtrPool(ptr_array, src, count_, sizeof((*ptr)[0]), pool) + #define DN_PArrayCopyPtrPoolAssert(ptr_array, src, count_, pool) DN_ArrayCopyPtrPool(ptr_array, src, count_, sizeof((*ptr)[0]), pool, DN_CallSiteNow) + #define DN_PArrayCopyPtrArena(ptr_array, src, count_, arena) DN_ArrayCopyPtrArena(ptr_array, src, count_, sizeof((*ptr)[0]), arena) + #define DN_PArrayCopyPtrArenaAssert(ptr_array, src, count_, arena) DN_ArrayCopyPtrArena(ptr_array, src, count_, sizeof((*ptr)[0]), arena, DN_CallSiteNow) #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem) #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes) @@ -5497,12 +5749,6 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_LArrayFind(c_array, size, ptr_find, eq_func) DN_PArrayFind(c_array, size, ptr_find, eq_func) #define DN_LArrayFindMemEq(c_array, size, ptr_find) DN_PArrayFindMemEq(c_array, size, ptr_find) -#define DN_LArrayResizeFromPool(c_array, size, pool, new_max) DN_PArrayResizeFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayResizeFromArena(c_array, size, arena, new_max) DN_PArrayResizeFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowFromPool(c_array, size, pool, new_max) DN_PArrayGrowFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayGrowFromArena(c_array, size, arena, new_max) DN_PArrayGrowFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowIfNeededFromPool(c_array, size, pool, add_count) DN_PArrayGrowIfNeededFromPool(c_array, size, DN_ArrayCountU(c_array), pool, add_count) -#define DN_LArrayGrowIfNeededFromArena(c_array, size, arena, add_count) DN_PArrayGrowIfNeededFromArena(c_array, size, DN_ArrayCountU(c_array), arena, add_count) #define DN_LArrayMakeArray(c_array, ptr_size, count, z_mem) DN_PArrayMakeArray(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) #define DN_LArrayMakeArrayZ(c_array, ptr_size, count) DN_PArrayMakeArrayZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) @@ -5536,17 +5782,21 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_IArrayFindMemEq(ptr_array, ptr_find) DN_PArrayFindMemEq((ptr_array)->data, (ptr_array)->count, ptr_find) #define DN_IArrayResizeFromPool(ptr_array, pool, new_max) DN_PArrayResizeFromPool((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, pool, new_max) #define DN_IArrayResizeFromArena(ptr_array, arena, new_max) DN_PArrayResizeFromArena((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, arena, new_max) -#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, new_max) -#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, new_max) +#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, &(ptr_array)->max, pool, new_max) +#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, &(ptr_array)->max, arena, new_max) #define DN_IArrayGrowIfNeededFromPool(ptr_array, pool, add_count) DN_PArrayGrowIfNeededFromPool(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, add_count) #define DN_IArrayGrowIfNeededFromArena(ptr_array, arena, add_count) DN_PArrayGrowIfNeededFromArena(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, add_count) +#define DN_IArrayCopyPtrPool(ptr_array, src, count_, pool) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrPool((ptr_array), src, count_, pool) +#define DN_IArrayCopyPtrPoolAssert(ptr_array, src, count_, pool) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrPoolAssert((ptr_array), src, count_, pool) +#define DN_IArrayCopyPtrArena(ptr_array, src, count_, arena) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrArena((ptr_array), src, count_, arena) +#define DN_IArrayCopyPtrArenaAssert(ptr_array, src, count_, arena) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrArenaAssert((ptr_array), src, count_, arena) -#define DN_IArrayMakeArray(ptr_array, count, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayZ(ptr_array, count) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayNoZ(ptr_array, count) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssert(ptr_array, count, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayAssertZ(ptr_array, count) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayMakeArray(ptr_array, count_, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_, z_mem) +#define DN_IArrayMakeArrayZ(ptr_array, count_) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayNoZ(ptr_array, count_) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayAssert(ptr_array, count_, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_, z_mem) +#define DN_IArrayMakeArrayAssertZ(ptr_array, count_) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count_) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) #define DN_IArrayMake(ptr_array, z_mem) DN_PArrayMake((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) #define DN_IArrayMakeZ(ptr_array) DN_PArrayMakeZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) @@ -5555,19 +5805,19 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_IArrayMakeAssertZ(ptr_array) DN_PArrayMakeAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) #define DN_IArrayMakeAssertNoZ(ptr_array) DN_PArrayMakeAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) -#define DN_IArrayAddArray(ptr_array, items, count, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count, add) +#define DN_IArrayAddArray(ptr_array, items, count_, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_, add) #define DN_IArrayAdd(ptr_array, item, add) DN_PArrayAdd((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item, add) -#define DN_IArrayAppendArray(ptr_array, items, count) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayAppendArray(ptr_array, items, count_) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_) #define DN_IArrayAppend(ptr_array, item) DN_PArrayAppend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) #define DN_IArrayAppendAssert(ptr_array, item) DN_PArrayAppendAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayPrependArray(ptr_array, items, count) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayPrependArray(ptr_array, items, count_) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_) #define DN_IArrayPrepend(ptr_array, item) DN_PArrayPrepend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayEraseRange(ptr_array, begin_index, count, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count, erase) +#define DN_IArrayEraseRange(ptr_array, begin_index, count_, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count_, erase) #define DN_IArrayErase(ptr_array, index, erase) DN_PArrayErase((ptr_array)->data, &(ptr_array)->count, index, erase) -#define DN_IArrayInsertArray(ptr_array, index, items, count) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count) +#define DN_IArrayInsertArray(ptr_array, index, items, count_) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count_) #define DN_IArrayInsert(ptr_array, index, item) DN_PArrayInsert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, item) -#define DN_IArrayPopFront(ptr_array, count) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayPopBack(ptr_array, count) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayPopFront(ptr_array, count_) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayPopBack(ptr_array, count_) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) // NOTE: Slices // @@ -5593,10 +5843,14 @@ DN_API void* DN_ArrayAddArray (void * DN_API void* DN_ArrayAddArrayAssert (void *data, DN_USize *count, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); DN_API bool DN_ArrayResizeFromPool (void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); DN_API bool DN_ArrayResizeFromArena (void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); -DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); -DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); +DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); +DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); DN_API bool DN_ArrayGrowIfNeededFromPool (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count); DN_API bool DN_ArrayGrowIfNeededFromArena (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count); +DN_API DN_USize DN_ArrayCopyPtrArena (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena); +DN_API DN_USize DN_ArrayCopyPtrArenaAssert (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena, DN_CallSite call_site); +DN_API DN_USize DN_ArrayCopyPtrPool (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool); +DN_API DN_USize DN_ArrayCopyPtrPoolAssert (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool, DN_CallSite call_site); #if defined (__cplusplus) template DN_ArrayFindResult DN_TArrayFind (T *data, DN_USize count, void const *find, DN_ArrayFindEqFunc *eq_func); @@ -5613,141 +5867,276 @@ template T* DN_TArrayAddArray (T *dat template T* DN_TArrayAddArrayAssert (T *data, DN_USize *count, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); template bool DN_TArrayResizeFromPool (T **data, DN_USize *count, DN_USize *max, DN_Pool *pool, DN_USize new_max); template bool DN_TArrayResizeFromArena (T **data, DN_USize *count, DN_USize *max, DN_Arena *arena, DN_USize new_max); -template bool DN_TArrayGrowFromPool (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max); -template bool DN_TArrayGrowFromArena (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowFromPool (T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowFromArena (T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max); template bool DN_TArrayGrowIfNeededFromPool (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize add_count); template bool DN_TArrayGrowIfNeededFromArena (T **data, DN_USize count, DN_USize *max, DN_Arena *pool, DN_USize add_count); +template DN_USize DN_TArrayCopyPtrArena (T **data, T const *src, DN_USize count, DN_Arena *arena); +template DN_USize DN_TArrayCopyPtrArenaAssert (T **data, T const *src, DN_USize count, DN_Arena *arena, DN_CallSite call_site); +template DN_USize DN_TArrayCopyPtrPool (T **data, T const *src, DN_USize count, DN_Pool *pool); +template DN_USize DN_TArrayCopyPtrPoolAssert (T **data, T const *src, DN_USize count, DN_Pool *pool, DN_CallSite call_site); #endif -DN_API void* DN_SinglyLLDetach (void **link, void **next); -DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); -DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); -DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); -#define DN_RingWriteStruct(ring, item) DN_RingWrite((ring), (item), sizeof(*(item))) -DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); -#define DN_RingReadStruct(ring, dest) DN_RingRead((ring), (dest), sizeof(*(dest))) +DN_API void* DN_SinglyLLDetach (void **link, void **next); +DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); +DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); +DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); +#define DN_RingWriteStruct(ring_ptr, item_ptr) DN_RingWrite((ring_ptr), (item_ptr), sizeof(*(item_ptr))) +DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); +#define DN_RingReadStruct(ring_ptr, dest_ptr) DN_RingRead((ring_ptr), (dest_ptr), sizeof(*(dest_ptr))) -// TODO: Replace with a C-style hash table -#if defined(__cplusplus) -DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; -DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; -template DN_DSMap DN_DSMapInit (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); -template void DN_DSMapDeinit (DN_DSMap *map, DN_ZMem z_mem); -template bool DN_DSMapIsValid (DN_DSMap const *map); -template DN_U32 DN_DSMapHash (DN_DSMap const *map, DN_DSMapKey key); -template DN_U32 DN_DSMapHashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapFind (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapMake (DN_DSMap *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapSet (DN_DSMap *map, DN_DSMapKey key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyU64 (DN_DSMap const *map, DN_U64 key); -template DN_DSMapResult DN_DSMapMakeKeyU64 (DN_DSMap *map, DN_U64 key); -template DN_DSMapResult DN_DSMapSetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyStr8 (DN_DSMap const *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapMakeKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapSetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); -template bool DN_DSMapResize (DN_DSMap *map, DN_U32 size); -template bool DN_DSMapErase (DN_DSMap *map, DN_DSMapKey key); -template bool DN_DSMapEraseKeyU64 (DN_DSMap *map, DN_U64 key); -template bool DN_DSMapEraseKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapKey DN_DSMapKeyBuffer (DN_DSMap const *map, void const *data, DN_USize count); -template DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_USize count); -template DN_DSMapKey DN_DSMapKeyU64 (DN_DSMap const *map, DN_U64 u64); -template DN_DSMapKey DN_DSMapKeyStr8 (DN_DSMap const *map, DN_Str8 string); -#define DN_DSMapKeyCStr8(map, string) DN_DSMapKeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash (DN_U64 u64); -DN_API bool DN_DSMapKeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); -DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); -#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 bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); -DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); -DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); -DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); -DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); -DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); -DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); -DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); -DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); -DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); -DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); -DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); -DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); -DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); -DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); -DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); -DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); -DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); -DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); - -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; -}; - -// NOTE: Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. -// Using "" to escape quotes inside a quoted string). +// NOTE: Hash Table +// Overview +// A hash table implemented in C-style C++ that is based off and heavily derivative of Jonathan +// Blow's "simple hash table" implementation from JAI that he posted on Pastebin +// https://pastebin.com/raw/xMUQXshn The goal of this hash table is to provide a relatively +// generic table that covers the 99% of use cases. For the 1% you should design a custom table +// suited for that workload. // +// The key features of the simple hash-table, paraphrased +// +// Open-addressed table with each table slot consists of {hash, key, value} for +// cache-coherency. Default load factor of 0.7 and uses a 4 byte hash +// +// Tombstoning with quadratic probing using triangular numbers. Less cache friendly than linear +// probing but more cache friendly than double hashing. It mitigates clustering of collisions +// around the insertion point but early probes maintain cache locality to the initial collision. +// Quote from the reference implementation: +// +// We use the 'triangular numbers' version of quadratic probing. It yields more collisions per +// insertion than double hashing (double hashing gives us 0.74, triangular numbers give us +// 0.84) but the collisions are cheaper because the initial probes are cache-coherent, unlike +// with double hashing. At large table sizes this ends up mattering substantially. +// (examples/hash_table_test.jai, at the 128 million entry table size, takes 15.5 seconds with +// triangular numbers, 17.8 seconds with double hashing.) +// +// Triangular numbers are guaranteed to hit every entry in the table: +// https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ +// +// This hash table requires that the caller sets up their own struct with the key, value and hash +// located in the struct and pass the offsets to these fields into the table such that it can +// updated the values in a type-erased manner. By keeping all the table slot data into one struct +// ensures that the optimal path of looking up an item requires exactly one cache miss to fetch +// the necessary data. As long as the table maintains a 0.7 load factor, probabilistically all +// lookups are resolved in 1 shot. +// +// Other tables that opt for {key, key...} {value, value...} {hash, hash...} have benefits and +// make varying trade-offs. But assuming that the user is not memory bound and most lookups are +// resolved in 1 lookup this optimises for the common case where a simple hash table covers the +// 99% of use-cases. +// +// Note that this table requires that the user passes a double pointer to where store key-value +// structs that the table maintains on their behalf. This is an oddity necessitated because of the +// table being implemented using C constructs e.g.: +// +// MyKeyValue *my_kvs = NULL; +// DN_HTableInitArgsDefault(MyKeyValue, &my_kvs, ...) +// +// By forcing the user to store the pointer of `my_kvs` we allow you to have a type-safe view +// into the objects that the table is manipulating. This is a useful default mode of operation +// that makes it easier to debug having a type-safe pointer on-hand to inspect the contents of the +// table. This is rather than having the user need to cast `table.kvs` back to its concrete type +// every time you inspect the table. +// +// This would not be necessary if this was implemented in basically any other language that +// supports generics as a first class citizen. +// +// Example +/* + // NOTE: A hash table that maps (U64 -> bool) using the OS heap allocator + struct MyKeyValue + { + DN_HTableHashType hash; // Must always be `DN_HTableHashType` which is a 4 byte hash + DN_U64 key; // Any arbitrary key as long as it's in the same struct + bool value; // Any arbitrary value as long as it's in the same struct + }; + + MyKeyValue* table_kvs = {}; + DN_HTableInitArgs table_args = DN_HTableInitArgsDefault(MyKeyValue, table_kvs, hash, key, value); + DN_HTableInitResult table_init = DN_HTableInitHeap(table_args, DN_OS_HeapInitDefault()); + if (table_init.success) { + DN_HTable table = table_init.table; + P0I_U8x20 key = {}; + bool value = true; + DN_HTableAdd(&table, &key, &value); + } +*/ +DN_API DN_U32 DN_HTableHashFuncDefault (void const* key, DN_USize size); +DN_API bool DN_HTableKeyEqFuncMemcmp (void const* lhs, void const* rhs, DN_USize size); +DN_API bool DN_HTableKeyEqFuncStr8Eq (void const* lhs, void const* rhs, DN_USize size); + +#define DN_HTableInitArgsDefault(KeyValueT, kvs_ptr, HashField, KeyField, ValueField) DN_HTableInitArgsDefault_(DN_Cast(void **)&kvs_ptr, sizeof(*kvs_ptr), offsetof(KeyValueT, HashField), sizeof(kvs_ptr->KeyField), offsetof(KeyValueT, KeyField), sizeof(kvs_ptr->ValueField), offsetof(KeyValueT, ValueField)) +DN_API DN_HTableInitArgs DN_HTableInitArgsDefault_(void** kvs, DN_USize size_of_kvs, DN_USize offset_of_hash, DN_USize size_of_key, DN_USize offset_of_key, DN_USize size_of_value, DN_USize offset_of_value); +DN_API DN_HTableInitResult DN_HTableInitHeap (DN_HTableInitArgs args, DN_Heap heap); +DN_API DN_HTable DN_HTableInitHeapAssert (DN_HTableInitArgs args, DN_Heap heap); +DN_API DN_HTableInitResult DN_HTableInitPool (DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool); +DN_API DN_HTable DN_HTableInitPoolAssert (DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool); +DN_API void DN_HTableDeinit (DN_HTable* table); + +DN_API DN_HTableLookupResult DN_HTableLookup (DN_HTable const* table, void const* key, DN_HTableAllowTombstone allow_tombstone); +DN_API DN_HTableSlot DN_HTableFind (DN_HTable const* table, void const* key); +DN_API void* DN_HTableValueFromFind (DN_HTable const* table, void const* key); +DN_API DN_HTableSlot DN_HTableSlotFromIndex (DN_HTable const* table, DN_USize index); + +DN_API bool DN_HTableResize (DN_HTable* table, DN_USize new_max); +DN_API void DN_HTableClear (DN_HTable* table); +DN_API DN_HTableResizeResult DN_HTableGrowMaybe (DN_HTable* table, DN_USize add_count); +DN_API DN_HTableAddResult DN_HTableMake (DN_HTable* table, void* key); +DN_API DN_HTableAddResult DN_HTableAdd (DN_HTable* table, void* key, void* value); +DN_API bool DN_HTableDel (DN_HTable* table, void* key); + +// NOTE: Binary Packer +DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); +DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); +DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); +DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); +DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); +DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); +DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); +DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); +DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); +DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); +DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); +DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); +DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); +DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); +DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); +DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); +DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); +DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); +DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); + +// NOTE: CSV Tokeniser/Packer +// Overview +// Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. Using +// "" to escape quotes inside a quoted string). + // API -// DN_CSVTokeniserNextN: Reads the next N consecutive fields from the parser. If `column_iterator` -// is `false` then the read of the N consecutive fields does not proceed past the end of the -// current CSV row. If `true` then it reads the next N fields even if reading would progress onto -// the next row. -DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); -DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); -DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); -DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); -DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); -DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); -DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); -DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); -DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); -DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); -DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); -DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); -DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); -DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); -DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); -DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); -DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); -DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); +// DN_CSVTokeniserNextN +// Reads the next N consecutive fields from the parser. If `column_iterator` is `false` then the +// read of the N consecutive fields does not proceed past the end of the current CSV row. If `true` +// then it reads the next N fields even if reading would progress onto the next row. +DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); +DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); +DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); +DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); +DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); +DN_API int DN_CSVTokeniserNextColumnN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); +DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); +DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); +DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); +DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); +DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); +DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); +DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); +DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); +DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); +DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); +DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); +DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); + +// NOTE: Test Framework +// Overview +// A simple barebones testing framework that we use to unit test this library. This framework +// allows grouping of unit tests into 1 top-level group with an arbitrary number of tests inside +// the group. It uses a begin and end pattern to scope tests into groups. + +// Example +/* + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitDefault()); + DN_TestCore result = DN_TestInit(&arena); + for (DN_TestGroupScopeF(&result, "Base")) { + for (DN_TestScopeF(&result, "Check that foo != bar!")) + DN_TestVerifyStr8NotEq(&result, DN_Str8Lit("foo"), DN_Str8Lit("bar")); // Test passes + for (DN_TestScopeF(&result, "Does 1 equal 2?")) + DN_TestVerifyUSizeEq(&result, 1, 2); // This test will fail! + for (DN_TestScopeF(&result, "Float test, 1 is definitely less than 2")) + DN_TestVerifyF64LessThan(&result, 1.f, 2.f); // Floats work as well + } + DN_Str8 test_output = DN_Str8FromTestCore(&result, &arena, DN_Str8FromTestCoreFlags_Colour); + printf("%.*s", DN_Str8PrintFmt(test_output)); + DN_ArenaDeinit(&arena); + */ + +DN_API DN_TestCore DN_TestInit (DN_Arena *arena); +DN_API void DN_TestGroupBeginF (DN_TestCore *test, char const *fmt, ...); +DN_API void DN_TestGroupEnd (DN_TestCore *test); +#define DN_TestGroupScopeF(test_ptr, fmt, ...) bool group_once_ = (DN_TestGroupBeginF(test_ptr, fmt, ##__VA_ARGS__), false); !group_once_; DN_TestGroupEnd(test_ptr), group_once_ = true +DN_API void DN_TestBeginF (DN_TestCore *test, char const *fmt, ...); +DN_API void DN_TestEnd (DN_TestCore *test); +#define DN_TestScopeF(test_ptr, fmt, ...) bool test_once_ = (DN_TestBeginF(test_ptr, fmt, ##__VA_ARGS__), false); !test_once_; DN_TestEnd(test_ptr), test_once_ = true + +DN_API void DN_TestVerifyExprF_ (DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool expr_result, char const *fmt, ...); +#define DN_TestVerifyExprF(test_ptr, expr, fmt, ...) DN_TestVerifyExprF_(test_ptr, DN_CallSiteNow, DN_Str8Lit(#expr), (expr), fmt, ##__VA_ARGS__) +#define DN_TestVerifyExpr(test_ptr, expr) DN_TestVerifyExprF_(test_ptr, DN_CallSiteNow, DN_Str8Lit(#expr), (expr), "") + +DN_API void DN_TestVerifyF64Fmt (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_F64 val, DN_F64 expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyF64GreaterThanEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyF64GreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64GreaterThan(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyF64GreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64LessThan(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyF64LessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64LessThanEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyF64LessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64Eq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyF64EqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64NotEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyF64NotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyISizeF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_ISize val, DN_ISize expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyISizeGreaterThanEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyISizeGreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeGreaterThan(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyISizeGreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeLessThan(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyISizeLessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeLessThanEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyISizeLessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyISizeEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeNotEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyISizeNotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyUSizeF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_USize val, DN_USize expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyUSizeGreaterThanEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyUSizeGreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeGreaterThan(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyUSizeGreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeLessThan(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyUSizeLessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeLessThanEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyUSizeLessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyUSizeEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeNotEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyUSizeNotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyStr8F (DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 str8, DN_Str8 expect, bool expect_eq, char const *fmt, ...); +#define DN_TestVerifyStr8EqF(test_ptr, str8, expect, fmt, ...) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " == " #expect), (str8), (expect), /*expect_eq=*/ true, fmt, ##__VA_ARGS__) +#define DN_TestVerifyStr8Eq(test_ptr, str8, expect) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " == " #expect), (str8), (expect), /*expect_eq=*/ true, "") +#define DN_TestVerifyStr8NotEqF(test_ptr, str8, expect, fmt, ...) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " != " #expect), (str8), (expect), /*expect_eq=*/ false, fmt, ##__VA_ARGS__) +#define DN_TestVerifyStr8NotEq(test_ptr, str8, expect) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " != " #expect), (str8), (expect), /*expect_eq=*/ false, "") + +DN_API void DN_TestVerifyBytesF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 bytes, DN_Str8 expect, bool expect_eq, char const *fmt, ...); +#define DN_TestVerifyBytesEqF(test_ptr, bytes, expect, fmt, ...) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " == " #expect), (bytes), (expect), /*expect_eq=*/ true, fmt, ##__VA_ARGS__) +#define DN_TestVerifyBytesEq(test_ptr, bytes, expect) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " == " #expect), (bytes), (expect), /*expect_eq=*/ true, "") +#define DN_TestVerifyBytesNotEqF(test_ptr, bytes, expect, fmt, ...) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " != " #expect), (bytes), (expect), /*expect_eq=*/ false, fmt, ##__VA_ARGS__) +#define DN_TestVerifyBytesNotEq(test_ptr, bytes, expect) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " != " #expect), (bytes), (expect), /*expect_eq=*/ false, "") + +DN_API DN_Str8 DN_Str8FromTestCore (DN_TestCore const *test, DN_Arena *arena, DN_Str8FromTestCoreFlags flags); + +#if DN_WITH_TESTS +DN_API DN_TestCore DN_TestSuite (DN_Arena *arena_); +#endif // TODO: Replace with a C implementation template @@ -5798,16 +6187,16 @@ template DN_BinarySearchResult DN_BinarySearch (T co template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) { - bool result = lhs < rhs; - return result; + 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_USize array_size, + T const &find, + DN_BinarySearchType type, + DN_BinarySearchLessThanProc less_than) { DN_BinarySearchResult result = {}; if (!array || array_size <= 0 || !less_than) @@ -5847,35 +6236,55 @@ DN_BinarySearchResult DN_BinarySearch(T const *array, return result; } -DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); -DN_API void DN_LeakDump_ (DN_LeakTracker *leak); +DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); +DN_API void DN_LeakDump_ (DN_LeakTracker *leak); #if defined(DN_LEAK_TRACKING) -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) -#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) -#define DN_LeakDump(leak) DN_LeakDump_(leak) +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) +#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) +#define DN_LeakDump(leak) DN_LeakDump_(leak) #else -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) -#define DN_LeakDump(leak) do { } while (0) +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) +#define DN_LeakDump(leak) do { } while (0) #endif #if DN_WITH_OS -DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); -DN_API DN_MemFuncs DN_MemFuncsDefault (); -DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); - -DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromHeap (DN_USize count, DN_ZMem z_mem); -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); - +DN_API DN_Str8 DN_OS_Str8FromStr8BuilderHeap (DN_Str8Builder const *builder); DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); DN_API void DN_OS_SetLogPrintFuncToOS (); +// NOTE: Heap +// Overview +// Helper functions to create a heap allocator that utilises OS memory allocators. + +// API +// DN_OS_HeapBasicAlloc +// DN_OS_HeapBasicDealloc +// Helper functions that can be used with as the `basic_alloc` and `basic_dealloc` functions for +// `DN_Heap`. These functions essentially call directly into `DN_OS_MemAlloc` and +// `DN_OS_MemDealloc` + +// DN_OS_HeapInitVirtual +// Create a heap allocator using the OS's native virtual memory APIs (e.g.: reserve, commit, +// release) + +// DN_OS_HeapInitBasic +// Create a heap allocator using the OS's native malloc-like APIs (e.g.: HeapAlloc on Windows, +// malloc on linux) + +// DN_OS_HeapInitDefault +// Create a heap allocator using the OS's native memory APIs. Defaults to virtual memory +// always unless you are on Emscripten where it'll use basic allocator (virtual memory is not +// supported through Emscripten). + +DN_API void * DN_OS_HeapBasicAlloc (DN_USize size); +DN_API void DN_OS_HeapBasicDealloc (void *ptr); +DN_API DN_Heap DN_OS_HeapInitBasic (); +DN_API DN_Heap DN_OS_HeapInitVirtual (); +DN_API DN_Heap DN_OS_HeapInitDefault (); + DN_API void * DN_OS_MemReserve (DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); DN_API bool DN_OS_MemCommit (void *ptr, DN_USize count, DN_U32 page_flags); DN_API void DN_OS_MemDecommit (void *ptr, DN_USize count); @@ -5991,30 +6400,65 @@ DN_API bool DN_OS_ConditionVariableWaitUntil (D DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); -DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); +// NOTE: Thread +// Overview +// Threading primitives that uses CreateThread on Win32 and pthreads everywhere else. Threads +// are also the primitives to implement lanes, a multi-core by default paradigm. More +// information is detailed in the OS's Thread Lane APIs. +// API +// DN_OS_ThreadInit +// Create a thread that will immediately execute `func`. Pass in a zero initialised `thread` +// that will get its fields initialised with the thread information. +// +// `tc_init_args` customises how much memory is allocated for the thread's TLS context which +// contains allocators that are exclusively owned by the thread. You may use +// DN_TCInitArgsDefault() for sensible default values. +// +// Note not all fields in the thread will be initialised until the thread has itself run its +// initialisation code which is entirely dependent on the OS scheduler, scheduling the thread to +// execute. +// +// TODO: Allow the caller to wait on the thread initialisation's completion + +// DN_OS_ThreadInitLane +// The same as `DN_OS_ThreadInit` but allows setting the lane information. `DN_OS_ThreadInit` +// calls into this function with a `lane == NULL` value. +// +// If you are intended to execute code in a lane-esque manner (e.g. GPU style programming on the +// CPU, multiple threads, single/linear program execution) pass in `lane` with the fields +// populated (the thread's index, how many threads there are in the laneway and the barrier to +// share across lanes must be initialised to synchronise different threads). +// +// For the general use-case if you wish to use lanes then prefer the `DN_OS_ThreadLane` APIs +// below which create the threads with the lane information setup accordingly. +DN_API bool DN_OS_ThreadInitLane (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context); +DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadInitArgs init_args, void *user_context); DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); DN_API DN_U32 DN_OS_ThreadID (); +DN_API DN_OSThreadInitArgs DN_OS_ThreadInitArgsDefault (); DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); -// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a -// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan -// Fleury. SIMT is formally defined as +// NOTE: Thread Lanes +// Overview +// Thread lanes provide an abstraction to represent the concept of programming a CPU like a +// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan +// Fleury. SIMT is formally defined as // -// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same -// instruction in lockstep, but each thread operates on different data and maintains its own state +// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same +// instruction in lockstep, but each thread operates on different data and maintains its own state // -// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a -// thread lane here contains the necessary state to facilitate this such as the current index in the -// wavefront and synchronisation primitives to coordinate the different lanes together. +// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a +// thread lane here contains the necessary state to facilitate this such as the current index in the +// wavefront and synchronisation primitives to coordinate the different lanes together. // -// The idea is to write code in a single-threaded manner (linear execution) but across multiple -// threads so that the default is all execution paths are inherently multi-threaded by default. Opt -// out of parallelism instead of opt in. This optimises for the trend of core counts increasing -// whilst clock counts remain static. +// The idea is to write code in a single-threaded manner (linear execution) but across multiple +// threads so that the default is all execution paths are inherently multi-threaded by default. Opt +// out of parallelism instead of opt in. This optimises for the trend of core counts increasing +// whilst clock counts remain static. // -// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and -// setup the required synchronisation primitives. It can then be dispatched all the threads which -// start executing the `entry_point` in parallel. +// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and +// setup the required synchronisation primitives. It can then be dispatched all the threads which +// start executing the `entry_point` in parallel. // // API // DN_OS_ThreadLaneSync @@ -6062,19 +6506,18 @@ DN_API DN_V2USize DN_OS_ThreadLaneRange (D DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); -DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); +DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_OSThreadInitArgs init_args, void *user_context); DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); - -DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); -DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); -DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); +DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); +DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); +DN_API bool DN_OS_AsyncQueueWork (DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask (DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); // NOTE: DN_OSPrint enum DN_OSPrintDest @@ -6242,16 +6685,16 @@ bool DN_TArrayResizeFromArena(T **data, DN_USize *count, DN_USize *max, DN_Arena } template -bool DN_TArrayGrowFromPool(T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max) +bool DN_TArrayGrowFromPool(T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max) { - bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, count, max, sizeof(**data), pool, new_max); + bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, max, sizeof(**data), pool, new_max); return result; } template -bool DN_TArrayGrowFromArena(T **data, DN_USize count, DN_USize *max, DN_Arena *arena, DN_USize new_max) +bool DN_TArrayGrowFromArena(T **data, DN_USize *max, DN_Arena *arena, DN_USize new_max) { - bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, count, max, sizeof(**data), arena, new_max); + bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, max, sizeof(**data), arena, new_max); return result; } @@ -6268,6 +6711,34 @@ bool DN_TArrayGrowIfNeededFromArena(T **data, DN_USize count, DN_USize *max, DN_ bool result = DN_ArrayGrowIfNeededFromArena(DN_Cast(void **)data, count, max, sizeof(**data), arena, add_count); return result; } + +template +DN_USize DN_TArrayCopyPtrArena(T **data, T const *src, DN_USize count, DN_Arena *arena) +{ + DN_USize result = DN_ArrayCopyPtrArena(DN_Cast(void **)data, src, count, sizeof(T), arena); + return result; +} + +template +DN_USize DN_TArrayCopyPtrArenaAssert(T **data, T const *src, DN_USize count, DN_Arena *arena, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrArenaAssert(DN_Cast(void **)data, src, count, sizeof(T), arena, call_site); + return result; +} + +template +DN_USize DN_TArrayCopyPtrPool(T **data, T const *src, DN_USize count, DN_Pool *pool) +{ + DN_USize result = DN_ArrayCopyPtrPool(DN_Cast(void **)data, src, count, sizeof(T), pool); + return result; +} + +template +DN_USize DN_TArrayCopyPtrPoolAssert(T **data, T const *src, DN_USize count, DN_Pool *pool, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrPoolAssert(DN_Cast(void **)data, src, count, sizeof(T), pool, call_site); + return result; +} #endif // defined(__cplusplus) #if DN_WITH_NET @@ -6479,8 +6950,8 @@ DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *aren #endif // #if DN_WITH_NET_EMSCRIPTEN #if DN_WITH_OS -#if defined(DN_PLATFORM_WIN32) - // DN: Single header generator commented out => #include "OS/dn_os_windows.h" + #if defined(DN_PLATFORM_WIN32) + // DN: Single header generator commented out => #include "OS/dn_os_windows.h" #if !defined(DN_OS_WINDOWS_H) #define DN_OS_WINDOWS_H @@ -7843,7 +8314,7 @@ DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *aren DN_MSVC_WARNING_POP #endif // !defined(_INC_WINDOWS) #endif // !defined(DN_OS_WINDOWS_H) - // DN: Single header generator commented out => #include "OS/dn_os_w32.h" + // DN: Single header generator commented out => #include "OS/dn_os_w32.h" #if !defined(DN_OS_W32_H) #define DN_OS_W32_H @@ -7924,8 +8395,8 @@ DN_API DN_Str8 DN_OS_W32WorkingDir (DN_Arena *arena, DN_Str8 suf DN_API DN_Str16 DN_OS_W32WorkingDirW (DN_Arena *arena, DN_Str16 suffix); DN_API bool DN_OS_W32DirWIterate (DN_Str16 path, DN_OSW32FolderIteratorW *it); #endif // !defined(DN_OS_W32_H) -#elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - // DN: Single header generator commented out => #include "OS/dn_os_posix.h" + #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + // DN: Single header generator commented out => #include "OS/dn_os_posix.h" #if !defined(DN_OS_POSIX_H) #define DN_OS_POSIX_H @@ -7993,9 +8464,9 @@ DN_API void DN_OS_PosixInit (DN_OSPosixCore *posix) DN_API void DN_OS_PosixThreadSetName (DN_Str8 name); DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus(); #endif // !defined(DN_OS_POSIX_H) -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif + #else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs + #endif #endif // #if DN_WITH_OS #endif // #if !defined(DN_H) \ No newline at end of file diff --git a/Source/Extra/dn_tests.cpp b/Source/Extra/dn_tests.cpp deleted file mode 100644 index 32939ed..0000000 --- a/Source/Extra/dn_tests.cpp +++ /dev/null @@ -1,2646 +0,0 @@ -#if defined(_CLANGD) - #define DN_WITH_OS 1 - #include "dn.h" - #include "../Standalone/dn_utest.h" - - #define DN_UNIT_TESTS_WITH_KECCAK - #define DN_UNIT_TESTS_WITH_NET - #define DN_UNIT_TESTS_WITH_CURL - #define DN_SHA3_IMPLEMENTATION -#endif - -#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_TSTResult -{ - bool passed; - int total_tests; - int total_good_tests; -}; - -enum DN_TSTPrint -{ - DN_TSTPrint_No, - DN_TSTPrint_OnFailure, - DN_TSTPrint_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; - DN_U32 f_1_ECX_ = 0; - DN_U32 f_1_EDX_ = 0; - DN_U32 f_7_EBX_ = 0; - DN_U32 f_7_ECX_ = 0; - DN_U32 f_81_ECX_ = 0; - DN_U32 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_TST_Base() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Base\n"); - { - #if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) - DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); - for (DN_UT_Test(&result, "Query CPUID")) { - DN_CPUReport cpu_report = DN_CPUGetReport(); - - // NOTE: Sanity check our report against MSDN's example //////////////////////////////////////// - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); - - // NOTE: Feature flags we haven't bothered detecting yet but are in MSDN's example ///////////// - /* - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ADX) == DN_RefImplCPUReport::ADX()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI1) == DN_RefImplCPUReport::BMI1()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_BMI2) == DN_RefImplCPUReport::BMI2()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CLFSH) == DN_RefImplCPUReport::CLFSH()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CX8) == DN_RefImplCPUReport::CX8()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ERMS) == DN_RefImplCPUReport::ERMS()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FSGSBASE) == DN_RefImplCPUReport::FSGSBASE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FXSR) == DN_RefImplCPUReport::FXSR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_HLE) == DN_RefImplCPUReport::HLE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_INVPCID) == DN_RefImplCPUReport::INVPCID()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LAHF) == DN_RefImplCPUReport::LAHF()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_LZCNT) == DN_RefImplCPUReport::LZCNT()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MSR) == DN_RefImplCPUReport::MSR()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_OSXSAVE) == DN_RefImplCPUReport::OSXSAVE()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PREFETCHWT1) == DN_RefImplCPUReport::PREFETCHWT1()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RTM) == DN_RefImplCPUReport::RTM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SEP) == DN_RefImplCPUReport::SEP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SYSCALL) == DN_RefImplCPUReport::SYSCALL()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_TBM) == DN_RefImplCPUReport::TBM()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XOP) == DN_RefImplCPUReport::XOP()); - DN_UT_Assert(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_XSAVE) == DN_RefImplCPUReport::XSAVE()); - */ - } - #endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) - - for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1s 1ms (seconds and ms)")) { - DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms); - DN_Str8 expect = DN_Str8Lit("1s 1ms"); - DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.count, expect.data, expect.count), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Age from U64: 1001 converts to 1.001s (fractional)")) { - DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_FractionalSec); - DN_Str8 expect = DN_Str8Lit("1.001s"); - DN_UT_AssertF(&result, DN_MemEq(str8.data, str8.count, expect.data, expect.count), "str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "FmtAppendTruncate: String truncates with 3 dots")) { - char buf[8] = {}; - DN_USize buf_size = 0; - DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1)); - DN_Str8 expect = DN_Str8Lit("This..."); // 7 characters long, 1 byte reserved for null-terminator - DN_UT_Assert(&result, buf_str8.truncated); - DN_UT_AssertF(&result, DN_Str8Eq(buf_str8.str8, expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "TicketMutex: 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); - } - - for (DN_UT_Test(&result, "TicketMutex: 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); - } - - for (DN_UT_Test(&result, "QSort String (Natural)")) { - DN_Str8 list[] = { - DN_Str8Lit("item10"), - DN_Str8Lit("item2"), - DN_Str8Lit("item1"), - DN_Str8Lit("item20"), - DN_Str8Lit("item12"), - DN_Str8Lit("Afile"), - DN_Str8Lit("file2"), - DN_Str8Lit("file10"), - DN_Str8Lit("file1"), - DN_Str8Lit("z_last"), - DN_Str8Lit("m_middle"), - DN_Str8Lit("a_first"), - DN_Str8Lit("version-1.2.10"), - DN_Str8Lit("version-1.2.2"), - DN_Str8Lit("version-1.10.0"), - }; - - DN_QSortStr8NaturalAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Sensitive); - - DN_USize list_index = 0; - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("Afile")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("a_first")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file1")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("file10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item1")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item12")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("item20")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("m_middle")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.10.0")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("z_last")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - } - - for (DN_UT_Test(&result, "QSort String (Lexicographic)")) { - DN_Str8 list[] = { - DN_Str8Lit("z_last"), - DN_Str8Lit("m_middle"), - DN_Str8Lit("a_first"), - DN_Str8Lit("version-1.2.10"), - DN_Str8Lit("version-1.2.2"), - DN_Str8Lit("version-1.10.0"), - }; - - DN_QSortStr8LexicographicAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Insensitive); - - DN_USize list_index = 0; - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("a_first")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("m_middle")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.10.0")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.10")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("version-1.2.2")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - DN_AssertF(DN_Str8Eq(list[list_index++], DN_Str8Lit("z_last")), "%.*s", DN_Str8PrintFmt(list[list_index-1])); - } - - // 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_PUSH - 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. - { - // 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. - for (DN_UT_Test(&result, "AtomicAddU32")) { - DN_U32 val = 0; - DN_AtomicAddU32(&val, 1); - DN_UT_AssertF(&result, val == 1, "val: %u", val); - } - - for (DN_UT_Test(&result, "AtomicAddU64")) { - uint64_t val = 0; - DN_AtomicAddU64(&val, 1); - DN_UT_AssertF(&result, val == 1, "val: %" PRIu64, val); - } - - for (DN_UT_Test(&result, "AtomicSubU32")) { - DN_U32 val = 1; - DN_AtomicSubU32(&val, 1); - DN_UT_AssertF(&result, val == 0, "val: %u", val); - } - - for (DN_UT_Test(&result, "AtomicSubU64")) { - uint64_t val = 1; - DN_AtomicSubU64(&val, 1); - DN_UT_AssertF(&result, val == 0, "val: %" PRIu64, val); - } - - for (DN_UT_Test(&result, "AtomicSetValue32")) { - DN_U32 a = 0; - DN_U32 b = 111; - DN_AtomicSetValue32(&a, b); - DN_UT_AssertF(&result, a == b, "a: %ld, b: %ld", a, b); - } - - for (DN_UT_Test(&result, "AtomicSetValue64")) { - int64_t a = 0; - int64_t b = 111; - DN_AtomicSetValue64(DN_Cast(uint64_t *) & a, b); - DN_UT_AssertF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); - } - - DN_UT_BeginF(&result, "CPUGetTSC: Compile check"); - DN_CPUGetTSC(); - DN_UT_End(&result); - - DN_UT_BeginF(&result, "CompilerReadBarrierAndCPUReadFence: Compile check"); - DN_CompilerReadBarrierAndCPUReadFence; - DN_UT_End(&result); - - DN_UT_BeginF(&result, "CompilerWriteBarrierAndCPUWriteFence: Compile check"); - DN_CompilerWriteBarrierAndCPUWriteFence; - DN_UT_End(&result); - } - DN_MSVC_WARNING_POP - } - return result; -} - -static DN_UTCore DN_TST_BaseArena() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Arena\n"); - { - for (DN_UT_Test(&result, "Reused memory is zeroed out")) { - uint8_t alignment = 1; - DN_USize alloc_size = DN_Kilobytes(128); - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena - uintptr_t first_ptr_address = 0; - { - DN_U64 mem_p = DN_MemListPos(arena.mem); - void *ptr = DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); - first_ptr_address = DN_Cast(uintptr_t) ptr; - DN_Memset(ptr, 'z', alloc_size); - DN_MemListPopTo(arena.mem, mem_p); - } - - // NOTE: Reallocate 128 kilobytes - char *ptr = DN_Cast(char *) DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_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); - } - - for (DN_UT_Test(&result, "Test arena grows naturally, 1mb + 4mb")) { - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - char *ptr_1mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(1), DN_ZMem_Yes); - char *ptr_4mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(4), DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_1mb); - DN_UT_Assert(&result, ptr_4mb); - - DN_MemBlock const *block_4mb_begin = arena.mem->curr; - char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; - - DN_MemBlock 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"); - } - - for (DN_UT_Test(&result, "Test arena grows naturally, 1mb, temp memory 4mb")) { - DN_MemList mem = DN_MemListFromVMem(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_DEFER { DN_MemListDeinit(&mem); }; - - // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow - char *ptr_1mb = DN_Cast(char *) DN_ArenaAlloc(&arena, DN_Megabytes(1), 1 /*align*/, DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_1mb); - - DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); - { - char *ptr_4mb = DN_ArenaNewArray(&temp, char, DN_Megabytes(4), DN_ZMem_Yes); - DN_UT_Assert(&result, ptr_4mb); - - DN_MemBlock const *block_4mb_begin = arena.mem->curr; - char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; - - DN_MemBlock 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_ArenaTempEnd(&temp, DN_ArenaReset_Yes); - DN_UT_Assert(&result, arena.mem->curr->prev == nullptr); - DN_UT_AssertF(&result, - arena.mem->curr->reserve >= DN_Megabytes(1), - "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", - (arena.mem->curr->reserve / 1024 / 1024), - arena.mem->curr->reserve, - DN_Megabytes(1)); - } - } - return result; -} - -static DN_UTCore DN_TST_BaseBytesHex() -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_UTCore test = DN_UT_Init(); - DN_UT_LogF(&test, "Bytes <-> Hex\n"); - { - for (DN_UT_Test(&test, "Convert 0x123")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x123")); - DN_UT_AssertF(&test, result == 0x123, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0xFFFF")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xFFFF")); - DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert FFFF")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("FFFF")); - DN_UT_AssertF(&test, result == 0xFFFF, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert abCD")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("abCD")); - DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0xabCD")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xabCD")); - DN_UT_AssertF(&test, result == 0xabCD, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0x")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x")); - DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 0X")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0X")); - DN_UT_AssertF(&test, result == 0x0, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert 3")) { - uint64_t result = DN_U64FromHexStr8Unsafe(DN_Str8Lit("3")); - DN_UT_AssertF(&test, result == 3, "result: %" PRIu64, result); - } - - for (DN_UT_Test(&test, "Convert f")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("f")); - DN_UT_Assert(&test, result.success); - DN_UT_Assert(&test, result.value == 0xf); - } - - for (DN_UT_Test(&test, "Convert g")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("g")); - DN_UT_Assert(&test, !result.success); - } - - for (DN_UT_Test(&test, "Convert -0x3")) { - DN_U64FromResult result = DN_U64FromHexStr8(DN_Str8Lit("-0x3")); - DN_UT_Assert(&test, !result.success); - } - - DN_U32 number = 0xd095f6; - for (DN_UT_Test(&test, "Convert %x to string", number)) { - DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("f695d000")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); - } - - number = 0xf6ed00; - for (DN_UT_Test(&test, "Convert %x to string", number)) { - DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(number_hex, DN_Str8Lit("00edf600")), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); - } - - DN_Str8 hex = DN_Str8Lit("0xf6ed00"); - for (DN_UT_Test(&test, "Convert %.*s to bytes", DN_Str8PrintFmt(hex))) { - DN_Str8 bytes = DN_BytesFromHexArena(hex, &scratch.arena); - DN_UT_AssertF(&test, - DN_Str8Eq(bytes, DN_Str8Lit("\xf6\xed\x00")), - "number_hex=%.*s", - DN_Str8PrintFmt(DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No))); - } - - for (DN_UT_Test(&test, "Convert empty bytes to string", number)) { - DN_Str8 bytes = DN_Str8Lit(""); - DN_Str8 as_hex = DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(&test, DN_Str8Eq(as_hex, DN_Str8Lit("")), "as_hex=%.*s", DN_Str8PrintFmt(as_hex)); - } - } - DN_TCScratchEnd(&scratch); - return test; -} - -#if DN_H_WITH_HELPERS -static DN_UTCore DN_TST_BinarySearch() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_BinarySearch\n"); - { - for (DN_UT_Test(&result, "Search array of 1 item")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 2 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 3 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array of 4 items")) { - DN_U32 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); - } - - for (DN_UT_Test(&result, "Search array with duplicate items")) { - DN_U32 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; -} -#endif // DN_H_WITH_HELPERS - -static DN_UTCore DN_TST_BaseDSMap() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_DSMap\n"); - { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - { - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_U32 const MAP_SIZE = 64; - DN_DSMap map = DN_DSMapInit(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER - { - DN_DSMapDeinit(&map, DN_ZMem_Yes); - }; - - for (DN_UT_Test(&result, "Find non-existent value")) { - DN_DSMapResult find = DN_DSMapFindKeyStr8(&map, DN_Str8Lit("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_DSMapKeyCStr8(&map, "Bar"); - for (DN_UT_Test(&result, "Insert value and lookup")) { - uint64_t desired_value = 0xF00BAA; - uint64_t *slot_value = DN_DSMapSet(&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_DSMapFind(&map, key).value; - DN_UT_Assert(&result, value); - DN_UT_Assert(&result, *value == desired_value); - } - - for (DN_UT_Test(&result, "Remove key")) { - DN_DSMapErase(&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_Str8Lit("Set"); break; - case DSMapTestType_MakeSlot: prefix = DN_Str8Lit("Make slot"); break; - } - - DN_MemList mem = DN_MemListFromVMem(0, 0, DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_U32 const MAP_SIZE = 64; - DN_DSMap map = DN_DSMapInit(&arena, MAP_SIZE, DN_DSMapFlags_Nil); - DN_DEFER - { - DN_DSMapDeinit(&map, DN_ZMem_Yes); - }; - - for (DN_UT_Test(&result, "%.*s: Test growing", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, !DN_DSMapFind(&map, key).found); - DN_DSMapResult make_result = {}; - if (result_type == DSMapTestType_Set) - make_result = DN_DSMapSet(&map, key, value); - else - make_result = DN_DSMapMake(&map, key); - DN_UT_Assert(&result, !make_result.found); - DN_UT_Assert(&result, DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_DSMapResult make_result = {}; - if (result_type == DSMapTestType_Set) - make_result = DN_DSMapSet(&map, key, value); - else - make_result = DN_DSMapMake(&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); - } - } - - for (DN_UT_Test(&result, "%.*s: Check the sentinel is present", DN_Str8PrintFmt(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); - } - - for (DN_UT_Test(&result, "%.*s: Recheck all the hash tables values after growing", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value_result); - DN_UT_Assert(&result, DN_DSMapKeyEquals(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_DSMapHash(&map, slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMapFind(&map, slot->key); - DN_UT_Assert(&result, slot->value == *check.value); - } - } - - for (DN_UT_Test(&result, "%.*s: Test shrinking", DN_Str8PrintFmt(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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, DN_DSMapFind(&map, key).found); - DN_DSMapErase(&map, key); - DN_UT_Assert(&result, !DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_DSMapErase(&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_DSMapKeyU64(&map, value_result); - - // NOTE: Validate each slot value - DN_DSMapResult find_result = DN_DSMapFind(&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_DSMapHash(&map, find_result.slot->key)); - - // NOTE: Check the reverse lookup is correct - DN_DSMapResult check = DN_DSMapFind(&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_DSMapKeyU64(&map, value); - DN_UT_Assert(&result, DN_DSMapFind(&map, key).found); - DN_DSMapErase(&map, key); - DN_UT_Assert(&result, !DN_DSMapFind(&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*/); - } - } - DN_TCScratchEnd(&scratch); - } - return result; -} - -static DN_UTCore DN_TST_BaseIArray() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_IArray\n"); - { - struct CustomArray - { - int *data; - DN_USize count; - DN_USize max; - }; - - int array_buffer[16]; - CustomArray array = {}; - array.data = array_buffer; - array.max = DN_ArrayCountU(array_buffer); - - for (DN_UT_Test(&result, "Make item")) { - int *item = DN_IArrayMake(&array, DN_ZMem_Yes); - DN_UT_Assert(&result, item && array.count == 1); - } - } - return result; -} - -static DN_UTCore DN_TST_BaseArray() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_Array\n"); - { - for (DN_UT_Test(&result, "Positive count, middle of array, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 2); - DN_UT_Assert(&result, erase.it_index == 2); - DN_UT_Assert(&result, size == 8); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Negative count, middle of array, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Stable); - int expected[] = {0, 1, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 3); - DN_UT_Assert(&result, erase.it_index == 1); - DN_UT_Assert(&result, size == 7); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "count = -1, stable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -1, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 1); - DN_UT_AssertF(&result, erase.it_index == 3, "lhs=%zu", erase.it_index); - DN_UT_Assert(&result, size == 9); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Positive count, unstable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Unstable); - int expected[] = {0, 1, 2, 8, 9, 5, 6, 7}; - DN_UT_Assert(&result, erase.items_erased == 2); - DN_UT_Assert(&result, erase.it_index == 2); - DN_UT_Assert(&result, size == 8); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Negative count, unstable erase")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Unstable); - int expected[] = {0, 1, 7, 8, 9, 5, 6}; - DN_UT_Assert(&result, erase.items_erased == 3); - DN_UT_Assert(&result, erase.it_index == 1); - DN_UT_Assert(&result, size == 7); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Edge case - begin_index at start, negative count")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 0, -2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 0); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Edge case - begin_index at end, positive count")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 9, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; - DN_UT_Assert(&result, erase.items_erased == 1); - DN_UT_Assert(&result, erase.it_index == 9); - DN_UT_Assert(&result, size == 9); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Invalid input - count = 0")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 0, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - - for (DN_UT_Test(&result, "Invalid input - null data")) { - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(nullptr, &size, sizeof(int), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 10); - } - - for (DN_UT_Test(&result, "Invalid input - null size")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, NULL, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - } - - for (DN_UT_Test(&result, "Invalid input - empty array")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 0; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 5); - DN_UT_Assert(&result, size == 0); - } - - for (DN_UT_Test(&result, "Out-of-bounds begin_index")) { - int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_USize size = 10; - DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 15, 2, DN_ArrayErase_Stable); - int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - DN_UT_Assert(&result, erase.items_erased == 0); - DN_UT_Assert(&result, erase.it_index == 9); - DN_UT_Assert(&result, size == 10); - DN_UT_Assert(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); - } - } - 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_SHA3_IMPLEMENTATION - #include "../Standalone/dn_sha3.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_TST__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_Str8Lit(string), - DN_UT_HASH_X_MACRO - #undef DN_UT_HASH_X_ENTRY -}; - -void DN_TST_KeccakDispatch_(DN_UTCore *test, int hash_type, DN_Str8 input) -{ - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 input_hex = DN_HexFromPtrBytesArena(input.data, input.count, &scratch.arena, DN_TrimLeadingZero_No); - - switch (hash_type) { - case Hash_SHA3_224: { - DN_SHA3U8x28 hash = DN_SHA3Hash224b(input.data, input.count); - DN_SHA3U8x28 expect; - DN_RefImpl_FIPS202_SHA3_224_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_256: { - DN_SHA3U8x32 hash = DN_SHA3Hash256b(input.data, input.count); - DN_SHA3U8x32 expect; - DN_RefImpl_FIPS202_SHA3_256_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_384: { - DN_SHA3U8x48 hash = DN_SHA3Hash384b(input.data, input.count); - DN_SHA3U8x48 expect; - DN_RefImpl_FIPS202_SHA3_384_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_SHA3_512: { - DN_SHA3U8x64 hash = DN_SHA3Hash512b(input.data, input.count); - DN_SHA3U8x64 expect; - DN_RefImpl_FIPS202_SHA3_512_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_224: { - DN_SHA3U8x28 hash = DN_KeccakHash224b(input.data, input.count); - DN_SHA3U8x28 expect; - DN_RefImpl_Keccak_(1152, 448, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_256: { - DN_SHA3U8x32 hash = DN_KeccakHash256b(input.data, input.count); - DN_SHA3U8x32 expect; - DN_RefImpl_Keccak_(1088, 512, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_384: { - DN_SHA3U8x48 hash = DN_KeccakHash384b(input.data, input.count); - DN_SHA3U8x48 expect; - DN_RefImpl_Keccak_(832, 768, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - - case Hash_Keccak_512: { - DN_SHA3U8x64 hash = DN_KeccakHash512b(input.data, input.count); - DN_SHA3U8x64 expect; - DN_RefImpl_Keccak_(576, 1024, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); - - DN_Str8 hash_hex = DN_HexFromPtrBytesArena(hash.data, DN_ArrayCountU(hash.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_Str8 expect_hex = DN_HexFromPtrBytesArena(expect.data, DN_ArrayCountU(expect.data), &scratch.arena, DN_TrimLeadingZero_No); - DN_UT_AssertF(test, - DN_MemEq(hash.data, sizeof(hash.data), expect.data, sizeof(expect.data)), - "\ninput: %.*s" - "\nhash: %.*s" - "\nexpect: %.*s", - DN_Str8PrintFmt(input_hex), - DN_Str8PrintFmt(hash_hex), - DN_Str8PrintFmt(expect_hex)); - } break; - } - DN_TCScratchEnd(&scratch); -} -#endif // defined(DN_UNIT_TESTS_WITH_KECCAK) - -DN_UTCore DN_TST_Keccak() -{ - DN_UTCore result = DN_UT_Init(); - #if defined(DN_UNIT_TESTS_WITH_KECCAK) - DN_Str8 const INPUTS[] = { - DN_Str8Lit("abc"), - DN_Str8Lit(""), - DN_Str8Lit("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), - DN_Str8Lit("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno" - "pqrstnopqrstu"), - }; - - DN_UT_LogF(&result, "DN_KC\n"); - { - for (int hash_type = 0; hash_type < Hash_Count; hash_type++) { - DN_PCG32 rng = DN_PCG32Init(0xd48e'be21'2af8'733d); - for (DN_Str8 input : INPUTS) { - DN_UT_BeginF(&result, "%.*s - Input: %.*s", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type]), DN_Cast(int) DN_Min(input.count, 54), input.data); - DN_TST_KeccakDispatch_(&result, hash_type, input); - DN_UT_End(&result); - } - - DN_UT_BeginF(&result, "%.*s - Deterministic random inputs", DN_Str8PrintFmt(DN_UT_HASH_STRING_[hash_type])); - for (DN_USize index = 0; index < 128; index++) { - char src[4096] = {}; - DN_U32 src_size = DN_PCG32Range(&rng, 0, sizeof(src)); - - for (DN_USize src_index = 0; src_index < src_size; src_index++) - src[src_index] = DN_Cast(char) DN_PCG32Range(&rng, 0, 255); - - DN_Str8 input = DN_Str8FromPtr(src, src_size); - DN_TST_KeccakDispatch_(&result, hash_type, input); - } - DN_UT_End(&result); - } - } - #endif - return result; -} - -static DN_UTCore DN_TST_M4() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_M4\n"); - { - for (DN_UT_Test(&result, "Simple translate and scale matrix")) { - DN_M4 translate = DN_M4TranslateF(1, 2, 3); - DN_M4 scale = DN_M4ScaleF(2, 2, 2); - DN_M4 mul_result = DN_M4Mul(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_M4ColumnMajorString(mul_result).data, - DN_M4ColumnMajorString(EXPECT).data); - } - } - return result; -} - -static DN_UTCore DN_TST_OS() -{ - DN_UTCore result = DN_UT_Init(); - -#if defined(DN_OS_INC_CPP) || 1 - DN_UT_LogF(&result, "DN_OS\n"); - { - for (DN_UT_Test(&result, "Generate secure RNG 32 bytes")) { - char const ZERO[32] = {}; - char buf[32] = {}; - DN_OS_GenBytesSecure(buf, DN_ArrayCountU(buf)); - DN_UT_Assert(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0); - } - - for (DN_UT_Test(&result, "Query executable directory")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 os_result = DN_OS_EXEDir(&scratch.arena); - DN_UT_Assert(&result, os_result.count); - DN_UT_AssertF(&result, DN_OS_PathIsDir(os_result), "result(%zu): %.*s", os_result.count, DN_Str8PrintFmt(os_result)); - DN_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "DN_OS_PerfCounterNow")) { - uint64_t os_result = DN_OS_PerfCounterNow(); - DN_UT_Assert(&result, os_result != 0); - } - - for (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); - } - - for (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"); - { - for (DN_UT_Test(&result, "Make directory recursive \"abcd/efgh\"")) { - DN_UT_AssertF(&result, DN_OS_PathMakeDir(DN_Str8Lit("abcd/efgh")), "Failed to make directory"); - DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd")), "Directory was not made"); - DN_UT_AssertF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd/efgh")), "Subdirectory was not made"); - DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd")) == false, "This function should only return true for files"); - DN_UT_AssertF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd/efgh")) == false, "This function should only return true for files"); - DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd/efgh")), "Failed to delete directory"); - DN_UT_AssertF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd")), "Failed to cleanup directory"); - } - - for (DN_UT_Test(&result, "File write, read, copy, move and delete")) { - // NOTE: Write step - DN_Str8 const SRC_FILE = DN_Str8Lit("dn_result_file"); - DN_B32 write_result = DN_OS_FileWriteAll(SRC_FILE, DN_Str8Lit("1234"), nullptr); - DN_UT_Assert(&result, write_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(SRC_FILE)); - - // NOTE: Read step - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 read_file = DN_OS_FileReadAllArena(&scratch.arena, SRC_FILE, nullptr); - DN_UT_AssertF(&result, read_file.count, "Failed to load file"); - DN_UT_AssertF(&result, read_file.count == 4, "File read wrong amount of bytes (%zu)", read_file.count); - DN_UT_AssertF(&result, DN_Str8Eq(read_file, DN_Str8Lit("1234")), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.count, DN_Str8PrintFmt(read_file)); - - // NOTE: Copy step - DN_Str8 const COPY_FILE = DN_Str8Lit("dn_result_file_copy"); - DN_B32 copy_result = DN_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr); - DN_UT_Assert(&result, copy_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(COPY_FILE)); - - // NOTE: Move step - DN_Str8 const MOVE_FILE = DN_Str8Lit("dn_result_file_move"); - DN_B32 move_result = DN_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); - DN_UT_Assert(&result, move_result); - DN_UT_Assert(&result, DN_OS_PathIsFile(MOVE_FILE)); - DN_UT_AssertF(&result, DN_OS_PathIsFile(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); - DN_TCScratchEnd(&scratch); - } - } - - DN_UT_LogF(&result, "\nSemaphore\n"); - { - DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); - - for (DN_UT_Test(&result, "Wait timeout")) { - DN_U64 begin = DN_OS_PerfCounterNow(); - DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 100 /*timeout_ms*/); - DN_U64 end = DN_OS_PerfCounterNow(); - DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Timeout, "Received wait result %zu", wait_result); - DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); - DN_UT_AssertF(&result, elapsed_ms >= 80 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); - } - - for (DN_UT_Test(&result, "Wait success")) { - DN_OS_SemaphoreIncrement(&sem, 1); - DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 0 /*timeout_ms*/); - DN_UT_AssertF(&result, wait_result == DN_OSSemaphoreWaitResult_Success, "Received wait result %zu", wait_result); - } - - DN_OS_SemaphoreDeinit(&sem); - } - - DN_UT_LogF(&result, "\nMutex\n"); - { - DN_OSMutex mutex = DN_OS_MutexInit(); - for (DN_UT_Test(&result, "Lock")) { - DN_OS_MutexLock(&mutex); - DN_OS_MutexUnlock(&mutex); - } - DN_OS_MutexDeinit(&mutex); - } - - DN_UT_LogF(&result, "\nCondition Variable\n"); - { - DN_OSMutex mutex = DN_OS_MutexInit(); - DN_OSConditionVariable cv = DN_OS_ConditionVariableInit(); - for (DN_UT_Test(&result, "Lock and timeout")) { - DN_U64 begin = DN_OS_PerfCounterNow(); - DN_OS_ConditionVariableWait(&cv, &mutex, 100 /*sleep_ms*/); - DN_U64 end = DN_OS_PerfCounterNow(); - DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); - DN_UT_AssertF(&result, elapsed_ms >= 99 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); - } - DN_OS_MutexDeinit(&mutex); - DN_OS_ConditionVariableDeinit(&cv); - } -#endif - - return result; -} - -static DN_UTCore DN_TST_Rect() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "DN_Rect\n"); - { - for (DN_UT_Test(&result, "No intersection")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From1N(0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(200, 0), DN_V2F32From2N(200, 200)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A's min intersects B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B's min intersects A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A's max intersects B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B's max intersects A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "B contains A")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A contains B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); - DN_Rect ab = DN_RectIntersection(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); - } - - for (DN_UT_Test(&result, "A equals B")) { - DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); - DN_Rect b = a; - DN_Rect ab = DN_RectIntersection(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_TST_BaseStrings() -{ - DN_UTCore result = DN_UT_Init(); - DN_UT_LogF(&result, "Strings\n"); - { - for (DN_UT_Test(&result, "Str8 literal")) { - DN_Str8 string = DN_Str8Lit("AB"); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - 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]); - } - - for (DN_UT_Test(&result, "C-string length")) { - DN_USize size = DN_CStr8Count("hello"); - DN_UT_AssertF(&result, size == 5, "size=%zu", size); - } - - char arena_base[512]; - for (DN_UT_Test(&result, "Str8 format from arena")) { - DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5); - DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); - DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Str8 format from pool")) { - DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Pool pool = DN_PoolFromArena(&arena, 0); - DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5); - DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); - DN_UT_AssertF(&result, DN_Str8Eq(str8, expect), "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Str8x32 from U64")) { - DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' '); - DN_Str8 expect = DN_Str8Lit("123 456"); - DN_UT_AssertF(&result, DN_Str8Eq(DN_Str8FromStruct(&str8), expect), "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); - } - - for (DN_UT_Test(&result, "Initialise with format string")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8FromFmtArena(&scratch.arena, "%s", "AB"); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - 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_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "Copy string")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8Lit("AB"); - DN_Str8 copy = DN_Str8FromStr8Arena(string, &scratch.arena); - DN_UT_AssertF(&result, copy.count == 2, "size: %zu", copy.count); - 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_TCScratchEnd(&scratch); - } - - for (DN_UT_Test(&result, "Trim whitespace around string")) { - DN_Str8 string = DN_Str8TrimWhitespaceAround(DN_Str8Lit(" AB ")); - DN_UT_AssertF(&result, DN_Str8Eq(string, DN_Str8Lit("AB")), "[string=%.*s]", DN_Str8PrintFmt(string)); - } - - for (DN_UT_Test(&result, "Allocate string from arena")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 string = DN_Str8AllocArena(2, DN_ZMem_No, &scratch.arena); - DN_UT_AssertF(&result, string.count == 2, "size: %zu", string.count); - DN_TCScratchEnd(&scratch); - } - - // NOTE: TrimPrefix/Suffix ///////////////////////////////////////////////////////////////////// - for (DN_UT_Test(&result, "Trim prefix with matching prefix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit("nft/")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("abc")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim prefix with non matching prefix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimPrefix(input, DN_Str8Lit(" ft/")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim suffix with matching suffix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("abc")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("nft/")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "Trim suffix with non matching suffix")) { - DN_Str8 input = DN_Str8Lit("nft/abc"); - DN_Str8 str_result = DN_Str8TrimSuffix(input, DN_Str8Lit("ab")); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, input), "%.*s", DN_Str8PrintFmt(str_result)); - } - - // NOTE: DN_Str8IsAllDigits ////////////////////////////////////////////////////////////// - for (DN_UT_Test(&result, "Is all digits fails on non-digit string")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("@123string"), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, str_result == false); - } - - for (DN_UT_Test(&result, "Is all digits fails on nullptr")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(nullptr, 0), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, str_result == false); - } - - for (DN_UT_Test(&result, "Is all digits fails on string w/ 0 size")) { - char const buf[] = "@123string"; - DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(buf, 0), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, !str_result); - } - - for (DN_UT_Test(&result, "Is all digits success")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23"), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, DN_Cast(bool) str_result == true); - } - - for (DN_UT_Test(&result, "Is all digits fails on whitespace")) { - DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23 "), DN_Str8IsAllType_Digits); - DN_UT_Assert(&result, DN_Cast(bool) str_result == false); - } - - // NOTE: DN_Str8BSplit /////////////////////////////////////////////////////////////////// - { - { - char const *TEST_FMT = "Binary split \"%.*s\" with \"%.*s\""; - DN_Str8 delimiter = DN_Str8Lit("/"); - DN_Str8 input = DN_Str8Lit("abcdef"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abcdef")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - - input = DN_Str8Lit("abc/def"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("abc")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("def")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - - input = DN_Str8Lit("/abcdef"); - for (DN_UT_Test(&result, TEST_FMT, DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("abcdef")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - } - - { - DN_Str8 delimiter = DN_Str8Lit("-=-"); - DN_Str8 input = DN_Str8Lit("123-=-456"); - for (DN_UT_Test(&result, "Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { - DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); - DN_UT_AssertF(&result, DN_Str8Eq(split.lhs, DN_Str8Lit("123")), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); - DN_UT_AssertF(&result, DN_Str8Eq(split.rhs, DN_Str8Lit("456")), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); - } - } - } - - // NOTE: DN_I64FromStr8 - for (DN_UT_Test(&result, "To I64: Convert empty string")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit(""), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 0); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"-0\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-0"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 0); - } - - for (DN_UT_Test(&result, "To I64: Convert \"-1\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == -1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1.2\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1.2"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_Assert(&result, str_result.value == 1); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1,234\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,234"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 1234); - } - - for (DN_UT_Test(&result, "To I64: Convert \"1,2\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,2"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_Assert(&result, str_result.value == 12); - } - - for (DN_UT_Test(&result, "To I64: Convert \"12a3\"")) { - DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("12a3"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_Assert(&result, str_result.value == 12); - } - - // NOTE: DN_U64FromStr8 - for (DN_UT_Test(&result, "To U64: Convert empty string")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit(""), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1"), 0); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"-0\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-0"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"-1\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-1"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1.2\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1.2"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1,234\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,234"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"1,2\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1,2"), ','); - DN_UT_Assert(&result, str_result.success); - DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); - } - - for (DN_UT_Test(&result, "To U64: Convert \"12a3\"")) { - DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("12a3"), 0); - DN_UT_Assert(&result, !str_result.success); - DN_UT_AssertF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); - } - - // NOTE: DN_Str8Find - for (DN_UT_Test(&result, "Find: String (char) is not in buffer")) { - DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_Str8Lit("2"); - DN_Str8FindResult str_result = DN_Str8FindStr8(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.count == 0); - } - - for (DN_UT_Test(&result, "Find: String (char) is in buffer")) { - DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); - DN_Str8 find = DN_Str8Lit("6"); - DN_Str8FindResult str_result = DN_Str8FindStr8(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_Str8FileNameFromPath - for (DN_UT_Test(&result, "File name from Windows path")) { - DN_Str8 buf = DN_Str8Lit("C:\\ABC\\str_result.exe"); - DN_Str8 str_result = DN_Str8FileNameFromPath(buf); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - for (DN_UT_Test(&result, "File name from Linux path")) { - DN_Str8 buf = DN_Str8Lit("/ABC/str_result.exe"); - DN_Str8 str_result = DN_Str8FileNameFromPath(buf); - DN_UT_AssertF(&result, DN_Str8Eq(str_result, DN_Str8Lit("str_result.exe")), "%.*s", DN_Str8PrintFmt(str_result)); - } - - // NOTE: DN_Str8TrimPrefix - for (DN_UT_Test(&result, "Trim prefix")) { - DN_Str8 prefix = DN_Str8Lit("@123"); - DN_Str8 buf = DN_Str8Lit("@123string"); - DN_Str8 str_result = DN_Str8TrimPrefix(buf, prefix, DN_Str8EqCase_Sensitive); - DN_UT_Assert(&result, DN_Str8Eq(str_result, DN_Str8Lit("string"))); - } - - // NOTE: DN_Str8TruncMiddle - { - for (DN_UT_Test(&result, "TruncMiddlePtr: Short string is not truncated")) { - DN_Str8 str = DN_Str8Lit("Hello"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 5); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Exact boundary (2*side_size) is not truncated")) { - DN_Str8 str = DN_Str8Lit("HelloWorld"); // 10 chars - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 10); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Long string is truncated in the middle")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); // 5 + 3 + 5 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Empty truncator concatenates head and tail")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit(""); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 10); // 5 + 0 + 5 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("HelloWorld")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: side_size of 0 returns just truncator")) { - DN_Str8 str = DN_Str8Lit("HelloWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 0, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 3); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("...")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Null dest calculates size without writing")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_Assert(&result, res.str8.data == nullptr); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: count_req is consistent between dry-run and actual")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult dry = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); - char dest[64] = {}; - DN_Str8TruncResult actual = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, dry.count_req == actual.count_req); - DN_UT_Assert(&result, dry.truncated == actual.truncated); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Minimum buffer size (2*side_size + trunc.size + 1) is sufficient")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[14] = {}; // Exactly 2*5 + 3 + 1 - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Single character side size")) { - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 1, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 5); // 1 + 3 + 1 - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("H...d")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - for (DN_UT_Test(&result, "TruncMiddlePtr: Large side_size falls back to copy")) { - DN_Str8 str = DN_Str8Lit("Hello"); - DN_Str8 trunc = DN_Str8Lit("..."); - char dest[64] = {}; - DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 100, trunc, dest, sizeof(dest)); - DN_UT_Assert(&result, !res.truncated); - DN_UT_Assert(&result, res.count_req == 5); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello")), "%.*s", DN_Str8PrintFmt(res.str8)); - } - - // NOTE: DN_Str8TruncMiddle (arena wrapper) - for (DN_UT_Test(&result, "TruncMiddle: Arena wrapper allocates and truncates correctly")) { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); - DN_Str8 trunc = DN_Str8Lit("..."); - DN_Str8TruncResult res = DN_Str8TruncMiddle(str, 5, trunc, &scratch.arena); - DN_UT_Assert(&result, res.truncated); - DN_UT_Assert(&result, res.count_req == 13); - DN_UT_AssertF(&result, DN_Str8Eq(res.str8, DN_Str8Lit("Hello...World")), "%.*s", DN_Str8PrintFmt(res.str8)); - DN_UT_Assert(&result, res.str8.data[res.str8.count] == '\0'); - DN_TCScratchEnd(&scratch); - } - } - } - return result; -} - -static DN_UTCore DN_TST_Win() -{ - DN_UTCore result = DN_UT_Init(); - #if defined(DN_PLATFORM_WIN32) - DN_UT_LogF(&result, "OS Win32\n"); - { - DN_TCScratch scratch = DN_TCScratchBeginArena(nullptr, 0); - DN_Str8 input8 = DN_Str8Lit("String"); - DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; - - for (DN_UT_Test(&result, "Str8 to Str16")) { - DN_Str16 str_result = DN_OS_W32Str8ToStr16(&scratch.arena, input8); - DN_UT_Assert(&result, DN_Str16Eq(str_result, input16)); - } - - for (DN_UT_Test(&result, "Str16 to Str8")) { - DN_Str8 str_result = DN_OS_W32Str16ToStr8(&scratch.arena, input16); - DN_UT_Assert(&result, DN_Str8Eq(str_result, input8)); - } - - for (DN_UT_Test(&result, "Str16 to Str8: Null terminates string")) { - int size_required = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); - char *string = DN_ArenaNewArray(&scratch.arena, char, size_required + 1, DN_ZMem_No); - - // Fill the string with error sentinels - DN_Memset(string, 'Z', size_required + 1); - - int size_returned = DN_OS_W32Str16ToStr8Buffer(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); - } - - for (DN_UT_Test(&result, "Str16 to Str8: Arena null terminates string")) { - DN_Str8 string8 = DN_OS_W32Str16ToStr8(&scratch.arena, input16); - int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); - char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; - - DN_UT_AssertF(&result, DN_Cast(int) string8.count == size_returned, "string_size: %d, result: %d", DN_Cast(int) string8.count, size_returned); - DN_UT_AssertF(&result, DN_Cast(int) string8.count == DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1); - DN_UT_Assert(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); - } - DN_TCScratchEnd(&scratch); - } - #endif // DN_PLATFORM_WIN32 - return result; -} - -static DN_UTCore DN_TST_Net() -{ - DN_UTCore result = DN_UT_Init(); -#if defined(DN_UNIT_TESTS_WITH_NET) - DN_Str8 label = {}; - DN_NETInterface net_interface = {}; -#if defined(DN_PLATFORM_EMSCRIPTEN) - net_interface = DN_NET_EmcInterface(); - label = DN_Str8Lit("Emscripten"); -#elif defined(DN_UNIT_TESTS_WITH_CURL) - net_interface = DN_NET_CurlInterface(); - label = DN_Str8Lit("CURL"); -#endif - - if (label.count) { - DN_UT_LogF(&result, "DN_NET\n"); - - DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(4), DN_MemFlags_Nil); - DN_Arena arena = DN_ArenaFromMemList(&mem); - DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org"); - DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com"); - - DN_USize net_base_size = DN_Megabytes(1); - char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes); - DN_NETCore net = {}; - net_interface.init(&net, net_base, net_base_size); - - DN_U64 arena_reset_p = DN_MemListPos(arena.mem); - for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP GET request", DN_Str8PrintFmt(label))) { - DN_NETRequestHandle request = net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); - DN_NETResponse response = net_interface.wait_for_response(request, &arena, UINT32_MAX); - DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); - DN_UT_AssertF(&result, response.error_str8.count == 0, "%.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_Assert(&result, response.body.count); - } - - for (DN_UT_Test(&result, "%.*s WaitForResponse HTTP POST request", DN_Str8PrintFmt(label))) { - net_interface.do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); - DN_NETResponse response = net_interface.wait_for_any_response(&net, &arena, UINT32_MAX); - DN_UT_AssertF(&result, response.http_status == 200, "http_status=%u", response.http_status); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_HTTP, "state=%u", response.state); - DN_UT_AssertF(&result, response.error_str8.count == 0, "error=%.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_Assert(&result, response.body.count); - } - - for (DN_UT_Test(&result, "%.*s WaitForResponse WS request", DN_Str8PrintFmt(label))) { - DN_NETRequestHandle request = net_interface.do_ws(&net, remote_ws_server_url); - DN_USize const WS_TIMEOUT_MS = 16; - - // NOTE: Wait for WS connection to open - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSOpen, "state=%d", response.state); - done = true; - } - - // NOTE: Receive the initial text from the echo server - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSText, "state=%d", response.state); - // NOTE: Send the close signal - net_interface.do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close); - done = true; - } - - // NOTE: Expect to hear the close - for (bool done = false; result.state != DN_UTState_TestFailed && !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { - DN_NETResponse response = net_interface.wait_for_response(request, &arena, WS_TIMEOUT_MS); - if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout - continue; - if (response.state == DN_NETResponseState_Error) - DN_UT_Log(&result, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); - DN_UT_AssertF(&result, response.state == DN_NETResponseState_WSClose, "state=%d"); - done = true; - } - } - net_interface.deinit(&net); - DN_MemListDeinit(arena.mem); - } -#endif // defined(DN_UNIT_TESTS_WITH_NET) - return result; -} - -DN_TSTResult DN_TST_RunSuite(DN_TSTPrint print) -{ - DN_UTCore tests[] = - { - DN_TST_Base(), - DN_TST_BaseArena(), - DN_TST_BaseStrings(), - DN_TST_BaseBytesHex(), - #if DN_H_WITH_HELPERS - DN_TST_BinarySearch(), - #endif - DN_TST_BaseDSMap(), - DN_TST_BaseIArray(), - DN_TST_BaseArray(), - DN_TST_Keccak(), - DN_TST_M4(), - DN_TST_OS(), - DN_TST_Rect(), - DN_TST_Win(), - DN_TST_Net(), - }; - - DN_TSTResult 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) { - if (test.num_tests_in_group <= 0) - continue; - bool do_print = print == DN_TSTPrint_Yes; - if (print == DN_TSTPrint_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/Source/Extra/dn_tests_main.cpp b/Source/Extra/dn_tests_main.cpp deleted file mode 100644 index 72007a9..0000000 --- a/Source/Extra/dn_tests_main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#if DN_WITH_NET_CURL - #define DN_NO_WINDOWS_H_REPLACEMENT_HEADER -#endif - -#define DN_ARENA_TEMP_MEM_UAF_GUARD 1 -#define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 -#define DN_WITH_OS 1 -#define DN_WITH_NET 1 -#if defined(DN_PLATFORM_EMSCRIPTEN) - #define DN_WITH_NET_EMSCRIPTEN 1 -#endif -#include "../dn.h" - -#if DN_WITH_NET_CURL - #define CURL_STATICLIB - #include -#endif -#include "../dn.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 dn = {}; - DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS, DN_TCInitArgsDefault()); - DN_TST_RunSuite(DN_TSTPrint_Yes); - return 0; -} -DN_MSVC_WARNING_POP diff --git a/Source/OS/dn_os_posix.cpp b/Source/OS/dn_os_posix.cpp index fe8425d..c904d34 100644 --- a/Source/OS/dn_os_posix.cpp +++ b/Source/OS/dn_os_posix.cpp @@ -1281,7 +1281,7 @@ static void *DN_OS_ThreadFunc_(void *user_context) return nullptr; } -DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context) +DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context) { bool result = false; if (!thread) @@ -1291,7 +1291,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh thread->user_context = user_context; thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); thread->lane = *lane; - thread->tc_init_args = tc_init_args; + thread->tc_init_args = init_args.tc_args; // TODO(doyle): Check if semaphore is valid // NOTE: pthread_t is essentially the thread ID. In Windows, the handle and @@ -1307,6 +1307,9 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh pthread_attr_t attribs = {}; pthread_attr_init(&attribs); + if (init_args.stack_size) + pthread_attr_setstacksize(&attribs, init_args.stack_size); + result = pthread_create(&p_thread, &attribs, DN_OS_ThreadFunc_, thread) == 0; pthread_attr_destroy(&attribs); @@ -1415,21 +1418,21 @@ DN_API DN_OSPosixProcSelfStatus DN_OS_PosixProcSelfStatus() DN_Memcpy(result.name, str8.data, result.name_size); } else if (DN_Str8StartsWith(line, PID, DN_Str8EqCase_Insensitive)) { DN_Str8 str8 = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, PID.count, line.count)); - DN_U64FromResult to_u64 = DN_U64FromStr8(str8, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(str8); result.pid = to_u64.value; DN_Assert(to_u64.success); } else if (DN_Str8StartsWith(line, VM_SIZE, DN_Str8EqCase_Insensitive)) { DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_SIZE.count, line.count)); DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB"))); DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs; - DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size); result.vm_size = DN_Kilobytes(to_u64.value); DN_Assert(to_u64.success); } else if (DN_Str8StartsWith(line, VM_PEAK, DN_Str8EqCase_Insensitive)) { DN_Str8 size_with_kb = DN_Str8TrimWhitespaceAround(DN_Str8Subset(line, VM_PEAK.count, line.count)); DN_Assert(DN_Str8EndsWith(size_with_kb, DN_Str8Lit("kB"))); DN_Str8 vm_size = DN_Str8BSplit(size_with_kb, DN_Str8Lit(" ")).lhs; - DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(vm_size); result.vm_peak = DN_Kilobytes(to_u64.value); DN_Assert(to_u64.success); } diff --git a/Source/OS/dn_os_w32.cpp b/Source/OS/dn_os_w32.cpp index 60932ea..6b27f7a 100644 --- a/Source/OS/dn_os_w32.cpp +++ b/Source/OS/dn_os_w32.cpp @@ -1,8 +1,7 @@ #define DN_OS_W32_CPP #if defined(_CLANGD) - #define DN_H_WITH_CORE 1 - #define DN_H_WITH_OS 1 + #define DN_WITH_OS 1 #include "../dn.h" #include "dn_os_w32.h" #endif @@ -99,13 +98,15 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZMem z_mem) { - DN_Core *dn = DN_Get(); - DN_AssertRaw(dn->init_flags & DN_InitFlags_OS && "DN must be initialised with the OS flag"); DN_U32 flags = z_mem == DN_ZMem_Yes ? HEAP_ZERO_MEMORY : 0; DN_Assert(size <= DN_Cast(DWORD)(-1)); void *result = HeapAlloc(GetProcessHeap(), flags, DN_Cast(DWORD) size); - DN_AtomicAddU64(&dn->os.mem_allocs_total, 1); - DN_AtomicAddU64(&dn->os.mem_allocs_frame, 1); + + DN_Core *dn = DN_Get(); + if (dn) { + DN_AtomicAddU64(&dn->os.mem_allocs_total, 1); + DN_AtomicAddU64(&dn->os.mem_allocs_frame, 1); + } return result; } @@ -637,10 +638,10 @@ DN_API bool DN_OS_PathIterateDir(DN_Str8 path, DN_OSDirIterator *it) if (it->handle) { wide_it.handle = it->handle; } else { - bool needs_asterisks = DN_Str8EndsWith(path, DN_Str8Lit("\\")) || - DN_Str8EndsWith(path, DN_Str8Lit("/")); - bool has_glob = DN_Str8EndsWith(path, DN_Str8Lit("\\*")) || - DN_Str8EndsWith(path, DN_Str8Lit("/*")); + bool needs_asterisks = DN_Str8EndsWithSensitive(path, DN_Str8Lit("\\")) || + DN_Str8EndsWithSensitive(path, DN_Str8Lit("/")); + bool has_glob = DN_Str8EndsWithSensitive(path, DN_Str8Lit("\\*")) || + DN_Str8EndsWithSensitive(path, DN_Str8Lit("/*")); DN_Str8 adjusted_path = path; if (!has_glob) { @@ -1227,14 +1228,13 @@ DN_API void DN_OS_ConditionVariableBroadcast(DN_OSConditionVariable *cv) } } -// 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, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context) +DN_API bool DN_OS_ThreadInitLane(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context) { bool result = false; if (!thread) @@ -1243,7 +1243,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh thread->func = func; thread->user_context = user_context; thread->init_semaphore = DN_OS_SemaphoreInit(0 /*initial_count*/); - thread->tc_init_args = tc_init_args; + thread->tc_init_args = init_args.tc_args; if (lane) { thread->is_lane_set = true; thread->lane = *lane; @@ -1253,7 +1253,7 @@ DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSTh DWORD thread_id = 0; SECURITY_ATTRIBUTES security_attribs = {}; thread->handle = CreateThread(&security_attribs, - 0 /*stack_size*/, + init_args.stack_size, DN_OS_ThreadFunc_, thread, 0 /*creation_flags*/, @@ -1522,10 +1522,10 @@ DN_API DN_Str8 DN_OS_W32Str16ToStr8FromHeap(DN_Str16 src) 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_Str8FromHeap(required_size, DN_ZMem_No); - if (buffer.count == 0) + DN_Str8 buffer = {}; + buffer.data = DN_Cast(char *) DN_OS_MemAlloc(required_size + 1, DN_ZMem_No); + buffer.count = required_size; + if (!buffer.data) return result; int chars_written = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, src.data, src_size, buffer.data, DN_Cast(int) buffer.count, nullptr, nullptr); diff --git a/Source/Standalone/dn_ini.c b/Source/Standalone/dn_ini.c index 2ec3d9e..1945886 100644 --- a/Source/Standalone/dn_ini.c +++ b/Source/Standalone/dn_ini.c @@ -861,7 +861,7 @@ DN_INIField *DN_INI_AppendKeyF(DN_INICore *ini, DN_INIArena *arena, DN_INISectio return result; } -#if defined(DN_INI_WITH_UNIT_TESTS) +#if defined(DN_INI_WITH_TESTS) void DN_INI_UnitTests() { // NOTE: Section and comments @@ -1141,4 +1141,4 @@ void DN_INI_UnitTests() DN_INI_Assert(DN_INI_Str8Eq(parse.first_section.child_first->first_field->next->key, DN_INIStr8Lit("bar"))); } } -#endif +#endif // #if defined(DN_INI_WITH_TESTS) diff --git a/Source/Standalone/dn_ini.h b/Source/Standalone/dn_ini.h index c8758fe..9f6c3ef 100644 --- a/Source/Standalone/dn_ini.h +++ b/Source/Standalone/dn_ini.h @@ -2,29 +2,30 @@ #define DN_INI_H // NOTE: DN INI Configuration -// Getting Started -// This is a single header and implementation file library that implements .ini file handling. -// It supports the following .ini features: + +// Getting Started +// This is a single header and implementation file library that implements .ini file handling. +// It supports the following .ini features: // -// - Plain sections: [sections] -// - Arbitrarily nested sections delimited by '.': [sections.a] [sections.a.b] [sections.a.b....] -// - Repeated section names (the last section takes precedence) -// - Comments marked by #: [section] # Comment -// - Multi-line values for keys: -// [my_section] -// the_key = the_value \ -// another line for the key \ -// and another one -// the_next_key = that's cool +// Plain sections: [sections] +// Arbitrarily nested sections delimited by '.': [sections.a] [sections.a.b] [sections.a.b....] +// Repeated section names (the last section takes precedence) +// Comments marked by #: [section] # Comment +// Multi-line values for keys: +// [my_section] +// the_key = the_value \ +// another line for the key \ +// and another one +// the_next_key = that's cool // -// Include both .h and .c in your translation unit or compile the .c separately and link against -// it to get started. The goals of the library are as follows: +// Include both .h and .c in your translation unit or compile the .c separately and link against +// it to get started. The goals of the library are as follows: // -// - Zero allocations to parse, accepts a NULL buffer to determine the amount of bytes required -// to parse the .ini buffer -// - Compile in C99 and compatible with C++ -// -// Example +// Zero allocations to parse, accepts a NULL buffer to determine the amount of bytes required +// to parse the .ini buffer +// Compile in C99 and compatible with C++ + +// Example /* #include #include @@ -94,34 +95,34 @@ } */ - #include // size_t +#include // size_t - #if !defined(DN_INI_Assert) - #include - #define DN_INI_Assert(expr) assert(expr) +#if !defined(DN_INI_Assert) + #include + #define DN_INI_Assert(expr) assert(expr) +#endif + +#include + +#if !defined(DN_INI_VSNPrintF) + #include + #define DN_INI_VSNPrintF(buffer, size, fmt, args) vsnprintf(buffer, size, fmt, args) +#endif + +#if !defined(DN_INI_Memset) || !defined(DN_INI_Memcmp) || !defined(DN_INI_Memcpy) + #include + #if !defined(DN_INI_Memset) + #define DN_INI_Memset(ptr, val, size) memset(ptr, val, size) #endif - #include - - #if !defined(DN_INI_VSNPrintF) - #include - #define DN_INI_VSNPrintF(buffer, size, fmt, args) vsnprintf(buffer, size, fmt, args) + #if !defined(DN_INI_Memcmp) + #define DN_INI_Memcmp(dest, src, size) memcmp(dest, src, size) #endif - #if !defined(DN_INI_Memset) || !defined(DN_INI_Memcmp) || !defined(DN_INI_Memcpy) - #include - #if !defined(DN_INI_Memset) - #define DN_INI_Memset(ptr, val, size) memset(ptr, val, size) - #endif - - #if !defined(DN_INI_Memcmp) - #define DN_INI_Memcmp(dest, src, size) memcmp(dest, src, size) - #endif - - #if !defined(DN_INI_Memcpy) - #define DN_INI_Memcpy(dest, src, size) memcpy(dest, src, size) - #endif + #if !defined(DN_INI_Memcpy) + #define DN_INI_Memcpy(dest, src, size) memcpy(dest, src, size) #endif +#endif typedef enum DN_INITokenType { DN_INITokenType_Nil, @@ -278,7 +279,7 @@ DN_INIField * DN_INI_AppendKeyCStr8 (DN_INICore *ini, DN DN_INIField * DN_INI_AppendKeyF (DN_INICore *ini, DN_INIArena *arena, DN_INISection *section, DN_INIStr8 key, char const *fmt, ...); void DN_INI_AppendField (DN_INISection *section, DN_INIField *field); -#if defined(DN_INI_WITH_UNIT_TESTS) -void DN_INI_UnitTests (); +#if defined(DN_INI_WITH_TESTS) +void DN_INI_TestSuite (); #endif -#endif // !defined(DN_INI_H) +#endif // #if !defined(DN_INI_H) diff --git a/Source/Standalone/dn_sha3.h b/Source/Standalone/dn_sha3.h index 97b19ae..8221205 100644 --- a/Source/Standalone/dn_sha3.h +++ b/Source/Standalone/dn_sha3.h @@ -2,18 +2,22 @@ #define DN_SHA3_H // NOTE: DN_Sha3 -- FIPS202 SHA3 + non-finalized SHA3 (aka. Keccak) hashing algorithms -// + // Overview // Single header file 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). -// + // Configuration // Define this in one and only one C++ file to enable the implementation code of the header file. // // #define DN_SHA3_IMPLEMENTATION // +// Define this to enable unit tests in the implementation, it requires dn.h to be visible in the +// +// #define DN_SHA3_WITH_TESTS + // License // MIT License // @@ -35,26 +39,26 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#if !defined(DN_SHA3Memcpy) +#if !defined(DN_SHA3_Memcpy) #include - #define DN_SHA3Memcpy(dest, src, count) memcpy(dest, src, count) + #define DN_SHA3_Memcpy(dest, src, count) memcpy(dest, src, count) #endif -#if !defined(DN_SHA3Memcmp) +#if !defined(DN_SHA3_Memcmp) #include - #define DN_SHA3Memcmp(dest, src, count) memcmp(dest, src, count) + #define DN_SHA3_Memcmp(dest, src, count) memcmp(dest, src, count) #endif -#if !defined(DN_SHA3Memset) +#if !defined(DN_SHA3_Memset) #include - #define DN_SHA3Memset(dest, byte, count) memset(dest, byte, count) + #define DN_SHA3_Memset(dest, byte, count) memset(dest, byte, count) #endif -#if !defined(DN_SHA3Assert) +#if !defined(DN_SHA3_Assert) #if defined(NDEBUG) - #define DN_SHA3Assert(expr) + #define DN_SHA3_Assert(expr) #else - #define DN_SHA3Assert(expr) \ + #define DN_SHA3_Assert(expr) \ do { \ if (!(expr)) { \ (*(volatile int *)0) = 0; \ @@ -81,47 +85,52 @@ typedef struct DN_SHA3State { char delimited_suffix; // The delimited suffix of the current hash } DN_SHA3State; -enum DN_SHA3Family +enum DN_SHA3Type { - DN_SHA3Family_SHA3, // FIPS 202 SHA3 (delimited suffix is 0x6) - DN_SHA3Family_Keccak, // Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6) + DN_SHA3Type_SHA3, // FIPS 202 SHA3 (delimited suffix is 0x6) + DN_SHA3Type_Keccak, // Non-finalized SHA3 (only difference is delimited suffix of 0x1 instead of 0x6) }; // hash_size_bits: Number of bits to hash to. Available sizes are 224, 256, 384 and 512. -DN_SHA3State DN_SHA3FamilyInit (DN_SHA3Family type, size_t hash_size_bits); -DN_SHA3State DN_SHA3FamilyInitSHA3 (size_t hash_size_bits); -DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits); -void DN_SHA3FamilyUpdate (DN_SHA3State *sha3, void const *data, size_t data_size); -void DN_SHA3FamilyFinish (DN_SHA3State *sha3, void *dest, size_t dest_size); -void DN_SHA3FamilyHash (DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, int dest_size); +DN_SHA3State DN_SHA3_Init (DN_SHA3Type type, size_t hash_size_bits); +DN_SHA3State DN_SHA3_InitSHA3 (size_t hash_size_bits); +DN_SHA3State DN_SHA3_InitKeccak (size_t hash_size_bits); +void DN_SHA3_Update (DN_SHA3State *sha3, void const *data, size_t data_size); +void DN_SHA3_Finish (DN_SHA3State *sha3, void *dest, size_t dest_size); +void DN_SHA3_Hash (DN_SHA3Type type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, int dest_size); -void DN_SHA3Hash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x28 DN_SHA3Hash224b (void const *src, size_t src_size); -void DN_SHA3Hash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x32 DN_SHA3Hash256b (void const *src, size_t src_size); -void DN_SHA3Hash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x48 DN_SHA3Hash384b (void const *src, size_t src_size); -void DN_SHA3Hash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x64 DN_SHA3Hash512b (void const *src, size_t src_size); +void DN_SHA3_Hash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x28 DN_SHA3_Hash224b (void const *src, size_t src_size); +void DN_SHA3_Hash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x32 DN_SHA3_Hash256b (void const *src, size_t src_size); +void DN_SHA3_Hash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x48 DN_SHA3_Hash384b (void const *src, size_t src_size); +void DN_SHA3_Hash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x64 DN_SHA3_Hash512b (void const *src, size_t src_size); -void DN_KeccakHash224bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x28 DN_KeccakHash224b (void const *src, size_t src_size); -void DN_KeccakHash256bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x32 DN_KeccakHash256b (void const *src, size_t src_size); -void DN_KeccakHash384bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x48 DN_KeccakHash384b (void const *src, size_t src_size); -void DN_KeccakHash512bPtr (void const *src, size_t src_size, void *dest, size_t dest_size); -DN_SHA3U8x64 DN_KeccakHash512b (void const *src, size_t src_size); +void DN_SHA3_HashKeccak224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x28 DN_SHA3_HashKeccak224b (void const *src, size_t src_size); +void DN_SHA3_HashKeccak256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x32 DN_SHA3_HashKeccak256b (void const *src, size_t src_size); +void DN_SHA3_HashKeccak384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x48 DN_SHA3_HashKeccak384b (void const *src, size_t src_size); +void DN_SHA3_HashKeccak512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size); +DN_SHA3U8x64 DN_SHA3_HashKeccak512b (void const *src, size_t src_size); -void DN_SHA3HexFromBytes (void const *src, uint64_t src_size, char *dest, uint64_t dest_size); -DN_SHA3Str8x56 DN_SHA3HexFromU8x28 (DN_SHA3U8x28 const *bytes); -DN_SHA3Str8x64 DN_SHA3HexFromU8x32 (DN_SHA3U8x32 const *bytes); -DN_SHA3Str8x96 DN_SHA3HexFromU8x48 (DN_SHA3U8x48 const *bytes); -DN_SHA3Str8x128 DN_SHA3HexFromU8x64 (DN_SHA3U8x64 const *bytes); -bool DN_SHA3U8x28Eq (DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b); -bool DN_SHA3U8x32Eq (DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b); -bool DN_SHA3U8x48Eq (DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b); -bool DN_SHA3U8x64Eq (DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b); +void DN_SHA3_HexFromBytes (void const *src, uint64_t src_size, char *dest, uint64_t dest_size); +DN_SHA3Str8x56 DN_SHA3_HexFromU8x28 (DN_SHA3U8x28 const *bytes); +DN_SHA3Str8x64 DN_SHA3_HexFromU8x32 (DN_SHA3U8x32 const *bytes); +DN_SHA3Str8x96 DN_SHA3_HexFromU8x48 (DN_SHA3U8x48 const *bytes); +DN_SHA3Str8x128 DN_SHA3_HexFromU8x64 (DN_SHA3U8x64 const *bytes); +bool DN_SHA3_U8x28Eq (DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b); +bool DN_SHA3_U8x32Eq (DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b); +bool DN_SHA3_U8x48Eq (DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b); +bool DN_SHA3_U8x64Eq (DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b); + +#if defined(DN_SHA3_WITH_TESTS) +DN_TestCore DN_SHA3_TestSuite(DN_Arena *arena); +void DN_SHA3_TestSuiteThenOutput(DN_Str8FromTestCoreFlags flags); +#endif #endif // DN_SHA3_H #if defined(DN_SHA3_IMPLEMENTATION) @@ -143,7 +152,7 @@ uint64_t const DN_SHA3_ROTATIONS[][5] = }; #define DN_SHA3_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate))))) -static void DN_SHA3FamilyPermute_(void *state) +static void DN_SHA3_Permute_(void *state) { uint64_t *lanes_u64 = (uint64_t *)state; for (int round_index = 0; round_index < 24; round_index++) { @@ -206,12 +215,12 @@ static void DN_SHA3FamilyPermute_(void *state) } } -DN_SHA3State DN_SHA3FamilyInit(DN_SHA3Family type, size_t hash_size_bits) +DN_SHA3State DN_SHA3_Init(DN_SHA3Type type, size_t hash_size_bits) { - DN_SHA3Assert(hash_size_bits == 224 || - hash_size_bits == 256 || - hash_size_bits == 384 || - hash_size_bits == 512); + DN_SHA3_Assert(hash_size_bits == 224 || + hash_size_bits == 256 || + hash_size_bits == 384 || + hash_size_bits == 512); char const SHA3_DELIMITED_SUFFIX = 0x06; char const KECCAK_DELIMITED_SUFFIX = 0x01; @@ -224,24 +233,24 @@ DN_SHA3State DN_SHA3FamilyInit(DN_SHA3Family type, size_t hash_size_bits) #endif result.hash_size_bits = hash_size_bits; result.absorb_size = bitrate / 8; - result.delimited_suffix = type == DN_SHA3Family_SHA3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX; - DN_SHA3Assert(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600); + result.delimited_suffix = type == DN_SHA3Type_SHA3 ? SHA3_DELIMITED_SUFFIX : KECCAK_DELIMITED_SUFFIX; + DN_SHA3_Assert(bitrate + (hash_size_bits * 2) /*capacity*/ == 1600); return result; } -DN_SHA3State DN_SHA3FamilyInitSHA3(size_t hash_size_bits) +DN_SHA3State DN_SHA3_InitSHA3(size_t hash_size_bits) { - DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_SHA3, hash_size_bits); + DN_SHA3State result = DN_SHA3_Init(DN_SHA3Type_SHA3, hash_size_bits); return result; } -DN_SHA3State DN_SHA3FamilyInitKeccak(size_t hash_size_bits) +DN_SHA3State DN_SHA3_InitKeccak(size_t hash_size_bits) { - DN_SHA3State result = DN_SHA3FamilyInit(DN_SHA3Family_Keccak, hash_size_bits); + DN_SHA3State result = DN_SHA3_Init(DN_SHA3Type_Keccak, hash_size_bits); return result; } -void DN_SHA3FamilyUpdate(DN_SHA3State *sha3, void const *data, size_t data_size) +void DN_SHA3_Update(DN_SHA3State *sha3, void const *data, size_t data_size) { uint8_t *state = sha3->state; uint8_t const *ptr = (uint8_t *)data; @@ -258,21 +267,21 @@ void DN_SHA3FamilyUpdate(DN_SHA3State *sha3, void const *data, size_t data_size) ptr_size -= bytes_to_absorb; if (sha3->state_size >= sha3->absorb_size) { - DN_SHA3Assert(sha3->state_size == sha3->absorb_size); - DN_SHA3FamilyPermute_(state); + DN_SHA3_Assert(sha3->state_size == sha3->absorb_size); + DN_SHA3_Permute_(state); sha3->state_size = 0; } } } -void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size) +void DN_SHA3_Finish(DN_SHA3State *sha3, void *dest, size_t dest_size) { - DN_SHA3Assert(dest_size >= (size_t)(sha3->hash_size_bits / 8)); + DN_SHA3_Assert(dest_size >= (size_t)(sha3->hash_size_bits / 8)); // Sponge Finalization Step: Final padding bit size_t const INDEX_OF_0X80_BYTE = sha3->absorb_size - 1; size_t const delimited_suffix_index = sha3->state_size; - DN_SHA3Assert(delimited_suffix_index < sha3->absorb_size); + DN_SHA3_Assert(delimited_suffix_index < sha3->absorb_size); uint8_t *state = sha3->state; state[delimited_suffix_index] ^= sha3->delimited_suffix; @@ -287,7 +296,7 @@ void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size) // this from the implementation here. state[INDEX_OF_0X80_BYTE] ^= 0x80; - DN_SHA3FamilyPermute_(state); + DN_SHA3_Permute_(state); // Squeeze Step: Squeeze bytes from the state into our hash uint8_t *dest_u8 = (uint8_t *)dest; @@ -295,8 +304,8 @@ void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size) size_t squeeze_index = 0; for (; squeeze_index < squeeze_count; squeeze_index++) { if (squeeze_index) - DN_SHA3FamilyPermute_(state); - DN_SHA3Memcpy(dest_u8, state, sha3->absorb_size); + DN_SHA3_Permute_(state); + DN_SHA3_Memcpy(dest_u8, state, sha3->absorb_size); dest_u8 += sha3->absorb_size; } @@ -304,119 +313,119 @@ void DN_SHA3FamilyFinish(DN_SHA3State *sha3, void *dest, size_t dest_size) size_t const remainder = dest_size % sha3->absorb_size; if (remainder) { if (squeeze_index) - DN_SHA3FamilyPermute_(state); - DN_SHA3Memcpy(dest_u8, state, remainder); + DN_SHA3_Permute_(state); + DN_SHA3_Memcpy(dest_u8, state, remainder); } } -void DN_SHA3FamilyHash(DN_SHA3Family type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_Hash(DN_SHA3Type type, size_t hash_size_bits, void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3State state = DN_SHA3FamilyInit(type, hash_size_bits); - DN_SHA3FamilyUpdate(&state, src, src_size); - DN_SHA3FamilyFinish(&state, dest, dest_size); + DN_SHA3State state = DN_SHA3_Init(type, hash_size_bits); + DN_SHA3_Update(&state, src, src_size); + DN_SHA3_Finish(&state, dest, dest_size); } -void DN_SHA3Hash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_Hash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_SHA3, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); } -DN_SHA3U8x28 DN_SHA3Hash224b(void const *src, size_t src_size) +DN_SHA3U8x28 DN_SHA3_Hash224b(void const *src, size_t src_size) { DN_SHA3U8x28 result = {}; - DN_SHA3Hash224bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_Hash224bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_SHA3Hash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_Hash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_SHA3, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); } -DN_SHA3U8x32 DN_SHA3Hash256b(void const *src, size_t src_size) +DN_SHA3U8x32 DN_SHA3_Hash256b(void const *src, size_t src_size) { DN_SHA3U8x32 result = {}; - DN_SHA3Hash256bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_Hash256bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_SHA3Hash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_Hash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_SHA3, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); } -DN_SHA3U8x48 DN_SHA3Hash384b(void const *src, size_t src_size) +DN_SHA3U8x48 DN_SHA3_Hash384b(void const *src, size_t src_size) { DN_SHA3U8x48 result = {}; - DN_SHA3Hash384bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_Hash384bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_SHA3Hash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_Hash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_SHA3, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_SHA3, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); } -DN_SHA3U8x64 DN_SHA3Hash512b(void const *src, size_t src_size) +DN_SHA3U8x64 DN_SHA3_Hash512b(void const *src, size_t src_size) { DN_SHA3U8x64 result = {}; - DN_SHA3Hash512bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_Hash512bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_KeccakHash224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_HashKeccak224bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_Keccak, /*hash_size_bits=*/ 224, src, src_size, dest, dest_size); } -DN_SHA3U8x28 DN_KeccakHash224b(void const *src, size_t src_size) +DN_SHA3U8x28 DN_SHA3_HashKeccak224b(void const *src, size_t src_size) { DN_SHA3U8x28 result = {}; - DN_KeccakHash224bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_HashKeccak224bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_KeccakHash256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_HashKeccak256bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_Keccak, /*hash_size_bits=*/ 256, src, src_size, dest, dest_size); } -DN_SHA3U8x32 DN_KeccakHash256b(void const *src, size_t src_size) +DN_SHA3U8x32 DN_SHA3_HashKeccak256b(void const *src, size_t src_size) { DN_SHA3U8x32 result = {}; - DN_KeccakHash256bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_HashKeccak256bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_KeccakHash384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_HashKeccak384bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_Keccak, /*hash_size_bits=*/ 384, src, src_size, dest, dest_size); } -DN_SHA3U8x48 DN_KeccakHash384b(void const *src, size_t src_size) +DN_SHA3U8x48 DN_SHA3_HashKeccak384b(void const *src, size_t src_size) { DN_SHA3U8x48 result = {}; - DN_KeccakHash384bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_HashKeccak384bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_KeccakHash512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) +void DN_SHA3_HashKeccak512bPtr(void const *src, size_t src_size, void *dest, size_t dest_size) { - DN_SHA3FamilyHash(DN_SHA3Family_Keccak, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); + DN_SHA3_Hash(DN_SHA3Type_Keccak, /*hash_size_bits=*/ 512, src, src_size, dest, dest_size); } -DN_SHA3U8x64 DN_KeccakHash512b(void const *src, size_t src_size) +DN_SHA3U8x64 DN_SHA3_HashKeccak512b(void const *src, size_t src_size) { DN_SHA3U8x64 result = {}; - DN_KeccakHash512bPtr(src, src_size, result.data, sizeof(result.data)); + DN_SHA3_HashKeccak512bPtr(src, src_size, result.data, sizeof(result.data)); return result; } -void DN_SHA3HexFromBytes(void const *src, size_t src_size, char *dest, size_t dest_size) +void DN_SHA3_HexFromBytes(void const *src, size_t src_size, char *dest, size_t dest_size) { (void)src_size; (void)dest_size; - DN_SHA3Assert(dest_size >= src_size * 2); + DN_SHA3_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; @@ -429,59 +438,373 @@ void DN_SHA3HexFromBytes(void const *src, size_t src_size, char *dest, size_t de } } -DN_SHA3Str8x56 DN_SHA3HexFromU8x28(DN_SHA3U8x28 const *bytes) +DN_SHA3Str8x56 DN_SHA3_HexFromU8x28(DN_SHA3U8x28 const *bytes) { DN_SHA3Str8x56 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + DN_SHA3_HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); result.data[sizeof(result.data) - 1] = 0; return result; } -DN_SHA3Str8x64 DN_SHA3HexFromU8x32(DN_SHA3U8x32 const *bytes) +DN_SHA3Str8x64 DN_SHA3_HexFromU8x32(DN_SHA3U8x32 const *bytes) { DN_SHA3Str8x64 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + DN_SHA3_HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); result.data[sizeof(result.data) - 1] = 0; return result; } -DN_SHA3Str8x96 DN_SHA3HexFromU8x48(DN_SHA3U8x48 const *bytes) +DN_SHA3Str8x96 DN_SHA3_HexFromU8x48(DN_SHA3U8x48 const *bytes) { DN_SHA3Str8x96 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + DN_SHA3_HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); result.data[sizeof(result.data) - 1] = 0; return result; } -DN_SHA3Str8x128 DN_SHA3HexFromU8x64(DN_SHA3U8x64 const *bytes) +DN_SHA3Str8x128 DN_SHA3_HexFromU8x64(DN_SHA3U8x64 const *bytes) { DN_SHA3Str8x128 result; - DN_SHA3HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); + DN_SHA3_HexFromBytes(bytes->data, sizeof(bytes->data), result.data, sizeof(result.data)); result.data[sizeof(result.data) - 1] = 0; return result; } -bool DN_SHA3U8x32Eq(DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b) +bool DN_SHA3_U8x32Eq(DN_SHA3U8x28 const *a, DN_SHA3U8x28 const *b) { - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; + int result = DN_SHA3_Memcmp(a->data, b->data, sizeof(*a)) == 0; return result; } -bool DN_SHA3U8x32Eq(DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b) +bool DN_SHA3_U8x32Eq(DN_SHA3U8x32 const *a, DN_SHA3U8x32 const *b) { - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; + int result = DN_SHA3_Memcmp(a->data, b->data, sizeof(*a)) == 0; return result; } -bool DN_SHA3U8x48Eq(DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b) +bool DN_SHA3_U8x48Eq(DN_SHA3U8x48 const *a, DN_SHA3U8x48 const *b) { - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; + int result = DN_SHA3_Memcmp(a->data, b->data, sizeof(*a)) == 0; return result; } -bool DN_SHA3U8x64Eq(DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b) +bool DN_SHA3_U8x64Eq(DN_SHA3U8x64 const *a, DN_SHA3U8x64 const *b) { - int result = DN_SHA3Memcmp(a->data, b->data, sizeof(*a)) == 0; + int result = DN_SHA3_Memcmp(a->data, b->data, sizeof(*a)) == 0; return result; } -#endif // DN_SHA3_IMPLEMENTATION + +#if defined(DN_SHA3_WITH_TESTS) +#if !defined(DN_H) + #error dn.h must be included to enable tests for dn_sha3.h +#endif +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_SHA3RefImplKeccak_(int r, int c, const uint8_t *in, uint64_t inLen, uint8_t sfx, uint8_t *out, uint64_t outLen); + +void DN_SHA3RefImplFIPS202_SHAKE128_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen) +{ + DN_SHA3RefImplKeccak_(1344, 256, in, inLen, 0x1F, out, outLen); +} + +void DN_SHA3RefImplFIPS202_SHAKE256_(const uint8_t *in, uint64_t inLen, uint8_t *out, uint64_t outLen) +{ + DN_SHA3RefImplKeccak_(1088, 512, in, inLen, 0x1F, out, outLen); +} + +void DN_SHA3RefImplFIPS202_SHA3_224_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_SHA3RefImplKeccak_(1152, 448, in, inLen, 0x06, out, 28); +} + +void DN_SHA3RefImplFIPS202_SHA3_256_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_SHA3RefImplKeccak_(1088, 512, in, inLen, 0x06, out, 32); +} + +void DN_SHA3RefImplFIPS202_SHA3_384_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_SHA3RefImplKeccak_(832, 768, in, inLen, 0x06, out, 48); +} + +void DN_SHA3RefImplFIPS202_SHA3_512_(const uint8_t *in, uint64_t inLen, uint8_t *out) +{ + DN_SHA3RefImplKeccak_(576, 1024, in, inLen, 0x06, out, 64); +} + +int DN_SHA3RefImplLFSR86540_(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_SHA3RefImplload64_(const uint8_t *x) +{ + int i; + uint64_t u = 0; + FOR(i, 8) + { + u <<= 8; + u |= x[7 - i]; + } + return u; +} + +static void DN_SHA3RefImplstore64_(uint8_t *x, uint64_t u) +{ + int i; + FOR(i, 8) + { + x[i] = u; + u >>= 8; + } +} + +static void DN_SHA3RefImplxor64_(uint8_t *x, uint64_t u) +{ + int i; + FOR(i, 8) + { + x[i] ^= u; + u >>= 8; + } +} + +#define rL(x, y) DN_SHA3RefImplload64_((uint8_t *)s + 8 * (x + 5 * y)) +#define wL(x, y, l) DN_SHA3RefImplstore64_((uint8_t *)s + 8 * (x + 5 * y), l) +#define XL(x, y, l) DN_SHA3RefImplxor64_((uint8_t *)s + 8 * (x + 5 * y), l) + +void DN_SHA3RefImplKeccak_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_SHA3RefImplLFSR86540_(&R)) XL(0, 0, (uint64_t)1 << ((1 << j) - 1)); + } +} + +void DN_SHA3RefImplKeccak_(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_SHA3RefImplKeccak_F1600(s); + b = 0; + } + } + /*pad*/ s[b] ^= sfx; + if ((sfx & 0x80) && (b == (R - 1))) + DN_SHA3RefImplKeccak_F1600(s); + s[R - 1] ^= 0x80; + DN_SHA3RefImplKeccak_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_SHA3RefImplKeccak_F1600(s); + } +} + + #undef XL + #undef wL + #undef rL + #undef ROL + #undef FOR +DN_MSVC_WARNING_POP +DN_GCC_WARNING_POP + +enum DN_SHA3TestHash +{ + DN_SHA3TestHash_224, + DN_SHA3TestHash_256, + DN_SHA3TestHash_384, + DN_SHA3TestHash_512, + DN_SHA3TestHash_Keccak224, + DN_SHA3TestHash_Keccak256, + DN_SHA3TestHash_Keccak384, + DN_SHA3TestHash_Keccak512, + DN_SHA3TestHash_Count, +}; + +static void DN_SHA3TestHashDispatch_(DN_TestCore *test, DN_SHA3TestHash hash_type, DN_Str8 input) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&test->arena, 1); + DN_Str8 input_hex = DN_HexFromPtrBytesArena(input.data, input.count, &scratch.arena, DN_TrimLeadingZero_No); + switch (hash_type) { + case DN_SHA3TestHash_Count: DN_AssertInvalidCodePath; break; + case DN_SHA3TestHash_224: { + DN_SHA3U8x28 hash = DN_SHA3_Hash224b(input.data, input.count); + DN_SHA3U8x28 expect; + DN_SHA3RefImplFIPS202_SHA3_224_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_256: { + DN_SHA3U8x32 hash = DN_SHA3_Hash256b(input.data, input.count); + DN_SHA3U8x32 expect; + DN_SHA3RefImplFIPS202_SHA3_256_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_384: { + DN_SHA3U8x48 hash = DN_SHA3_Hash384b(input.data, input.count); + DN_SHA3U8x48 expect; + DN_SHA3RefImplFIPS202_SHA3_384_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_512: { + DN_SHA3U8x64 hash = DN_SHA3_Hash512b(input.data, input.count); + DN_SHA3U8x64 expect; + DN_SHA3RefImplFIPS202_SHA3_512_(DN_Cast(uint8_t *) input.data, input.count, (uint8_t *)expect.data); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_Keccak224: { + DN_SHA3U8x28 hash = DN_SHA3_HashKeccak224b(input.data, input.count); + DN_SHA3U8x28 expect; + DN_SHA3RefImplKeccak_(1152, 448, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_Keccak256: { + DN_SHA3U8x32 hash = DN_SHA3_HashKeccak256b(input.data, input.count); + DN_SHA3U8x32 expect; + DN_SHA3RefImplKeccak_(1088, 512, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_Keccak384: { + DN_SHA3U8x48 hash = DN_SHA3_HashKeccak384b(input.data, input.count); + DN_SHA3U8x48 expect; + DN_SHA3RefImplKeccak_(832, 768, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + + case DN_SHA3TestHash_Keccak512: { + DN_SHA3U8x64 hash = DN_SHA3_HashKeccak512b(input.data, input.count); + DN_SHA3U8x64 expect; + DN_SHA3RefImplKeccak_(576, 1024, DN_Cast(uint8_t *) input.data, input.count, 0x01, (uint8_t *)expect.data, sizeof(expect)); + DN_TestVerifyBytesEqF(test, DN_Str8FromLitArray(hash.data), DN_Str8FromLitArray(expect.data), "Input: %.*s", DN_Str8PrintFmt(input_hex)); + } break; + } + DN_TCScratchEnd(&scratch); +} + +DN_TestCore DN_SHA3_TestSuite(DN_Arena *arena) +{ + DN_TestCore result = DN_TestInit(arena); + for (DN_TestGroupScopeF(&result, "SHA3")) { + for (DN_ForIndexU(hash_type, DN_SHA3TestHash_Count)) { + // NOTE: Get a name for the hash type + DN_Str8 hash_name = {}; + switch (hash_type) { + case DN_SHA3TestHash_224: hash_name = DN_Str8Lit("SHA3-224"); break; + case DN_SHA3TestHash_256: hash_name = DN_Str8Lit("SHA3-256"); break; + case DN_SHA3TestHash_384: hash_name = DN_Str8Lit("SHA3-384"); break; + case DN_SHA3TestHash_512: hash_name = DN_Str8Lit("SHA3-512"); break; + case DN_SHA3TestHash_Keccak224: hash_name = DN_Str8Lit("Keccak-224"); break; + case DN_SHA3TestHash_Keccak256: hash_name = DN_Str8Lit("Keccak-256"); break; + case DN_SHA3TestHash_Keccak384: hash_name = DN_Str8Lit("Keccak-384"); break; + case DN_SHA3TestHash_Keccak512: hash_name = DN_Str8Lit("Keccak-512"); break; + case DN_SHA3TestHash_Count: DN_AssertInvalidCodePath; break; + } + + // NOTE: Test SHA3 against some fixed inputs + { + DN_Str8 const INPUTS[] = { + DN_Str8Lit("abc"), + DN_Str8Lit(""), + DN_Str8Lit("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), + DN_Str8Lit("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"), + }; + + for (DN_ForItCArray(it, DN_Str8 const, INPUTS)) { + for (DN_TestScopeF(&result, "[%.*s] %.*s", DN_Str8PrintFmt(hash_name), DN_Str8PrintFmt(*it.data))) + DN_SHA3TestHashDispatch_(&result, DN_Cast(DN_SHA3TestHash)hash_type, *it.data); + } + } + + // NOTE: Test SHA3 against some deterministic inputs generated from a PRNG + for (DN_TestScopeF(&result, "[%.*s] Deterministic random inputs", DN_Str8PrintFmt(hash_name))) { + DN_PCG32 rng = DN_PCG32Init(0xd48e'be21'2af8'733d); + for (DN_USize index = 0; index < 128; index++) { + // NOTE: Create a 4kb buffer with the deterministic contents + char src[4096] = {}; + DN_U32 src_size = DN_PCG32Range(&rng, 0, sizeof(src)); + for (DN_USize src_index = 0; src_index < src_size; src_index++) + src[src_index] = DN_Cast(char) DN_PCG32Range(&rng, 0, 255); + + // NOTE: Do the hashing + DN_Str8 input = DN_Str8FromPtr(src, src_size); + DN_SHA3TestHashDispatch_(&result, DN_Cast(DN_SHA3TestHash)hash_type, input); + } + } + } + } + return result; +} + +void DN_SHA3_TestSuiteThenOutput(DN_Str8FromTestCoreFlags flags) +{ + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitDefault()); + DN_TestCore test = DN_SHA3_TestSuite(&arena); + DN_Str8 output = DN_Str8FromTestCore(&test, &arena, flags); + printf("%.*s", DN_Str8PrintFmt(output)); + DN_ArenaDeinit(&arena); +} +#endif // #if defined(DN_SHA3_WITH_TESTS) +#endif // #if defined(DN_SHA3_IMPLEMENTATION) diff --git a/Source/Standalone/dn_utest.h b/Source/Standalone/dn_utest.h deleted file mode 100644 index 200eaea..0000000 --- a/Source/Standalone/dn_utest.h +++ /dev/null @@ -1,333 +0,0 @@ -#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, ...) \ - 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; - -DN_UTCore DN_UT_Init(); -void DN_UT_Deinit(DN_UTCore *ut); - -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/Source/dn.cpp b/Source/dn.cpp index 2bd10a2..68b2c8a 100644 --- a/Source/dn.cpp +++ b/Source/dn.cpp @@ -1,8 +1,9 @@ #if defined(_CLANGD) - #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 + #define DN_WITH_TESTS 1 #define DN_WITH_OS 1 #define DN_WITH_NET 1 #define DN_WITH_NET_CURL 1 + #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 #include "dn.h" #endif @@ -16,7 +17,6 @@ enum DN_ArenaUAFCheckReportType_ DN_ArenaUAFCheckReportType_TempEndOutOfOrder, }; - DN_Core *g_dn_; DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) @@ -50,7 +50,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) } { - os->mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_MemFuncsDefault()); + os->mem = DN_MemListFromHeap(DN_Megabytes(1), DN_Kilobytes(4), DN_MemFlags_NoAllocTrack, DN_OS_HeapInitDefault()); os->arena = DN_ArenaFromMemList(&os->mem); #if defined(DN_PLATFORM_WIN32) @@ -91,7 +91,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #endif // NOTE: Initialise thread context - DN_TCInitFromMemFuncs(&dn->main_tc, DN_OS_ThreadID(), args, DN_MemFuncsDefault()); + DN_TCInitFromHeap(&dn->main_tc, DN_OS_ThreadID(), args, DN_OS_HeapInitDefault()); DN_TCEquip(&dn->main_tc); } @@ -100,9 +100,8 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) #if DN_WITH_OS // NOTE: Setup the allocation table with allocation tracking turned off on // the arena we're using to initialise the table. - dn->leak.alloc_table_mem = DN_MemListFromMemFuncs(DN_Megabytes(1), DN_Kilobytes(512), DN_MemFlags_NoAllocTrack | DN_MemFlags_AllocCanLeak, DN_MemFuncsDefault()); - dn->leak.alloc_table_arena = DN_ArenaFromMemList(&dn->leak.alloc_table_mem); - dn->leak.alloc_table = DN_DSMapInit(&dn->leak.alloc_table_arena, 4096, DN_DSMapFlags_Nil); + DN_HTableInitArgs table_args = DN_HTableInitArgsDefault(DN_LeakTrackerKV, dn->leak.alloc_table_kvs, hash, key, value); + dn->leak.alloc_table = DN_HTableInitHeapAssert(table_args, DN_OS_HeapInitBasic()); #endif } @@ -126,12 +125,12 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " Thread Context: "); if (DN_BitIsSet(flags, DN_InitFlags_OS)) { - DN_Arena *arena = dn->main_tc.main_arena; - DN_Str8 mem_funcs = DN_Str8Lit(""); - switch (arena->mem->funcs.type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: mem_funcs = DN_Str8Lit("Heap"); break; - case DN_MemFuncsType_Virtual: mem_funcs = DN_Str8Lit("Virtual"); break; + DN_Arena *arena = dn->main_tc.main_arena; + DN_Str8 heap = DN_Str8Lit(""); + switch (arena->mem->heap.type) { + case DN_HeapType_Nil: break; + case DN_HeapType_Basic: heap = DN_Str8Lit("Basic"); break; + case DN_HeapType_Virtual: heap = DN_Str8Lit("Virtual"); break; } DN_Str8x32 main_commit = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->commit); DN_Str8x32 main_reserve = DN_Str8x32FromByteCountU64Auto(dn->main_tc.main_arena->mem->curr->reserve); @@ -144,7 +143,7 @@ DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args) DN_Str8x32 temp_reserve = DN_Str8x32FromByteCountU64Auto(temp->mem->curr->reserve); DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " T(x%zu) %.*s/%.*s", dn->main_tc.temp_arenas_count, DN_Str8PrintFmt(temp_commit), DN_Str8PrintFmt(temp_reserve)); } - DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(mem_funcs)); + DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), " E %.*s/%.*s (%.*s)\n", DN_Str8PrintFmt(err_commit), DN_Str8PrintFmt(err_reserve), DN_Str8PrintFmt(heap)); } else { DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "N/A\n"); } @@ -241,12 +240,10 @@ DN_API bool DN_VerifyArgsF(DN_VerifyType type, bool expr, DN_CallSite call_site, } // NOTE: Log stack trace - if (DN_PARANOIA_LEVEL) { - if (type == DN_VerifyType_Nil) { - DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); - DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); - DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); - } + if (type == DN_VerifyType_Nil && DN_PARANOIA_LEVEL) { + DN_Str8 trace = DN_Str8FromStackTraceNowArena(&scratch.arena, 128 /*limit*/, 4 /*skip*/); + DN_Str8BuilderAppendF(&builder, "\nTrace:\n "); + DN_Str8BuilderAppendRef(&builder, DN_Str8PadNewLinesArena(trace, DN_Str8Lit(" "), &scratch.arena)); } DN_Str8 log = DN_Str8FromStr8BuilderArena(&builder, &scratch.arena); @@ -1067,16 +1064,16 @@ DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) return result; } -static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_MemFuncs mem_funcs) +static DN_MemBlock *DN_MemBlockFromHeap_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_Heap heap) { DN_MemBlock *result = nullptr; - switch (mem_funcs.type) { - case DN_MemFuncsType_Nil: + switch (heap.type) { + case DN_HeapType_Nil: break; - case DN_MemFuncsType_Heap: { + case DN_HeapType_Basic: { DN_AssertF(reserve > DN_ARENA_HEADER_SIZE, "%I64u > %I64u", reserve, DN_ARENA_HEADER_SIZE); - result = DN_Cast(DN_MemBlock *) mem_funcs.heap_alloc(reserve); + result = DN_Cast(DN_MemBlock *) heap.basic_alloc(reserve); if (!result) return result; @@ -1085,24 +1082,27 @@ static DN_MemBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bo result->reserve = reserve; } break; - case DN_MemFuncsType_Virtual: { - DN_AssertF(mem_funcs.virtual_page_size, "Page size must be set to a non-zero, power of two value"); - DN_Assert(DN_IsPowerOfTwo(mem_funcs.virtual_page_size)); + case DN_HeapType_Virtual: { + DN_AssertF(heap.virtual_page_size, + "Page size must be set to a non-zero, power of two value. Virtual memory " + "functions are usually initialised by values obtained during initialisation, has " + "DN_Init() been called yet?"); + DN_Assert(DN_IsPowerOfTwo(heap.virtual_page_size)); - DN_USize const page_size = mem_funcs.virtual_page_size; + DN_USize const page_size = heap.virtual_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(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%zu < %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_MemBlock *) mem_funcs.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + result = DN_Cast(DN_MemBlock *) heap.virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); if (!result) return result; - if (mem_commit == DN_MemCommit_No && !mem_funcs.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { - mem_funcs.virtual_release(result, real_reserve); + if (mem_commit == DN_MemCommit_No && !heap.virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { + heap.virtual_release(result, real_reserve); return result; } @@ -1127,13 +1127,16 @@ static bool DN_ArenaHasPoison_(DN_MemFlags flags) return result; } -static DN_MemBlock *DN_MemBlockFromMemFuncsFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +static DN_MemBlock *DN_MemBlockFromHeapFlags_(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) { bool track_alloc = (flags & DN_MemFlags_NoAllocTrack) == 0; bool alloc_can_leak = flags & DN_MemFlags_AllocCanLeak; - DN_MemBlock *result = DN_ArenaBlockFromMemFuncs_(reserve, commit, track_alloc, alloc_can_leak, mem_funcs); - if (result && DN_ArenaHasPoison_(flags)) - DN_ASanPoisonMemoryRegion(DN_Cast(char *) result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE); + DN_MemBlock *result = DN_MemBlockFromHeap_(reserve, commit, track_alloc, alloc_can_leak, heap); + if (result && DN_ArenaHasPoison_(flags)) { + char *poison = DN_Cast(char *)result + result->used; + DN_USize poison_size = result->commit - result->used; + DN_ASanPoisonMemoryRegion(poison, poison_size); + } return result; } @@ -1178,6 +1181,77 @@ DN_API DN_MemStats DN_MemStatsSumArray(DN_MemStats const *array, DN_USize count) return result; } +DN_API DN_Heap DN_HeapInitBasic(DN_HeapBasicAllocFunc *basic_alloc, DN_HeapBasicDeallocFunc *basic_dealloc) +{ + DN_Heap result = {}; + result.type = DN_HeapType_Basic; + result.basic_alloc = basic_alloc; + result.basic_dealloc = basic_dealloc; + return result; +} + +DN_API DN_Heap DN_HeapInitVirtual(DN_U32 page_size, DN_HeapVirtualReserveFunc *virtual_reserve, DN_HeapVirtualCommitFunc *virtual_commit, DN_HeapVirtualReleaseFunc *virtual_release) +{ + DN_Heap result = {}; + result.type = DN_HeapType_Virtual; + result.virtual_page_size = page_size; + result.virtual_reserve = virtual_reserve; + result.virtual_commit = virtual_commit; + result.virtual_release = virtual_release; + return result; +} + +DN_API void* DN_HeapAlloc(DN_Heap *heap, DN_USize reserve, DN_USize commit) +{ + void *result = nullptr; + switch (heap->type) { + case DN_HeapType_Nil: + break; + + case DN_HeapType_Basic: result = heap->basic_alloc(reserve); break; + case DN_HeapType_Virtual: { + DN_AssertF(heap->virtual_page_size, + "Page size must be set to a non-zero, power of two value. Virtual memory " + "functions are usually initialised by values obtained during initialisation, has " + "DN_Init() been called yet?"); + DN_Assert(DN_IsPowerOfTwo(heap->virtual_page_size)); + + DN_USize const page_size = heap->virtual_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, "%zu < %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_MemBlock *) heap->virtual_reserve(real_reserve, mem_commit, DN_MemPage_ReadWrite); + if (!result) + return result; + + if (mem_commit == DN_MemCommit_No && !heap->virtual_commit(result, real_commit, DN_MemPage_ReadWrite)) { + heap->virtual_release(result, real_reserve); + return result; + } + } break; + } + + if (result) { + heap->bytes_alloc += reserve; + heap->bytes_alloc_total += reserve; + } + return result; +} + +DN_API void DN_HeapDealloc(DN_Heap *heap, void *ptr, DN_USize size) +{ + heap->bytes_alloc -= size; + heap->bytes_freed += size; + if (heap->type == DN_HeapType_Basic) + heap->basic_dealloc(ptr); + else + heap->virtual_release(ptr, size); +} + DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags flags) { DN_Assert(buffer); @@ -1199,17 +1273,17 @@ DN_API DN_MemList DN_MemListFromBuffer(void *buffer, DN_USize size, DN_MemFlags return result; } -DN_API DN_MemList DN_MemListFromMemFuncs(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs) +DN_API DN_MemList DN_MemListFromHeap(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) { DN_MemList result = {}; - result.funcs = mem_funcs; - result.flags |= flags | DN_MemFlags_MemFuncs; - result.curr = DN_MemBlockFromMemFuncsFlags_(reserve, commit, flags, mem_funcs); + result.heap = heap; + result.flags |= flags | DN_MemFlags_Heap; + result.curr = DN_MemBlockFromHeapFlags_(reserve, commit, flags, heap); DN_MemListOnNewBlock_(&result, result.curr); return result; } -static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) +static void DN_MemBlockDeinit_(DN_MemList *mem, DN_MemBlock *block) { DN_USize release_size = block->reserve; if (DN_BitIsNotSet(mem->flags, DN_MemFlags_NoAllocTrack)) @@ -1218,12 +1292,8 @@ static void DN_MemBlockDeinit_(DN_MemList const *mem, DN_MemBlock *block) if (DN_ArenaHasPoison_(mem->flags)) DN_ASanUnpoisonMemoryRegion(block, block->commit); - if (mem->flags & DN_MemFlags_MemFuncs) { - if (mem->funcs.type == DN_MemFuncsType_Heap) - mem->funcs.heap_dealloc(block); - else - mem->funcs.virtual_release(block, release_size); - } + if (mem->flags & DN_MemFlags_Heap) + DN_HeapDealloc(&mem->heap, block, release_size); } DN_API void DN_MemListDeinit(DN_MemList *mem) @@ -1256,11 +1326,11 @@ DN_API bool DN_MemListCommitTo(DN_MemList *mem, DN_U64 pos) } // NOTE: Do the commit - DN_Assert(mem->funcs.virtual_page_size); - DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->funcs.virtual_page_size); + DN_Assert(mem->heap.virtual_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(real_pos, mem->heap.virtual_page_size); DN_USize commit_size = end_commit - curr->commit; char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + if (!mem->heap.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) return false; if (DN_ArenaHasPoison_(mem->flags)) @@ -1285,7 +1355,7 @@ DN_API bool DN_MemListGrow(DN_MemList *mem, DN_U64 reserve, DN_U64 commit) return false; bool result = false; - DN_MemBlock *new_block = DN_MemBlockFromMemFuncsFlags_(reserve, commit, mem->flags, mem->funcs); + DN_MemBlock *new_block = DN_MemBlockFromHeapFlags_(reserve, commit, mem->flags, mem->heap); if (new_block) { result = true; new_block->prev = mem->curr; @@ -1302,7 +1372,7 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem return nullptr; if (!mem->curr) { - mem->curr = DN_MemBlockFromMemFuncsFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->funcs); + mem->curr = DN_MemBlockFromHeapFlags_(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, mem->flags, mem->heap); DN_MemListOnNewBlock_(mem, mem->curr); } @@ -1327,15 +1397,14 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem goto try_alloc_again; } - DN_USize prev_arena_commit = curr->commit; if (end_pos > curr->commit) { - DN_Assert(mem->funcs.virtual_page_size); - DN_Assert(mem->funcs.type == DN_MemFuncsType_Virtual); + DN_Assert(mem->heap.virtual_page_size); + DN_Assert(mem->heap.type == DN_HeapType_Virtual); DN_Assert((mem->flags & DN_MemFlags_UserBuffer) == 0); - DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->funcs.virtual_page_size); + DN_USize end_commit = DN_AlignUpPowerOfTwo(end_pos, mem->heap.virtual_page_size); DN_USize commit_size = end_commit - curr->commit; char *commit_ptr = DN_Cast(char *) curr + curr->commit; - if (!mem->funcs.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) + if (!mem->heap.virtual_commit(commit_ptr, commit_size, DN_MemPage_ReadWrite)) return nullptr; if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) DN_ASanPoisonMemoryRegion(commit_ptr, commit_size); @@ -1352,10 +1421,8 @@ DN_API void *DN_MemListAlloc(DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem if (poison && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) DN_ASanUnpoisonMemoryRegion(result, size); - if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) { - DN_USize reused_bytes = DN_Min(prev_arena_commit - offset_pos, size); - DN_Memset(result, 0, reused_bytes); - } + if (z_mem == DN_ZMem_Yes && DN_BitIsNotSet(mem->flags, DN_MemFlags_SimAlloc)) + DN_Memset(result, 0, size); DN_Assert(mem->stats.hwm.used >= mem->stats.info.used); DN_Assert(mem->stats.hwm.commit >= mem->stats.info.commit); @@ -1544,7 +1611,7 @@ static void DN_ArenaUAFCheck_(DN_Arena *arena, DN_ArenaUAFCheckReportType_ type) DN_Str8 error_msg = {}; if (type == DN_ArenaUAFCheckReportType_AllocViolation) { error_msg = DN_Str8FromFmtAllocator(allocator, - "Arena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " + "\n\nArena use-after-free (UAF) detected in temporary memory usage! This allocation (trace " "shown above) is attempting to allocate memory inside the active temporary region (id: %'u) " "but belongs to a different region (id: %'u). This means when the active temporary region is " "released, this allocation will be released and scrubbed causing a potential UAF.\n\nEnsure " @@ -1602,6 +1669,15 @@ DN_API DN_Arena DN_ArenaFromMemList(DN_MemList *mem) return result; } +DN_API DN_Arena DN_ArenaFromHeap(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap) +{ + DN_MemList mem = DN_MemListFromHeap(reserve, commit, flags, heap); + DN_Arena result = {}; + result.flags |= DN_ArenaFlags_OwnsMemList; + result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); + return result; +} + DN_API DN_Arena DN_ArenaTempBeginFromMemList(DN_MemList* mem) { DN_Arena result = DN_ArenaFromMemList(mem); @@ -1684,6 +1760,12 @@ DN_API void *DN_ArenaCopy(DN_Arena *arena, void const *data, DN_U64 size, DN_U8 return result; } +DN_API void DN_ArenaDeinit(DN_Arena *arena) +{ + if (arena->flags & DN_ArenaFlags_OwnsMemList) + DN_MemListDeinit(arena->mem); +} + DN_API DN_Pool DN_PoolFromArena(DN_Arena *arena, DN_U8 align) { DN_Pool result = {}; @@ -2062,15 +2144,15 @@ DN_API DN_TCInitArgs DN_TCInitArgsDefault() return result; } -DN_API void DN_TCInitFromMemFuncs(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs) +DN_API void DN_TCInitFromHeap(DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_Heap heap) { DN_Assert(args.temp_count <= DN_ArrayCountU(tc->temp_arenas)); - tc->main_arena_mem_ = DN_MemListFromMemFuncs(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->main_arena_mem_ = DN_MemListFromHeap(args.main_reserve, args.main_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); for (DN_ForIndexU(index, args.temp_count)) { - tc->temp_arena_mems_[index] = DN_MemListFromMemFuncs(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->temp_arena_mems_[index] = DN_MemListFromHeap(args.temp_reserve, args.temp_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); tc->temp_arenas_[index] = DN_ArenaFromMemList(&tc->temp_arena_mems_[index]); } - tc->err_sink_arena_mem_ = DN_MemListFromMemFuncs(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, mem_funcs); + tc->err_sink_arena_mem_ = DN_MemListFromHeap(args.err_sink_reserve, args.err_sink_commit, DN_MemFlags_AllocCanLeak | DN_MemFlags_NoAllocTrack, heap); tc->main_arena_ = DN_ArenaFromMemList(&tc->main_arena_mem_); tc->err_sink_arena_ = DN_ArenaFromMemList(&tc->err_sink_arena_mem_); DN_TCInit(tc, thread_id, &tc->main_arena_, tc->temp_arenas_, args.temp_count, &tc->err_sink_arena_); @@ -2313,7 +2395,7 @@ DN_API char DN_CharToUpper(char ch) return result; } -DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) +DN_API DN_U64FromResult DN_U64FromStr8Delimiters(DN_Str8 string, DN_Str8 const *delimiters, DN_USize delimiters_count) { // NOTE: Argument check DN_U64FromResult result = {}; @@ -2339,12 +2421,25 @@ DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) // NOTE: Convert the string number to the binary number for (DN_USize index = start_index; index < trim_string.count; index++) { - char ch = trim_string.data[index]; + + // NOTE: Check for presence of the delimiter, we skip the first character as a U64 string + // prefixed with a delimiter is not considered valid. if (index) { - if (separator != 0 && ch == separator) + DN_USize max_delimiter_match = 0; + for (DN_ForItSize(it, DN_Str8 const, delimiters, delimiters_count)) { + DN_Str8 delimiter = *it.data; + DN_Str8 check_slice = DN_Str8Subset(trim_string, index, delimiter.count); + if (DN_Str8EqSensitive(check_slice, delimiter)) + max_delimiter_match = DN_Max(max_delimiter_match, delimiter.count); // Take length, there might be multiple so we keep going + } + + if (max_delimiter_match) { + index += max_delimiter_match - 1; continue; + } } + char ch = trim_string.data[index]; if (!DN_CharIsDigit(ch)) return result; @@ -2357,18 +2452,48 @@ DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string, char separator) return result; } -DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size, char separator) +DN_API DN_U64FromResult DN_U64FromStr8Delimiter(DN_Str8 string, DN_Str8 delimiter) { - DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); - DN_U64FromResult result = DN_U64FromStr8(str8, separator); + DN_U64FromResult result = DN_U64FromStr8Delimiters(string, &delimiter, 1); return result; } -DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size, char separator) +DN_API DN_U64FromResult DN_U64FromStr8(DN_Str8 string) { - DN_U64FromResult from = DN_U64FromPtr(data, size, separator); + DN_U64FromResult result = DN_U64FromStr8Delimiters(string, nullptr, 0); + return result; +} + +DN_API DN_U64FromResult DN_U64FromPtrDelimiter(void const *data, DN_USize size, DN_Str8 delimiter) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_U64FromResult result = DN_U64FromStr8Delimiter(str8, delimiter); + return result; +} + + +DN_API DN_U64FromResult DN_U64FromPtr(void const *data, DN_USize size) +{ + DN_Str8 str8 = DN_Str8FromPtr((char *)data, size); + DN_U64FromResult result = DN_U64FromStr8Delimiters(str8, nullptr, 0); + return result; +} + +DN_API DN_U64 DN_U64FromPtrUnsafeDelimiter(void const *data, DN_USize size, DN_Str8 delimiter) +{ + DN_U64FromResult from = DN_U64FromPtrDelimiter(data, size, delimiter); DN_U64 result = from.value; - DN_Assert(from.success); + DN_VerifyWarning(from.success); + return result; +} + + +DN_API DN_U64 DN_U64FromPtrUnsafe(void const *data, DN_USize size) +{ + DN_Str8 str8 = DN_Str8FromPtr(data, size); + DN_U64FromResult from = DN_U64FromStr8Delimiters(str8, nullptr, 0); + DN_U64 result = from.value; + DN_VerifyWarning(from.success); return result; } @@ -3130,7 +3255,7 @@ DN_API DN_Str8BSplitResult DN_Str8BSplitArray(DN_Str8 string, DN_Str8 const *fin for (DN_USize find_index = 0; find_index < find_size; find_index++) { DN_Str8 find_item = find[find_index]; DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.count); - if (DN_Str8Eq(string_slice, find_item)) { + if (DN_Str8EqSensitive(string_slice, find_item)) { result.input_index = find_index; result.lhs.count = index; result.rhs.data = string_slice.data + find_item.count; @@ -3160,7 +3285,7 @@ DN_API DN_Str8BSplitResult DN_Str8BSplitLastArray(DN_Str8 string, DN_Str8 const for (DN_USize find_index = 0; find_index < find_size; find_index++) { DN_Str8 find_item = find[find_index]; DN_Str8 string_slice = DN_Str8Subset(string, index, find_item.count); - if (DN_Str8Eq(string_slice, find_item)) { + if (DN_Str8EqSensitive(string_slice, find_item)) { result.lhs.count = index; result.rhs.data = string_slice.data + find_item.count; result.rhs.count = string.count - (index + find_item.count); @@ -3189,7 +3314,7 @@ DN_API DN_USize DN_Str8Split(DN_Str8 string, DN_Str8 delimiter, DN_Str8 *splits, bool handle_quotes = DN_BitIsSet(flags, DN_Str8SplitFlags_HandleQuotedStrings); do { DN_Str8 item = {}; - if (handle_quotes && DN_Str8StartsWith(it, DN_Str8Lit("\""))) { + if (handle_quotes && DN_Str8StartsWithSensitive(it, DN_Str8Lit("\""))) { DN_Str8FindResult find = DN_Str8FindStr8(DN_Str8Advance(it, 1), DN_Str8Lit("\""), DN_Str8EqCase_Sensitive); DN_Assert(find.found); item = find.start_to_before_match; @@ -3333,7 +3458,7 @@ DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) bool result = true; switch (eq_case) { case DN_Str8EqCase_Sensitive: { - result = (DN_Memcmp(lhs.data, rhs.data, lhs.count) == 0); + result = DN_MemEqUnsafe(lhs.data, rhs.data, lhs.count); } break; case DN_Str8EqCase_Insensitive: { @@ -3344,6 +3469,12 @@ DN_API bool DN_Str8Eq(DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case) return result; } +DN_API bool DN_Str8EqSensitive(DN_Str8 lhs, DN_Str8 rhs) +{ + bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Sensitive); + return result; +} + DN_API bool DN_Str8EqInsensitive(DN_Str8 lhs, DN_Str8 rhs) { bool result = DN_Str8Eq(lhs, rhs, DN_Str8EqCase_Insensitive); @@ -3357,6 +3488,13 @@ DN_API bool DN_Str8StartsWith(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_c return result; } +DN_API bool DN_Str8StartsWithSensitive(DN_Str8 string, DN_Str8 prefix) +{ + bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + + DN_API bool DN_Str8StartsWithInsensitive(DN_Str8 string, DN_Str8 prefix) { bool result = DN_Str8StartsWith(string, prefix, DN_Str8EqCase_Insensitive); @@ -3370,6 +3508,12 @@ DN_API bool DN_Str8EndsWith(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_cas return result; } +DN_API bool DN_Str8EndsWithSensitive(DN_Str8 string, DN_Str8 suffix) +{ + bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Sensitive); + return result; +} + DN_API bool DN_Str8EndsWithInsensitive(DN_Str8 string, DN_Str8 suffix) { bool result = DN_Str8EndsWith(string, suffix, DN_Str8EqCase_Insensitive); @@ -3394,6 +3538,18 @@ DN_API DN_Str8 DN_Str8TrimPrefix(DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase e return result; } +DN_API DN_Str8 DN_Str8TrimPrefixSensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimPrefixInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + DN_API DN_Str8 DN_Str8TrimHexPrefix(DN_Str8 string) { DN_Str8 result = DN_Str8TrimPrefix(string, DN_Str8Lit("0x"), DN_Str8EqCase_Insensitive); @@ -3408,10 +3564,34 @@ DN_API DN_Str8 DN_Str8TrimSuffix(DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase e return result; } -DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string) +DN_API DN_Str8 DN_Str8TrimSuffixSensitive(DN_Str8 string, DN_Str8 prefix) { - DN_Str8 result = DN_Str8TrimPrefix(string, trim_string); - result = DN_Str8TrimSuffix(result, trim_string); + DN_Str8 result = DN_Str8TrimSuffix(string, prefix, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimSuffixInsensitive(DN_Str8 string, DN_Str8 prefix) +{ + DN_Str8 result = DN_Str8TrimSuffix(string, prefix, DN_Str8EqCase_Insensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAround(DN_Str8 string, DN_Str8 trim_string, DN_Str8EqCase eq_case) +{ + DN_Str8 result = DN_Str8TrimPrefix(string, trim_string, eq_case); + result = DN_Str8TrimSuffix(result, trim_string, eq_case); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAroundSensitive(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8TrimAround(string, trim_string, DN_Str8EqCase_Sensitive); + return result; +} + +DN_API DN_Str8 DN_Str8TrimAroundInsensitive(DN_Str8 string, DN_Str8 trim_string) +{ + DN_Str8 result = DN_Str8TrimAround(string, trim_string, DN_Str8EqCase_Insensitive); return result; } @@ -3867,7 +4047,7 @@ DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num if (!print_row_line) print_row_line = DN_BitIsSet(flags, DN_Str8TableFlags_RowLines); - if (print_row_line) { + if (print_row_line && (i != num_rows - 1)) { DN_Str8BuilderAppendF(&builder, "+"); for (DN_USize sub_i = 0; sub_i < num_cols; sub_i++) { for (DN_USize sub_j = 0; sub_j < col_widths[sub_i] + 2; sub_j++) @@ -3890,7 +4070,7 @@ DN_API DN_Str8 DN_Str8Table(DN_Str8 const *rows, DN_USize num_rows, DN_USize num return result; } -#if DN_STR8_AVX512F +#if DN_WITH_STR8_AVX512F DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F(DN_Str8 string, DN_Str8 find) { // NOTE: Algorithm as described in http://0x80.pl/articles/simd-strfind.html @@ -4567,7 +4747,7 @@ DN_API bool DN_Str8BuilderPrependF(DN_Str8Builder *builder, DN_FMT_ATTRIB char c DN_API bool DN_Str8BuilderErase(DN_Str8Builder *builder, DN_Str8 string) { for (DN_Str8Link **it = &builder->head; *it; it = &((*it)->next)) { - if (DN_Str8Eq((*it)->string, string)) { + if (DN_Str8EqSensitive((*it)->string, string)) { *it = (*it)->next; builder->string_size -= string.count; builder->count -= 1; @@ -7986,7 +8166,7 @@ DN_API DN_ArrayEraseResult DN_ArrayEraseRange(void *data, DN_USize *count, DN_US *count -= real_erase_count; } - result.items_erased = erase_count; + result.items_erased = real_erase_count; // NOTE: If we are erasing from the current index of the iterator to the end of the array then // there's no more elements in the array to iterate. So the returned index should b // one-past-last index @@ -8044,7 +8224,7 @@ DN_API void *DN_ArrayAddArrayAssert(void *data, DN_USize *count, DN_USize max, D DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); if (buffer) { @@ -8052,8 +8232,9 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_Memcpy(buffer, *data, bytes_to_copy); DN_PoolDealloc(pool, *data); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8065,7 +8246,7 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_PoolNewArray(pool, DN_U8, bytes_to_alloc); if (buffer) { @@ -8073,8 +8254,9 @@ DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_Memcpy(buffer, *data, bytes_to_copy); DN_PoolDealloc(pool, *data); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8086,15 +8268,16 @@ DN_API bool DN_ArrayResizeFromPool(void **data, DN_USize *count, DN_USize *max, DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) { bool result = true; - if (new_max != *max) { + if (!max || new_max != *max) { DN_USize bytes_to_alloc = elem_size * new_max; void *buffer = DN_ArenaNewArray(arena, DN_U8, bytes_to_alloc, DN_ZMem_No); if (buffer) { DN_USize bytes_to_copy = elem_size * DN_Min(*count, new_max); DN_Memcpy(buffer, *data, bytes_to_copy); *data = buffer; - *max = new_max; *count = DN_Min(*count, new_max); + if (max) + *max = new_max; } else { result = false; } @@ -8103,23 +8286,26 @@ DN_API bool DN_ArrayResizeFromArena(void **data, DN_USize *count, DN_USize *max, return result; } -DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) +DN_API bool DN_ArrayGrowFromPool(void **data, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max) { bool result = true; - if (new_max > *max) + if (!max || new_max > *max) { + DN_USize count = 0; result = DN_ArrayResizeFromPool(data, &count, max, elem_size, pool, new_max); + } return result; } -DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) +DN_API bool DN_ArrayGrowFromArena(void **data, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max) { bool result = true; - if (new_max > *max) + if (!max || new_max > *max) { + DN_USize count = 0; result = DN_ArrayResizeFromArena(data, &count, max, elem_size, arena, new_max); + } return result; } - DN_API bool DN_ArrayGrowIfNeededFromPool(void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count) { bool result = true; @@ -8142,6 +8328,36 @@ DN_API bool DN_ArrayGrowIfNeededFromArena(void **data, DN_USize count, DN_USize return result; } +DN_API DN_USize DN_ArrayCopyPtrArena(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena) +{ + DN_USize result = 0; + if (DN_ArrayGrowFromArena(data, /*max=*/ &result, elem_size, arena, /*new_max=*/ count)) + DN_Memcpy(*data, src, elem_size * count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrArenaAssert(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrArena(data, src, count, elem_size, arena); + DN_AssertCallSiteF(result == count, call_site, "Array copy failed, failed to allocate: %zu items", count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrPool(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool) +{ + DN_USize result = 0; + if (DN_ArrayGrowFromPool(data, /*max=*/ &result, elem_size, pool, /*new_max=*/ count)) + DN_Memcpy(*data, src, elem_size * count); + return result; +} + +DN_API DN_USize DN_ArrayCopyPtrPoolAssert(void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrPool(data, src, count, elem_size, pool); + DN_AssertCallSiteF(result == count, call_site, "Array copy failed, failed to allocate: %zu items", count); + return result; +} + DN_API void *DN_SinglyLLDetach(void **link, void **next) { void *result = *link; @@ -8169,13 +8385,16 @@ DN_API bool DN_RingHasData(DN_Ring const *ring, DN_U64 size) DN_API void DN_RingWrite(DN_Ring *ring, void const *src, DN_U64 src_size) { - DN_Assert(src_size <= ring->size); + DN_AssertF(src_size <= ring->size, + "Payload to write (%s) to ring exceeds the capacity of the ring (%s)", + DN_Str8x32FromByteCountU64Auto(src_size).data, + DN_Str8x32FromByteCountU64Auto(ring->size).data); DN_U64 offset = ring->write_pos % ring->size; DN_U64 bytes_before_split = ring->size - offset; DN_U64 pre_split_bytes = DN_Min(bytes_before_split, src_size); DN_U64 post_split_bytes = src_size - pre_split_bytes; void const *pre_split_data = src; - void const *post_split_data = ((char *)src + pre_split_bytes); + void const *post_split_data = (DN_Cast(char *)src + pre_split_bytes); DN_Memcpy(ring->base + offset, pre_split_data, pre_split_bytes); DN_Memcpy(ring->base, post_split_data, post_split_bytes); ring->write_pos += src_size; @@ -8193,476 +8412,334 @@ DN_API void DN_RingRead(DN_Ring *ring, void *dest, DN_U64 dest_size) ring->read_pos += dest_size; } -#if defined(__cplusplus) -template -DN_DSMap DN_DSMapInit(DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags) +DN_API DN_U32 DN_HTableHashFuncDefault(void const *key, DN_USize size) { - DN_AssertF(DN_IsPowerOfTwo(size), "Power-of-two size required, given size was '%u'", size); - DN_Assert(arena); - - DN_DSMap result = {}; - if (size <= 0) - return result; - if (!arena) - return result; - - result.arena = arena; - result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); - result.hash_to_slot = DN_ArenaNewArray(result.arena, DN_U32, size, DN_ZMem_Yes); - result.slots = DN_ArenaNewArray(result.arena, DN_DSMapSlot, size, DN_ZMem_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?"); + DN_U32 const DEFAULT_SEED = 0xb255b383; + DN_U32 result = DN_MurmurHash3HashU32FromBytes(key, DN_Cast(int)size, DEFAULT_SEED); return result; } -template -void DN_DSMapDeinit(DN_DSMap *map, DN_ZMem z_mem) +DN_API bool DN_HTableKeyEqFuncMemcmp(void const *lhs, void const *rhs, DN_USize size) { - if (!map) - return; - // TODO(doyle): Use z_mem - (void)z_mem; - DN_MemListDeinit(map->arena->mem); - *map = {}; -} - -template -bool DN_DSMapIsValid(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 + bool result = DN_Memcmp(lhs, rhs, size) == 0; return result; } -template -DN_U32 DN_DSMapHash(DN_DSMap const *map, DN_DSMapKey key) +DN_API bool DN_HTableKeyEqFuncStr8Eq(void const *lhs, void const *rhs, DN_USize) { - DN_U32 result = 0; - if (!map) - return result; + DN_Str8 lhs_str8 = *DN_Cast(DN_Str8*) lhs; + DN_Str8 rhs_str8 = *DN_Cast(DN_Str8*) rhs; + bool result = DN_Str8EqSensitive(lhs_str8, rhs_str8); + return result; +} - if (key.type == DN_DSMapKeyType_U64NoHash) { - result = DN_Cast(DN_U32) key.u64; - return result; - } +DN_API DN_HTableInitArgs DN_HTableInitArgsDefault_(void** kvs, DN_USize size_of_kv, DN_USize offset_of_hash, DN_USize size_of_key, DN_USize offset_of_key, DN_USize size_of_value, DN_USize offset_of_value) +{ + DN_HTableInitArgs result = {}; + DN_AssertF((*kvs) == 0, "The key-value pointer must be null as the hash table will allocate the objects and keep the pointer in sync with the table for you: %p", *kvs); + result.hash_func = DN_HTableHashFuncDefault; + result.key_eq_func = DN_HTableKeyEqFuncMemcmp; + result.load_factor = 0.7f; + result.max = 0; + result.kvs = kvs; + result.size_of_kv = size_of_kv; + result.offset_of_key = offset_of_key; + result.offset_of_hash = offset_of_hash; + result.size_of_key = size_of_key; + result.offset_of_value = offset_of_value; + result.size_of_value = size_of_value; + return result; +} - if (key.type == DN_DSMapKeyType_BufferAsU64NoHash) { - result = key.hash; - return result; - } +DN_API DN_HTable DN_HTableInit_(DN_HTableInitArgs args) +{ + DN_HTable result = {}; + result.load_factor01 = args.load_factor; + result.hash_func = args.hash_func; + result.key_eq_func = args.key_eq_func; + result.kvs = args.kvs; + result.size_of_kv = args.size_of_kv; + result.offset_of_key = args.offset_of_key; + result.offset_of_hash = args.offset_of_hash; + result.size_of_key = args.size_of_key; + result.offset_of_value = args.offset_of_value; + result.size_of_value = args.size_of_value; + 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); +DN_API DN_HTableInitResult DN_HTableInitHeap(DN_HTableInitArgs args, DN_Heap heap) +{ + DN_HTableInitResult result = {}; + result.table = DN_HTableInit_(args); + result.table.heap = heap; + result.table.flags |= DN_HTableFlags_UsingHeap; + result.success = DN_HTableResize(&result.table, args.max); + return result; +} + +DN_API DN_HTable DN_HTableInitHeapAssert(DN_HTableInitArgs args, DN_Heap heap) +{ + DN_HTableInitResult init = DN_HTableInitHeap(args, heap); + DN_HTable result = init.table; + DN_Assert(init.success); + return result; +} + +DN_API DN_HTableInitResult DN_HTableInitPool(DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool) +{ + DN_HTableInitResult result = {}; + result.table = DN_HTableInit_(args); + result.table.pool = pool; + result.success = DN_HTableResize(&result.table, args.max); + if (dealloc_pool == DN_HTableDeallocPoolOnDeinit_Yes) + result.table.flags |= DN_HTableFlags_DeallocPoolOnDeinit; + if (!result.success) + result.table = {}; + return result; +} + +DN_API DN_HTable DN_HTableInitPoolAssert(DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool) +{ + DN_HTableInitResult init = DN_HTableInitPool(args, pool, dealloc_pool); + DN_HTable result = init.table; + DN_Assert(init.success); + return result; +} + +DN_API void DN_HTableDeinit(DN_HTable *table) +{ + if ((table->flags & DN_HTableFlags_UsingHeap)) { + DN_HeapDealloc(&table->heap, *table->kvs, table->max * table->size_of_kv); } 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 + DN_PoolDealloc(&table->pool, *table->kvs); + if (table->flags & DN_HTableFlags_DeallocPoolOnDeinit) + DN_ArenaDeinit(table->pool.arena); + } + *table->kvs = nullptr; + *table = {}; +} - 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_AssertInvalidCodePath; /*FALLTHRU*/ - case DN_DSMapKeyType_Invalid: break; +static DN_HTableSlot DN_HTableSlotFromArgs_(void *kvs, DN_USize size_of_kv, DN_USize offset_of_hash, DN_USize offset_of_key, DN_USize offset_of_value, DN_USize index) +{ + DN_HTableSlot result = {}; + result.kvs_index = index; + result.kv_struct = (DN_Cast(char*) kvs) + (index * size_of_kv); + result.key = DN_Cast(char*) result.kv_struct + offset_of_key; + result.value = DN_Cast(char*) result.kv_struct + offset_of_value; + result.hash = DN_Cast(char*) result.kv_struct + offset_of_hash; + result.hash_u32 = *DN_Cast(DN_HTableHashType*) result.hash; + return result; +} - case DN_DSMapKeyType_Buffer: - key_ptr = DN_Cast(char const *) key.buffer_data; - len = key.buffer_size; - break; +DN_API DN_HTableLookupResult DN_HTableLookup(DN_HTable const *table, void const *key, DN_HTableAllowTombstone allow_tombstone) +{ + DN_HTableLookupResult result = {}; + if (table->max == 0 || !key) + return result; - case DN_DSMapKeyType_U64: - key_ptr = DN_Cast(char const *) & key.u64; - len = sizeof(key.u64); - break; + DN_Assert(DN_IsPowerOfTwo(table->max)); + DN_U32 hash = table->hash_func(key, table->size_of_key); + if (hash < DN_HTableHashSentinel_FirstValid) + hash += DN_HTableHashSentinel_FirstValid; + DN_USize const mask = table->max - 1; + DN_USize index = hash & mask; + DN_USize probe_increment = 1; + for (DN_USize offset = 0; offset < table->max; offset++, probe_increment++) { + DN_HTableSlot slot = DN_HTableSlotFromIndex(table, index); + bool matched = slot.hash_u32 == DN_HTableHashSentinel_Empty; + if (!matched && allow_tombstone == DN_HTableAllowTombstone_Yes) + matched = slot.hash_u32 == DN_HTableHashSentinel_Tomb; + if (!matched) + matched = slot.hash_u32 >= DN_HTableHashSentinel_FirstValid && slot.hash_u32 == hash && table->key_eq_func(slot.key, key, table->size_of_key); + if (matched) { + result.slot = slot; + result.key_hash = hash; + 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); + // NOTE: We use triangular number probing, referenced off jblow's implementation. Triangular + // numbers are guaranteed to hit every entry in the table. Triangular probing can cause more + // collisions but avoids the localised clustering of collisions when linear probing. It is less + // cache-friendly than linear but more cache-friendly than double hashing. + // https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ + index = (index + probe_increment) & mask; } return result; } -template -DN_U32 DN_DSMapHashToSlotIndex(DN_DSMap const *map, DN_DSMapKey key) +DN_API DN_HTableSlot DN_HTableFind(DN_HTable const *table, void const *key) { - DN_Assert(key.type != DN_DSMapKeyType_Invalid); - DN_U32 result = DN_DS_MAP_SENTINEL_SLOT; - if (!DN_DSMapIsValid(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); - } + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + DN_HTableSlot result = {}; + if (lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) + result = lookup.slot; + return result; } -template -DN_DSMapResult DN_DSMapFind(DN_DSMap const *map, DN_DSMapKey key) +DN_API void* DN_HTableValueFromFind(DN_HTable const *table, void const *key) { - DN_DSMapResult result = {}; - if (DN_DSMapIsValid(map)) { - DN_U32 index = DN_DSMapHashToSlotIndex(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; + DN_HTableSlot slot = DN_HTableFind(table, key); + void *result = slot.value; // Might be null if the key was not found + return result; +} + +DN_API DN_HTableSlot DN_HTableSlotFromIndex(DN_HTable const* table, DN_USize index) +{ + DN_HTableSlot result = {}; + if (index < table->max) + result = DN_HTableSlotFromArgs_(*table->kvs, table->size_of_kv, table->offset_of_hash, table->offset_of_key, table->offset_of_value, index); + return result; +} + +DN_API bool DN_HTableResize(DN_HTable *table, DN_USize new_max) +{ + if (new_max == 0) + new_max = 32; + if (!DN_IsPowerOfTwo(new_max)) + new_max = DN_AlignUpPowerOfTwoUSize(new_max); + + void* new_kvs = {}; + DN_USize new_kvs_size = new_max * table->size_of_kv; + if (table->flags & DN_HTableFlags_UsingHeap) + new_kvs = DN_HeapAlloc(&table->heap, /*reserve*/ new_kvs_size, /*commit*/ new_kvs_size); + else + new_kvs = DN_PoolAlloc(&table->pool, new_max * table->size_of_kv); + + if (new_kvs) { + // NOTE: Copy over the old table slots into the new table storage + void* old_kvs = *table->kvs; + DN_USize old_max = table->max; + table->count = table->tombs_count = 0; + table->max = new_max; + *table->kvs = new_kvs; + for (DN_ForIndexU(index, old_max)) { + DN_HTableSlot old_slot = DN_HTableSlotFromArgs_(old_kvs, table->size_of_kv, table->offset_of_hash, table->offset_of_key, table->offset_of_value, index); + if (old_slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) + DN_HTableAdd(table, old_slot.key, old_slot.value); } - result.value = &result.slot->value; + + if (old_kvs) { + if (table->flags & DN_HTableFlags_UsingHeap) + DN_HeapDealloc(&table->heap, old_kvs, old_max * table->size_of_kv); + else + DN_PoolDealloc(&table->pool, old_kvs); + } + } + + bool result = new_kvs != nullptr; + return result; +} + +DN_API void DN_HTableClear(DN_HTable *table) +{ + for (DN_ForIndexU(index, table->max)) { + DN_HTableSlot slot = DN_HTableSlotFromIndex(table, index); + DN_Memset(slot.hash, DN_HTableHashSentinel_Empty, sizeof(slot.hash_u32)); + } + table->tombs_count = 0; + table->count = 0; +} + +DN_API DN_HTableResizeResult DN_HTableGrowMaybe(DN_HTable *table, DN_USize add_count) +{ + DN_HTableResizeResult result = {}; + result.success = true; + DN_USize new_count = table->count + table->tombs_count + add_count; + DN_F32 new_load_factor = table->max ? new_count / DN_Cast(DN_F32) table->max : table->load_factor01; + if (new_load_factor >= table->load_factor01) { + // NOTE: In this branch, the total number of actively used slots (including tombstones) and the + // `add_amount` exceeds the load factor of the table. We check if doubling the `count` of just + // the _active slots_ in the table would still fit under the table's load factor. If it does + // then the table is currently mostly tombstones, we can avoid doubling the size of the table by + // rehashing which will wipe out the tombstones. + DN_USize new_max = 0; + if (((table->count * 2) + 1) < (table->max * table->load_factor01)) + new_max = table->max; // "Mostly tombstones" branch + else + new_max = table->max * 2; + + result.needed_resize = true; + result.success = DN_HTableResize(table, new_max); } return result; } -template -DN_DSMapResult DN_DSMapMake(DN_DSMap *map, DN_DSMapKey key) +DN_API DN_HTableAddResult DN_HTableMake(DN_HTable* table, void* key) { - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - DN_U32 index = DN_DSMapHashToSlotIndex(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_DSMapResize(map, map->size * 2)) - return result; - result = DN_DSMapMake(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_PoolNewArrayCopy(&map->pool, char, key.buffer_data, key.buffer_size); - } + // NOTE: First do the lookup ignoring tombstones. This probes the chain looking for the existence + // of the value. + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + bool load_factor_is_good = true; + if (lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid) { + // NOTE: If it exists, this is the easy path, we can update the slot returned in the lookup. + // + // Note that we do not check if `hash_u32` is the empty value, because that empty value might + // be at the end of a probe chain. If we were to use that slot, this key-value could potentially + // be placed spatially far away from the optimal position (if there were tombstones closer + // that we skipped over, we want to use those). Hence we take the else branch and do the lookup + // again, this time allowing us to match the tombstone or the first empty slot. } 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_DSMapSet(DN_DSMap *map, DN_DSMapKey key, T const &value) -{ - DN_DSMapResult result = {}; - if (!DN_DSMapIsValid(map)) - return result; - - result = DN_DSMapMake(map, key); - result.slot->value = value; - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyU64(DN_DSMap const *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyU64(DN_DSMap *map, DN_U64 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key, value); - return result; -} - -template -DN_DSMapResult DN_DSMapFindKeyStr8(DN_DSMap const *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapFind(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapMakeKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapMake(map, map_key); - return result; -} - -template -DN_DSMapResult DN_DSMapSetKeyStr8(DN_DSMap *map, DN_Str8 key, T const &value) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - DN_DSMapResult result = DN_DSMapSet(map, map_key); - return result; -} - -template -bool DN_DSMapResize(DN_DSMap *map, DN_U32 size) -{ - if (!DN_DSMapIsValid(map) || size < map->occupied || size < map->initial_size) - return false; - - DN_Arena *prev_arena = map->arena; - DN_MemList *new_mem = prev_arena->mem; - DN_MemList prev_mem = *prev_arena->mem; - prev_arena->mem = &prev_mem; - - *new_mem = {}; - new_mem->funcs = prev_mem.funcs; - new_mem->flags = prev_mem.flags; - - DN_Arena new_arena = {}; - new_arena.mem = new_mem; - - DN_DSMap new_map = DN_DSMapInit(&new_arena, size, map->flags); - if (!DN_DSMapIsValid(&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_DSMapSet(&new_map, old_key, old_slot->value); - } - - if ((map->flags & DN_DSMapFlags_DontFreeArenaOnResize) == 0) - DN_DSMapDeinit(map, DN_ZMem_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_DSMapErase(DN_DSMap *map, DN_DSMapKey key) -{ - if (!DN_DSMapIsValid(map)) - return false; - - DN_U32 index = DN_DSMapHashToSlotIndex(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_PoolDealloc(&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_DSMapHashToSlotIndex(map, last_slot->key); - map->hash_to_slot[hash_to_slot_index] = slot_index; - *last_slot = {}; // TODO: Optional? - } + // NOTE: The value does not exist, look it up again but match on the first tombstone/empty slot + lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_Yes); + bool add_will_use_new_slot = lookup.slot.hash_u32 == DN_HTableHashSentinel_Empty; + if (add_will_use_new_slot) { + DN_HTableResizeResult resize = DN_HTableGrowMaybe(table, 1); + if (resize.success) + lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_Yes); + else + load_factor_is_good = false; } } - 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_DSMapResize(map, map->size / 2); + // NOTE: Update the table slot/kv + DN_HTableAddResult result = {}; + if (load_factor_is_good) { + result.success = true; + result.slot = lookup.slot; + if (result.slot.hash_u32 == DN_HTableHashSentinel_Empty) { + table->count++; + } else if (result.slot.hash_u32 == DN_HTableHashSentinel_Tomb) { + DN_Assert(table->tombs_count); + table->tombs_count--; + table->count++; + } else { + result.existed = true; + } - return true; -} - -template -bool DN_DSMapEraseKeyU64(DN_DSMap *map, DN_U64 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyU64(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -bool DN_DSMapEraseKeyStr8(DN_DSMap *map, DN_Str8 key) -{ - DN_DSMapKey map_key = DN_DSMapKeyStr8(map, key); - bool result = DN_DSMapErase(map, map_key); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBuffer(DN_DSMap const *map, void const *data, DN_USize count) -{ - DN_Assert(count > 0 && count <= UINT32_MAX); - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_Buffer; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) count; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash(DN_DSMap const *map, void const *data, DN_USize count) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_BufferAsU64NoHash; - result.buffer_data = data; - result.buffer_size = DN_Cast(DN_U32) count; - DN_Assert(count >= sizeof(result.hash)); - DN_Memcpy(&result.hash, data, sizeof(result.hash)); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyU64(DN_DSMap const *map, DN_U64 u64) -{ - DN_DSMapKey result = {}; - result.type = DN_DSMapKeyType_U64; - result.u64 = u64; - result.hash = DN_DSMapHash(map, result); - return result; -} - -template -DN_DSMapKey DN_DSMapKeyStr8(DN_DSMap const *map, DN_Str8 string) -{ - DN_DSMapKey result = DN_DSMapKeyBuffer(map, string.data, string.count); - return result; -} - -// NOTE: DN_DSMap -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash(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_DSMapKeyEquals(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; + // NOTE: Update the slot with the given data + if (result.existed) { + DN_Assert(result.slot.hash_u32 == lookup.key_hash); + } else { + result.slot.hash_u32 = lookup.key_hash; + DN_Assert(result.slot.hash); + DN_Assert(result.slot.key); + DN_Memcpy(result.slot.hash, &lookup.key_hash, sizeof(lookup.key_hash)); + DN_Memcpy(result.slot.key, key, table->size_of_key); } } return result; } -DN_API bool operator==(DN_DSMapKey lhs, DN_DSMapKey rhs) +DN_API DN_HTableAddResult DN_HTableAdd(DN_HTable* table, void* key, void* value) { - bool result = DN_DSMapKeyEquals(lhs, rhs); + DN_HTableAddResult result = DN_HTableMake(table, key); + if (result.success) + DN_Memcpy(result.slot.value, value, table->size_of_value); + return result; +} + +DN_API bool DN_HTableDel(DN_HTable *table, void *key) +{ + DN_HTableLookupResult lookup = DN_HTableLookup(table, key, DN_HTableAllowTombstone_No); + bool result = lookup.slot.hash_u32 >= DN_HTableHashSentinel_FirstValid; + if (result) { + DN_HTableHashType sentinel = DN_HTableHashSentinel_Tomb; + DN_Memcpy(lookup.slot.hash, &sentinel, sizeof(sentinel)); + table->tombs_count++; + DN_Assert(table->count); + table->count--; + } return result; } -#endif // defined(__cplusplus) DN_API void DN_BinPackU64(DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item) { @@ -9042,7 +9119,7 @@ DN_API void DN_CSVPackU64(DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *v { if (serialise == DN_CSVSerialise_Read) { DN_Str8 csv_value = DN_CSVTokeniserNextColumn(&pack->read_tokeniser); - DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value, 0); + DN_U64FromResult to_u64 = DN_U64FromStr8(csv_value); DN_Assert(to_u64.success); *value = to_u64.value; } else { @@ -9150,6 +9227,1992 @@ DN_API bool DN_CSVPackNewLine(DN_CSVPack *pack, DN_CSVSerialise serialise) return result; } +DN_API DN_TestCore DN_TestInit(DN_Arena *arena) +{ + DN_TestCore result = {}; + result.arena = arena; + result.pool = DN_PoolFromArena(arena, DN_POOL_DEFAULT_ALIGN); + DN_PArrayGrowFromPool(result.groups, &result.groups_max, &result.pool, 32); + return result; +} + +DN_API void DN_TestGroupBeginF(DN_TestCore *test, char const *fmt, ...) +{ + DN_AssertF(!test->curr_group, "Previous test group (%.*s) must be ended before starting a new group", DN_Str8PrintFmt(test->curr_group->name)); + + // NOTE: ALlocate the group + DN_PArrayGrowIfNeededFromPool(test->groups, test->groups_count, &test->groups_max, &test->pool, 1); + DN_TestGroup *group = DN_PArrayMakeZ(test->groups, &test->groups_count, test->groups_max); + DN_PArrayGrowFromPool(group->entries, &group->entries_max, &test->pool, 32); + + // NOTE: Set up the group + va_list args; + va_start(args, fmt); + group->name = DN_Str8FromFmtVArena(test->arena, fmt, args); + va_end(args); + + // NOTE: Mark as active + test->curr_group = group; + group->ts_begin = DN_OS_PerfCounterNow(); +} + +DN_API void DN_TestGroupEnd(DN_TestCore *test) +{ + DN_TestGroup *group = test->curr_group; + DN_AssertF(group, "Test group must be started before attempting to end it"); + group->ts_end = DN_OS_PerfCounterNow(); + test->curr_group = nullptr; +} + +DN_API void DN_TestBeginF(DN_TestCore *test, char const *fmt, ...) +{ + DN_AssertF(test->curr_group, "Test group must be begun first before creating a test"); + + // NOTE:Allocate test entry + DN_TestGroup *group = test->curr_group; + DN_PArrayGrowIfNeededFromPool(group->entries, group->entries_count, &group->entries_max, &test->pool, 1); + DN_AssertF(!group->curr_entry, "Test (%.*s) must be ended before starting another test in the group (%.*s)", DN_Str8PrintFmt(group->curr_entry->name), DN_Str8PrintFmt(group->name)); + DN_TestEntry *entry = DN_PArrayMakeZ(group->entries, &group->entries_count, group->entries_max); + + // NOTE: Fill in test entry + va_list args; + va_start(args, fmt); + entry->name = DN_Str8FromFmtVArena(test->arena, fmt, args); + va_end(args); + + // NOTE: Mark as active + group->curr_entry = entry; + entry->ts_begin = DN_OS_PerfCounterNow(); + test->total_count++; +} + +DN_API void DN_TestEnd(DN_TestCore *test) +{ + DN_TestGroup *group = test->curr_group; + DN_AssertF(group, "Test group must be started before attempting to end a test in it"); + DN_TestEntry *entry = group->curr_entry; + DN_AssertF(entry, "Test must be started before attempting to end a test in group (%.*s)", DN_Str8PrintFmt(group->name)); + + entry->ts_end = DN_OS_PerfCounterNow(); + if (entry->failed) + test->total_failed++; + else + test->total_passed++; + group->curr_entry = nullptr; +} + +DN_API DN_TestDiagnosticRow *DN_TestVerifySetupFmtV(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool verify_failed, char const *fmt, va_list args) +{ + DN_AssertF(test->curr_group, "Test group must be started before attempting to verify a test invariant"); + DN_AssertF(test->curr_group->curr_entry, "Test must be started before attempting to verify a test invariant in a group (%.*s)", DN_Str8PrintFmt(test->curr_group->name)); + DN_TestDiagnosticRow *result = nullptr; + if (verify_failed) { + DN_TestGroup *group = test->curr_group; + DN_TestEntry *entry = group->curr_entry; + entry->failed = true; + + DN_PArrayGrowIfNeededFromPool(entry->diagnostics, entry->diagnostics_count, &entry->diagnostics_max, &test->pool, 1); + result = DN_PArrayMakeZ(entry->diagnostics, &entry->diagnostics_count, entry->diagnostics_max); + result->expr = expr; + result->call_site = call_site; + result->message = DN_Str8FromFmtVArena(test->arena, fmt, args); + } + return result; +} + +DN_API void DN_TestVerifyExprF_(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool expr_result, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, /*verify_failed=*/ !expr_result, fmt, args); + va_end(args); + if (row) + row->invariant = DN_Str8FromFmtArena(test->arena, "%s (expected %s)", expr_result ? "true" : "false", expr_result ? "false" : "true"); +} + +DN_API void DN_TestVerifyF64Fmt(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_F64 val, DN_F64 expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + row->invariant = DN_Str8FromFmtArena(test->arena, "%f %.*s %f (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + } +} + +DN_API void DN_TestVerifyISizeF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_ISize val, DN_ISize expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(4) in call to 'DN_Str8FromFmtArena' must be the address of a string. Actual type: 'int'. + DN_MSVC_WARNING_DISABLE(6328) // Size mismatch: '__int64' passed as _Param_(3) when 'unsigned int' is required in call to 'DN_Str8FromFmtArena' + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'. + row->invariant = DN_Str8FromFmtArena(test->arena, "%'zd %.*s %'zd (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + DN_MSVC_WARNING_POP + } +} + +DN_API void DN_TestVerifyUSizeF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_USize val, DN_USize expect, DN_TestLogic logic, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + DN_Str8 logic_str8 = {}; + DN_Str8 logic_inv_str8 = {}; + switch (logic) { + case DN_TestLogic_GreaterThan: logic_str8 = DN_Str8Lit(">"); logic_inv_str8 = DN_Str8Lit("<="); verify_failed = !(val > expect); break; + case DN_TestLogic_GreaterThanEq: logic_str8 = DN_Str8Lit(">="); logic_inv_str8 = DN_Str8Lit("<"); verify_failed = !(val >= expect); break; + case DN_TestLogic_LessThan: logic_str8 = DN_Str8Lit("<"); logic_inv_str8 = DN_Str8Lit(">="); verify_failed = !(val < expect); break; + case DN_TestLogic_LessThanEq: logic_str8 = DN_Str8Lit("<="); logic_inv_str8 = DN_Str8Lit(">"); verify_failed = !(val <= expect); break; + case DN_TestLogic_Eq: logic_str8 = DN_Str8Lit("=="); logic_inv_str8 = DN_Str8Lit("!="); verify_failed = !(val == expect); break; + case DN_TestLogic_NotEq: logic_str8 = DN_Str8Lit("!="); logic_inv_str8 = DN_Str8Lit("=="); verify_failed = !(val != expect); break; + } + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, /*expr=*/ DN_Str8Lit(""), verify_failed, fmt, args); + va_end(args); + if (row) { + row->expr = DN_Str8FromFmtArena(test->arena, "%.*s %.*s %.*s", DN_Str8PrintFmt(val_str8), DN_Str8PrintFmt(logic_str8), DN_Str8PrintFmt(expect_str8)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(4) in call to 'DN_Str8FromFmtArena' must be the address of a string. Actual type: 'int'. + DN_MSVC_WARNING_DISABLE(6328) // Size mismatch: '__int64' passed as _Param_(3) when 'unsigned int' is required in call to 'DN_Str8FromFmtArena' + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8FromFmtArena'. + row->invariant = DN_Str8FromFmtArena(test->arena, "%'zu %.*s %'zu (expected)", val, DN_Str8PrintFmt(logic_inv_str8), expect); + DN_MSVC_WARNING_POP + } +} + +DN_API void DN_TestVerifyStr8F(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 str8, DN_Str8 expect, bool expect_eq, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + if (expect_eq) + verify_failed = !DN_Str8EqSensitive(str8, expect); + else + verify_failed = DN_Str8EqSensitive(str8, expect); + + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, verify_failed, fmt, args); + va_end(args); + if (row) { + if (expect_eq) + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" != \"%.*s\"", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + else + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" == \"%.*s\"", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } +} + +DN_API void DN_TestVerifyBytesF(DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 bytes, DN_Str8 expect, bool expect_eq, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + bool verify_failed = false; + if (expect_eq) + verify_failed = !DN_Str8EqSensitive(bytes, expect); + else + verify_failed = DN_Str8EqSensitive(bytes, expect); + + DN_TestDiagnosticRow *row = DN_TestVerifySetupFmtV(test, call_site, expr, verify_failed, fmt, args); + va_end(args); + if (row) { + DN_Str8 bytes_hex = DN_HexFromStr8BytesArena(bytes, test->arena, DN_TrimLeadingZero_No); + DN_Str8 expect_hex = DN_HexFromStr8BytesArena(expect, test->arena, DN_TrimLeadingZero_No); + if (expect_eq) + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" != \"%.*s\"", DN_Str8PrintFmt(bytes_hex), DN_Str8PrintFmt(expect_hex)); + else + row->invariant = DN_Str8FromFmtArena(test->arena, "\"%.*s\" == \"%.*s\"", DN_Str8PrintFmt(bytes_hex), DN_Str8PrintFmt(expect_hex)); + } +} + +DN_API DN_Str8 DN_Str8FromTestCore(DN_TestCore const *test, DN_Arena *arena, DN_Str8FromTestCoreFlags flags) +{ + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); + DN_USize padding_count = 100; + DN_Str8 padding_line = DN_Str8FillF(&scratch.arena, padding_count, "."); + if (flags & DN_Str8FromTestCoreFlags_Colour) + padding_line = DN_Str8FromFmtANSIColourU8RGBArena(DN_ANSIColourMode_Fg, 64, 64, 64, &scratch.arena, "%.*s", DN_Str8PrintFmt(padding_line)); + + DN_V3F32 const good_colour = DN_V3F32From3N(0, 255, 0); + DN_V3F32 const bad_colour = DN_V3F32From3N(255, 0, 0); + DN_Str8Builder builder = DN_Str8BuilderFromArena(arena); + for (DN_ForItSize(group_it, DN_TestGroup const, test->groups, test->groups_count)) { + DN_TestGroup const* group = group_it.data; + DN_USize group_successes = 0; + DN_USize group_failures = 0; + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(5) in call to 'DN_Str8BuilderAppendF' must be the address of a string. Actual type: 'const unsigned __int64'. + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8BuilderAppendF'. + DN_Str8BuilderAppendF(&builder, "[%.*s] (%'zu test%s)\n", DN_Str8PrintFmt(group->name), group->entries_count, (group->entries_count > 1) ? "s" : ""); + DN_MSVC_WARNING_POP + for (DN_ForItSize(entry_it, DN_TestEntry const, group->entries, group->entries_count)) { + DN_TestEntry const* entry = entry_it.data; + if (entry->failed) + group_failures++; + else + group_successes++; + + // NOTE: Extract the test name and pad the line with dots (.) + DN_Str8 test_prefix = DN_Str8FromFmtArena(&scratch.arena, " [%zu/%zu] %.*s ", entry_it.index + 1, group->entries_count, DN_Str8PrintFmt(entry->name)); + if (test_prefix.count < padding_line.count) { + DN_USize remaining = padding_line.count - test_prefix.count; + DN_Str8 padder = DN_Str8Subset(padding_line, 0, remaining); + test_prefix = DN_Str8AppendF(&scratch.arena, test_prefix, "%.*s", DN_Str8PrintFmt(padder)); + } + + // NOTE: Construct the test line + DN_Str8 outcome_str8 = entry->failed ? DN_Str8Lit("FAILED") : DN_Str8Lit("OK"); + if (flags & DN_Str8FromTestCoreFlags_Colour) { + if (entry->failed) + outcome_str8 = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, bad_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(outcome_str8)); + else + outcome_str8 = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, good_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(outcome_str8)); + } + + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(entry->ts_begin, entry->ts_end); + DN_Str8BuilderAppendF(&builder, "%.*s %.*s (%.3fms)\n", DN_Str8PrintFmt(test_prefix), DN_Str8PrintFmt(outcome_str8), elapsed_ms); + + // NOTE: Output the test diagnostics, populated on failure + if (entry->failed) { + for (DN_ForItSize(row_it, DN_TestDiagnosticRow, entry->diagnostics, entry->diagnostics_count)) { + DN_USize row_count = 1; + DN_Str8TableFlags table_flags = DN_Str8TableFlags_None; + if (row_it.index == 0) { + row_count++; // Header + table_flags |= DN_Str8TableFlags_HasHeader; + } + + DN_USize col_count = 3; // Expression, Eval, Call site + DN_USize row_index = 0; + DN_Str8 *rows_str8 = DN_ArenaNewArrayZ(&scratch.arena, DN_Str8, row_count * col_count); + if (row_it.index == 0) { + rows_str8[row_index++] = DN_Str8Lit("Expr."); + rows_str8[row_index++] = DN_Str8Lit("Eval."); + rows_str8[row_index++] = DN_Str8Lit("Location"); + } + + DN_TestDiagnosticRow *row = row_it.data; + DN_Str8 file_name = DN_Str8FileNameFromPath(row->call_site.file); + rows_str8[row_index++] = row->expr; + rows_str8[row_index++] = row->invariant; + rows_str8[row_index++] = DN_Str8FromFmtArena(&scratch.arena, "%.*s:%u", DN_Str8PrintFmt(file_name), row->call_site.line); + + DN_Str8 table = DN_Str8Table(rows_str8, row_count, col_count, table_flags, &scratch.arena); + table = DN_Str8PadNewLinesArena(table, DN_Str8Lit(" "), &scratch.arena); + DN_Str8BuilderAppendF(&builder, " %.*s\n", DN_Str8PrintFmt(table)); + if (row->message.count) { + DN_Str8 line_break = DN_Str8LineBreakArena(row->message, 100, DN_Str8Lit("\n "), DN_Str8LineBreakMode_AtWord, &scratch.arena); + DN_Str8BuilderAppendF(&builder, " User Message: %.*s\n", DN_Str8PrintFmt(line_break)); + } + } + DN_Str8BuilderAppendF(&builder, "\n"); + } + } + + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(group->ts_begin, group->ts_end); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6067) // _Param_(3) in call to 'DN_Str8BuilderAppendF' must be the address of a string. Actual type: 'const unsigned __int64'. + DN_MSVC_WARNING_DISABLE(6271) // Extra argument passed to 'DN_Str8BuilderAppendF'. + DN_Str8 group_prefix = DN_Str8FromFmtArena(&scratch.arena, "%'zu/%'zu test%s", group_successes, group->entries_count, group_successes > 1 ? "s" :""); + DN_MSVC_WARNING_POP + if (flags & DN_Str8FromTestCoreFlags_Colour) { + if (group_failures == 0) + group_prefix = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, good_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(group_prefix)); + else + group_prefix = DN_Str8FromFmtANSIColourV3F32RGB255Arena(DN_ANSIColourMode_Fg, bad_colour, &scratch.arena, "%.*s", DN_Str8PrintFmt(group_prefix)); + } + DN_Str8BuilderAppendF(&builder, "\n %.*s passed in [%.*s] %.3fms (%zu failed)\n", DN_Str8PrintFmt(group_prefix), DN_Str8PrintFmt(group->name), elapsed_ms, group_failures); + } + + DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); + return result; +} + +#if DN_WITH_TESTS +#if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) +// 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; + DN_U32 f_1_ECX_ = 0; + DN_U32 f_1_EDX_ = 0; + DN_U32 f_7_EBX_ = 0; + DN_U32 f_7_ECX_ = 0; + DN_U32 f_81_ECX_ = 0; + DN_U32 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 cpui[4]; + + __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)); + } + + *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; + + if (result.nIds_ >= 1) { + result.f_1_ECX_ = result.data_[1][2]; + result.f_1_EDX_ = result.data_[1][3]; + } + + if (result.nIds_ >= 7) { + result.f_7_EBX_ = result.data_[7][1]; + result.f_7_ECX_ = result.data_[7][2]; + } + + __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)); + } + + if (result.nExIds_ >= 0x80000001) { + result.f_81_ECX_ = result.extdata_[1][2]; + result.f_81_EDX_ = result.extdata_[1][3]; + } + + 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; +} +#endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + +DN_MSVC_WARNING_PUSH +DN_MSVC_WARNING_DISABLE(6262) // Function uses '23524' bytes of stack. Consider moving some data to heap. +DN_API DN_TestCore DN_TestSuite(DN_Arena *arena_) +{ + DN_TestCore result = DN_TestInit(arena_); + for (DN_TestGroupScopeF(&result, "Base")) { + // NOTE: AgeStr8From + { + for (DN_TestScopeF(&result, "[AgeStr8FromMsU64] '1001' in seconds converts to '1s 1ms'")) { + DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_Sec | DN_AgeUnit_Ms); + DN_Str8 expect = DN_Str8Lit("1s 1ms"); + DN_TestVerifyStr8Eq(&result, DN_Str8FromStruct(&str8), expect); + } + + for (DN_TestScopeF(&result, "[AgeStr8FromMsU64] '1001' in seconds converts to '1.001s' (fractional)")) { + DN_Str8x128 str8 = DN_AgeStr8FromMsU64(1001, DN_AgeUnit_FractionalSec); + DN_Str8 expect = DN_Str8Lit("1.001s"); + DN_TestVerifyStr8Eq(&result, DN_Str8FromStruct(&str8), expect); + } + } + + // NOTE: TicketMutex + { + for (DN_TestScopeF(&result, "[TicketMutex] Start and stop")) { + DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + DN_TicketMutex_End(&mutex); + DN_TestVerifyUSizeEq(&result, mutex.ticket, mutex.serving); + } + + for (DN_TestScopeF(&result, "[TicketMutex] 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_TestVerifyExpr(&result, DN_Cast(bool) DN_TicketMutex_CanLock(&mutex, ticket_b) == false); + DN_TestVerifyExpr(&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_TestVerifyUSizeEq(&result, mutex.ticket, mutex.serving); + DN_TestVerifyUSizeEq(&result, mutex.ticket, ticket_b + 1); + } + } + + // NOTE: QSort + { + for (DN_TestScopeF(&result, "[QSort] Str8 Lexicographic Ascending")) { + DN_Str8 list[] = { + DN_Str8Lit("z_last"), + DN_Str8Lit("m_middle"), + DN_Str8Lit("a_first"), + DN_Str8Lit("version-1.2.10"), + DN_Str8Lit("version-1.2.2"), + DN_Str8Lit("version-1.10.0"), + }; + + DN_QSortStr8LexicographicAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Insensitive); + + DN_USize list_index = 0; + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("a_first")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("m_middle")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.10.0")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("z_last")); + } + + for (DN_TestScopeF(&result, "[QSort] Str8 Natural")) { + DN_Str8 list[] = { + DN_Str8Lit("item10"), + DN_Str8Lit("item2"), + DN_Str8Lit("item1"), + DN_Str8Lit("item20"), + DN_Str8Lit("item12"), + DN_Str8Lit("Afile"), + DN_Str8Lit("file2"), + DN_Str8Lit("file10"), + DN_Str8Lit("file1"), + DN_Str8Lit("z_last"), + DN_Str8Lit("m_middle"), + DN_Str8Lit("a_first"), + DN_Str8Lit("version-1.2.10"), + DN_Str8Lit("version-1.2.2"), + DN_Str8Lit("version-1.10.0"), + }; + + DN_QSortStr8NaturalAsc(list, DN_ArrayCountU(list), DN_Str8EqCase_Sensitive); + + DN_USize list_index = 0; + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("Afile")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("a_first")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file1")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("file10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item1")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item12")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("item20")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("m_middle")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.2")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.2.10")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("version-1.10.0")); + DN_TestVerifyStr8Eq(&result, list[list_index++], DN_Str8Lit("z_last")); + } + } + + // NOTE: CPUID + #if defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + { + for (DN_TestScopeF(&result, "[CPU] Query CPUID")) { + DN_RefImplCPUReport ref_cpu_report = DN_RefImplCPUReport_Init(); + DN_CPUReport cpu_report = DN_CPUGetReport(); + + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNow) == ref_cpu_report._3DNOW()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_3DNowExt) == ref_cpu_report._3DNOWEXT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_ABM) == ref_cpu_report.ABM()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AES) == ref_cpu_report.AES()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX) == ref_cpu_report.AVX()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX2) == ref_cpu_report.AVX2()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512CD) == ref_cpu_report.AVX512CD()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512ER) == ref_cpu_report.AVX512ER()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512F) == ref_cpu_report.AVX512F()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_AVX512PF) == ref_cpu_report.AVX512PF()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_CMPXCHG16B) == ref_cpu_report.CMPXCHG16B()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_F16C) == ref_cpu_report.F16C()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_FMA) == ref_cpu_report.FMA()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MMX) == ref_cpu_report.MMX()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MmxExt) == ref_cpu_report.MMXEXT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MONITOR) == ref_cpu_report.MONITOR()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_MOVBE) == ref_cpu_report.MOVBE()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_PCLMULQDQ) == ref_cpu_report.PCLMULQDQ()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_POPCNT) == ref_cpu_report.POPCNT()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDRAND) == ref_cpu_report.RDRAND()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDSEED) == ref_cpu_report.RDSEED()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_RDTSCP) == ref_cpu_report.RDTSCP()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SHA) == ref_cpu_report.SHA()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE) == ref_cpu_report.SSE()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE2) == ref_cpu_report.SSE2()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE3) == ref_cpu_report.SSE3()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE41) == ref_cpu_report.SSE41()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE42) == ref_cpu_report.SSE42()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSE4A) == ref_cpu_report.SSE4a()); + DN_TestVerifyExpr(&result, DN_CPUHasFeature(&cpu_report, DN_CPUFeature_SSSE3) == ref_cpu_report.SSSE3()); + } + } + #endif // defined(DN_PLATFORM_WIN32) && defined(DN_COMPILER_MSVC) + + // NOTE: FmtAppendTruncate + { + for (DN_TestScopeF(&result, "[Fmt] FmtAppendTruncate truncates with 3 dots")) { + char buf[8] = {}; + DN_USize buf_size = 0; + DN_FmtAppendResult buf_str8 = DN_FmtAppendTruncate(buf, &buf_size, sizeof(buf), DN_Str8Lit("..."), "This string is longer than %d characters", DN_Cast(int)(sizeof(buf) - 1)); + DN_Str8 expect = DN_Str8Lit("This..."); + DN_TestVerifyExpr(&result, buf_str8.truncated); + DN_TestVerifyStr8EqF(&result, buf_str8.str8, expect, "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(buf_str8.str8), DN_Str8PrintFmt(expect)); + } + } + + // NOTE: Atomics + { + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(28113) + DN_MSVC_WARNING_DISABLE(28112) + { + for (DN_TestScopeF(&result, "[Atomics] AtomicAddU32")) { + DN_U32 val = 0; + DN_AtomicAddU32(&val, 1); + DN_TestVerifyUSizeEq(&result, val, 1); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicAddU64")) { + uint64_t val = 0; + DN_AtomicAddU64(&val, 1); + DN_TestVerifyExprF(&result, val == 1, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSubU32")) { + DN_U32 val = 1; + DN_AtomicSubU32(&val, 1); + DN_TestVerifyUSizeEq(&result, val, 0); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSubU64")) { + uint64_t val = 1; + DN_AtomicSubU64(&val, 1); + DN_TestVerifyExprF(&result, val == 0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSetValue32")) { + DN_U32 a = 0; + DN_U32 b = 111; + DN_AtomicSetValue32(&a, b); + DN_TestVerifyUSizeEq(&result, a, b); + } + + for (DN_TestScopeF(&result, "[Atomics] AtomicSetValue64")) { + int64_t a = 0; + int64_t b = 111; + DN_AtomicSetValue64(DN_Cast(uint64_t *) & a, b); + DN_TestVerifyExprF(&result, a == b, "a: %" PRId64 ", b: %" PRId64, a, b); + } + + for (DN_TestScopeF(&result, "[Intrinsics] CPUGetTSC compile check")) { + DN_CPUGetTSC(); + } + + for (DN_TestScopeF(&result, "[Intrinsics] CompilerReadBarrierAndCPUReadFence compile check")) { + DN_CompilerReadBarrierAndCPUReadFence; + } + + for (DN_TestScopeF(&result, "[Intrinsics] CompilerWriteBarrierAndCPUWriteFence compile check")) { + DN_CompilerWriteBarrierAndCPUWriteFence; + } + } + DN_MSVC_WARNING_POP + } + + // NOTE: Arena + { + for (DN_TestScopeF(&result, "[Arena] Reused memory is zeroed out")) { + uint8_t alignment = 1; + DN_USize alloc_size = DN_Kilobytes(128); + DN_MemList mem = DN_MemListFromHeap(0, 0, DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + uintptr_t first_ptr_address = 0; + { + DN_U64 mem_p = DN_MemListPos(arena.mem); + void *ptr = DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); + first_ptr_address = DN_Cast(uintptr_t) ptr; + DN_Memset(ptr, 'z', alloc_size); + DN_MemListPopTo(arena.mem, mem_p); + } + + char *ptr = DN_Cast(char *) DN_ArenaAlloc(&arena, alloc_size, alignment, DN_ZMem_Yes); + + DN_TestVerifyUSizeEq(&result, first_ptr_address, DN_Cast(uintptr_t) ptr); + + for (DN_USize i = 0; i < alloc_size; i++) + DN_TestVerifyExpr(&result, ptr[i] == 0); + } + + for (DN_TestScopeF(&result, "[Arena] Grows naturally, 1mb + 4mb")) { + DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + char *ptr_1mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(1), DN_ZMem_Yes); + char *ptr_4mb = DN_ArenaNewArray(&arena, char, DN_Megabytes(4), DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_1mb != nullptr); + DN_TestVerifyExpr(&result, ptr_4mb != nullptr); + + DN_MemBlock const *block_4mb_begin = arena.mem->curr; + char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev; + DN_TestVerifyExprF(&result, block_1mb_begin != nullptr, "New block should have been allocated"); + char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_TestVerifyExprF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_TestVerifyExprF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_TestVerifyExprF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + + for (DN_TestScopeF(&result, "[Arena] Grows naturally, 1mb, temp memory 4mb")) { + DN_MemList mem = DN_MemListFromHeap(DN_Megabytes(2), DN_Megabytes(2), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_DEFER { DN_MemListDeinit(&mem); }; + + char *ptr_1mb = DN_Cast(char *) DN_ArenaAlloc(&arena, DN_Megabytes(1), 1, DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_1mb != nullptr); + + DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); + { + char *ptr_4mb = DN_ArenaNewArray(&temp, char, DN_Megabytes(4), DN_ZMem_Yes); + DN_TestVerifyExpr(&result, ptr_4mb != nullptr); + + DN_MemBlock const *block_4mb_begin = arena.mem->curr; + char const *block_4mb_end = DN_Cast(char *) block_4mb_begin + block_4mb_begin->reserve; + + DN_MemBlock const *block_1mb_begin = block_4mb_begin->prev; + char const *block_1mb_end = DN_Cast(char *) block_1mb_begin + block_1mb_begin->reserve; + + DN_TestVerifyExprF(&result, block_1mb_begin != block_4mb_begin, "New block should have been allocated and linked"); + DN_TestVerifyExprF(&result, ptr_1mb >= DN_Cast(char *) block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block"); + DN_TestVerifyExprF(&result, ptr_4mb >= DN_Cast(char *) block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block"); + } + DN_ArenaTempEnd(&temp, DN_ArenaReset_Yes); + DN_TestVerifyExpr(&result, arena.mem->curr->prev == nullptr); + DN_TestVerifyExprF(&result, + arena.mem->curr->reserve >= DN_Megabytes(1), + "size=%" PRIu64 "MiB (%" PRIu64 "B), expect=%" PRIu64 "B", + (arena.mem->curr->reserve / 1024 / 1024), + arena.mem->curr->reserve, + DN_Megabytes(1)); + } + } + + // NOTE: BytesHex + { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_DEFER { DN_TCScratchEnd(&scratch); }; + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0x123")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x123")); + DN_TestVerifyExprF(&result, val == 0x123, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0xFFFF")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xFFFF")); + DN_TestVerifyExprF(&result, val == 0xFFFF, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert FFFF")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("FFFF")); + DN_TestVerifyExprF(&result, val == 0xFFFF, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert abCD")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("abCD")); + DN_TestVerifyExprF(&result, val == 0xabCD, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0xabCD")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0xabCD")); + DN_TestVerifyExprF(&result, val == 0xabCD, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0x")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0x")); + DN_TestVerifyExprF(&result, val == 0x0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 0X")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("0X")); + DN_TestVerifyExprF(&result, val == 0x0, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert 3")) { + uint64_t val = DN_U64FromHexStr8Unsafe(DN_Str8Lit("3")); + DN_TestVerifyExprF(&result, val == 3, "val: %" PRIu64, val); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert f")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("f")); + DN_TestVerifyExpr(&result, res.success); + DN_TestVerifyExpr(&result, res.value == 0xf); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert g")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("g")); + DN_TestVerifyExpr(&result, !res.success); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert -0x3")) { + DN_U64FromResult res = DN_U64FromHexStr8(DN_Str8Lit("-0x3")); + DN_TestVerifyExpr(&result, !res.success); + } + + DN_U32 number = 0xd095f6; + for (DN_TestScopeF(&result, "[BytesHex] Convert %x to string", number)) { + DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, number_hex, DN_Str8Lit("f695d000"), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); + } + + number = 0xf6ed00; + for (DN_TestScopeF(&result, "[BytesHex] Convert %x to string", number)) { + DN_Str8 number_hex = DN_HexFromPtrBytesArena(&number, sizeof(number), &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, number_hex, DN_Str8Lit("00edf600"), "number_hex=%.*s", DN_Str8PrintFmt(number_hex)); + } + + DN_Str8 hex = DN_Str8Lit("0xf6ed00"); + for (DN_TestScopeF(&result, "[BytesHex] Convert %.*s to bytes", DN_Str8PrintFmt(hex))) { + DN_Str8 bytes = DN_BytesFromHexArena(hex, &scratch.arena); + DN_TestVerifyStr8EqF(&result, + bytes, + DN_Str8Lit("\xf6\xed\x00"), + "number_hex=%.*s", + DN_Str8PrintFmt(DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No))); + } + + for (DN_TestScopeF(&result, "[BytesHex] Convert empty bytes to string")) { + DN_Str8 bytes = DN_Str8Lit(""); + DN_Str8 as_hex = DN_HexFromPtrBytesArena(bytes.data, bytes.count, &scratch.arena, DN_TrimLeadingZero_No); + DN_TestVerifyStr8EqF(&result, as_hex, DN_Str8Lit(""), "as_hex=%.*s", DN_Str8PrintFmt(as_hex)); + } + } + + // NOTE: BinarySearch + { + for (DN_TestScopeF(&result, "[BinarySearch] Search array of 1 item")) { + DN_U32 array[] = {1}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + } + + for (DN_TestScopeF(&result, "[BinarySearch] Search array of 3 items")) { + DN_U32 array[] = {1, 2, 3}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 1); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 3); + } + + for (DN_TestScopeF(&result, "[BinarySearch] Search array with duplicate items")) { + DN_U32 array[] = {1, 1, 2, 2, 3}; + DN_BinarySearchResult search = {}; + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_Match); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 4U, DN_BinarySearchType_LowerBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 0U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 0); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 1U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 2); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 2U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, search.found); + DN_TestVerifyUSizeEq(&result, search.index, 4); + + search = DN_BinarySearch(array, DN_ArrayCountU(array), 3U, DN_BinarySearchType_UpperBound); + DN_TestVerifyExpr(&result, !search.found); + DN_TestVerifyUSizeEq(&result, search.index, 5); + } + } + + // NOTE: IArray + { + for (DN_TestScopeF(&result, "[IArray] Make item")) { + struct CustomArray { + int *data; + DN_USize count; + DN_USize max; + }; + + int array_buffer[16]; + CustomArray array = {}; + array.data = array_buffer; + array.max = DN_ArrayCountU(array_buffer); + + int *item = DN_IArrayMake(&array, DN_ZMem_Yes); + DN_TestVerifyExpr(&result, item != nullptr); + DN_TestVerifyUSizeEq(&result, array.count, 1); + } + } + + // NOTE: Array + { + for (DN_TestScopeF(&result, "[Array] Positive count, middle of array, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 2); + DN_TestVerifyUSizeEq(&result, erase.it_index, 2); + DN_TestVerifyUSizeEq(&result, size, 8); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Negative count, middle of array, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Stable); + int expected[] = {0, 1, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 3); + DN_TestVerifyUSizeEq(&result, erase.it_index, 1); + DN_TestVerifyUSizeEq(&result, size, 7); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] count = -1, stable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -1, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 1); + DN_TestVerifyUSizeEqF(&result, erase.it_index, 3, "lhs=%zu", erase.it_index); + DN_TestVerifyUSizeEq(&result, size, 9); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Positive count, unstable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 3, 2, DN_ArrayErase_Unstable); + int expected[] = {0, 1, 2, 8, 9, 5, 6, 7}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 2); + DN_TestVerifyUSizeEq(&result, erase.it_index, 2); + DN_TestVerifyUSizeEq(&result, size, 8); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Negative count, unstable erase")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, -3, DN_ArrayErase_Unstable); + int expected[] = {0, 1, 7, 8, 9, 5, 6}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 3); + DN_TestVerifyUSizeEq(&result, erase.it_index, 1); + DN_TestVerifyUSizeEq(&result, size, 7); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Edge case - begin_index at start, negative count")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 0, -2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 0); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Edge case - begin_index at end, positive count")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 9, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 1); + DN_TestVerifyUSizeEq(&result, erase.it_index, 9); + DN_TestVerifyUSizeEq(&result, size, 9); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - count = 0")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 0, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - null data")) { + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(nullptr, &size, sizeof(int), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 10); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - null size")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, NULL, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + } + + for (DN_TestScopeF(&result, "[Array] Invalid input - empty array")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 0; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 5, 2, DN_ArrayErase_Stable); + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 5); + DN_TestVerifyUSizeEq(&result, size, 0); + } + + for (DN_TestScopeF(&result, "[Array] Out-of-bounds begin_index")) { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_USize size = 10; + DN_ArrayEraseResult erase = DN_ArrayEraseRange(arr, &size, sizeof(arr[0]), 15, 2, DN_ArrayErase_Stable); + int expected[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + DN_TestVerifyUSizeEq(&result, erase.items_erased, 0); + DN_TestVerifyUSizeEq(&result, erase.it_index, 9); + DN_TestVerifyUSizeEq(&result, size, 10); + DN_TestVerifyExpr(&result, DN_Memcmp(arr, expected, size * sizeof(arr[0])) == 0); + } + } + + // NOTE: M4 + { + for (DN_TestScopeF(&result, "[M4] Simple translate and scale matrix")) { + DN_M4 translate = DN_M4TranslateF(1, 2, 3); + DN_M4 scale = DN_M4ScaleF(2, 2, 2); + DN_M4 mul_result = DN_M4Mul(translate, scale); + + const DN_M4 EXPECT = { + { + {2, 0, 0, 0}, + {0, 2, 0, 0}, + {0, 0, 2, 0}, + {1, 2, 3, 1}, + } + }; + + DN_TestVerifyExprF(&result, + memcmp(mul_result.columns, EXPECT.columns, sizeof(EXPECT)) == 0, + "\nresult =\n%s\nexpected =\n%s", + DN_M4ColumnMajorString(mul_result).data, + DN_M4ColumnMajorString(EXPECT).data); + } + } + + // NOTE: OS + #if DN_WITH_OS + { + for (DN_TestScopeF(&result, "[OS] Generate secure RNG 32 bytes")) { + char const ZERO[32] = {}; + char buf[32] = {}; + DN_OS_GenBytesSecure(buf, DN_ArrayCountU(buf)); + DN_TestVerifyExpr(&result, DN_Memcmp(buf, ZERO, DN_ArrayCountU(buf)) != 0); + } + + for (DN_TestScopeF(&result, "[OS] Query executable directory")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 os_result = DN_OS_EXEDir(&scratch.arena); + DN_TestVerifyExpr(&result, os_result.count > 0); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(os_result), "result(%zu): %.*s", os_result.count, DN_Str8PrintFmt(os_result)); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[OS] DN_OS_PerfCounterNow")) { + uint64_t os_result = DN_OS_PerfCounterNow(); + DN_TestVerifyExpr(&result, os_result != 0); + } + + for (DN_TestScopeF(&result, "[OS] Consecutive ticks are ordered")) { + uint64_t a = DN_OS_PerfCounterNow(); + uint64_t b = DN_OS_PerfCounterNow(); + DN_TestVerifyExprF(&result, b >= a, "a: %" PRIu64 ", b: %" PRIu64, a, b); + } + + for (DN_TestScopeF(&result, "[OS] 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_TestVerifyF64LessThanEq(&result, s, ms); + DN_TestVerifyF64LessThanEq(&result, ms, us); + DN_TestVerifyF64LessThanEq(&result, us, ns); + } + + for (DN_TestScopeF(&result, "[OS] Make directory recursive \"abcd/efgh\"")) { + DN_TestVerifyExprF(&result, DN_OS_PathMakeDir(DN_Str8Lit("abcd/efgh")), "Failed to make directory"); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd")), "Directory was not made"); + DN_TestVerifyExprF(&result, DN_OS_PathIsDir(DN_Str8Lit("abcd/efgh")), "Subdirectory was not made"); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd")) == false, "This function should only return true for files"); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(DN_Str8Lit("abcd/efgh")) == false, "This function should only return true for files"); + DN_TestVerifyExprF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd/efgh")), "Failed to delete directory"); + DN_TestVerifyExprF(&result, DN_OS_PathDelete(DN_Str8Lit("abcd")), "Failed to cleanup directory"); + } + + for (DN_TestScopeF(&result, "[OS] File write, read, copy, move and delete")) { + DN_Str8 const SRC_FILE = DN_Str8Lit("dn_result_file"); + DN_B32 write_result = DN_OS_FileWriteAll(SRC_FILE, DN_Str8Lit("1234"), nullptr); + DN_TestVerifyExpr(&result, write_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(SRC_FILE)); + + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 read_file = DN_OS_FileReadAllArena(&scratch.arena, SRC_FILE, nullptr); + DN_TestVerifyExprF(&result, read_file.count > 0, "Failed to load file"); + DN_TestVerifyExprF(&result, read_file.count == 4, "File read wrong amount of bytes (%zu)", read_file.count); + DN_TestVerifyStr8EqF(&result, read_file, DN_Str8Lit("1234"), "Read %zu bytes instead of the expected 4: '%.*s'", read_file.count, DN_Str8PrintFmt(read_file)); + + DN_Str8 const COPY_FILE = DN_Str8Lit("dn_result_file_copy"); + DN_B32 copy_result = DN_OS_FileCopy(SRC_FILE, COPY_FILE, true, nullptr); + DN_TestVerifyExpr(&result, copy_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(COPY_FILE)); + + DN_Str8 const MOVE_FILE = DN_Str8Lit("dn_result_file_move"); + DN_B32 move_result = DN_OS_FileMove(COPY_FILE, MOVE_FILE, true, nullptr); + DN_TestVerifyExpr(&result, move_result); + DN_TestVerifyExpr(&result, DN_OS_PathIsFile(MOVE_FILE)); + DN_TestVerifyExprF(&result, DN_OS_PathIsFile(COPY_FILE) == false, "Moving a file should remove the original"); + + DN_B32 delete_src_file = DN_OS_PathDelete(SRC_FILE); + DN_B32 delete_moved_file = DN_OS_PathDelete(MOVE_FILE); + DN_TestVerifyExpr(&result, delete_src_file); + DN_TestVerifyExpr(&result, delete_moved_file); + + 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_TestVerifyExpr(&result, delete_non_existent_moved_file == false); + DN_TestVerifyExpr(&result, delete_non_existent_src_file == false); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[OS] Wait timeout")) { + DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); + DN_DEFER { DN_OS_SemaphoreDeinit(&sem); }; + + DN_U64 begin = DN_OS_PerfCounterNow(); + DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 100); + DN_U64 end = DN_OS_PerfCounterNow(); + DN_TestVerifyUSizeEqF(&result, wait_result, DN_OSSemaphoreWaitResult_Timeout, "Received wait result %zu", wait_result); + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); + DN_TestVerifyExprF(&result, elapsed_ms >= 80 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); + } + + for (DN_TestScopeF(&result, "[OS] Wait success")) { + DN_OSSemaphore sem = DN_OS_SemaphoreInit(0); + DN_DEFER { DN_OS_SemaphoreDeinit(&sem); }; + + DN_OS_SemaphoreIncrement(&sem, 1); + DN_OSSemaphoreWaitResult wait_result = DN_OS_SemaphoreWait(&sem, 0); + DN_TestVerifyUSizeEqF(&result, wait_result, DN_OSSemaphoreWaitResult_Success, "Received wait result %zu", wait_result); + } + + for (DN_TestScopeF(&result, "[OS] Lock mutex")) { + DN_OSMutex mutex = DN_OS_MutexInit(); + DN_DEFER { DN_OS_MutexDeinit(&mutex); }; + + DN_OS_MutexLock(&mutex); + DN_OS_MutexUnlock(&mutex); + } + + for (DN_TestScopeF(&result, "[OS] Lock and timeout condition variable")) { + DN_OSMutex mutex = DN_OS_MutexInit(); + DN_OSConditionVariable cv = DN_OS_ConditionVariableInit(); + DN_DEFER { + DN_OS_MutexDeinit(&mutex); + DN_OS_ConditionVariableDeinit(&cv); + }; + + DN_U64 begin = DN_OS_PerfCounterNow(); + DN_OS_ConditionVariableWait(&cv, &mutex, 100); + DN_U64 end = DN_OS_PerfCounterNow(); + DN_F64 elapsed_ms = DN_OS_PerfCounterMs(begin, end); + DN_TestVerifyExprF(&result, elapsed_ms >= 99 && elapsed_ms <= 120, "Expected to sleep for ~100ms, slept %f ms", elapsed_ms); + } + } + #endif // #if DN_WITH_OS + + // NOTE: Rect + { + for (DN_TestScopeF(&result, "[Rect] No intersection")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From1N(0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(200, 0), DN_V2F32From2N(200, 200)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A's min intersects B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B's min intersects A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(50, 50), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A's max intersects B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B's max intersects A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(-50, -50), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] B contains A")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A contains B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = DN_RectFrom2V2(DN_V2F32From2N(25, 25), DN_V2F32From2N(25, 25)); + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + + for (DN_TestScopeF(&result, "[Rect] A equals B")) { + DN_Rect a = DN_RectFrom2V2(DN_V2F32From2N(0, 0), DN_V2F32From2N(100, 100)); + DN_Rect b = a; + DN_Rect ab = DN_RectIntersection(a, b); + + DN_V2F32 ab_max = ab.pos + ab.size; + DN_TestVerifyExprF(&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); + } + } + + // NOTE: Strings + { + for (DN_TestScopeF(&result, "[Strings] Str8 literal")) { + DN_Str8 string = DN_Str8Lit("AB"); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TestVerifyExprF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_TestVerifyExprF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + } + + for (DN_TestScopeF(&result, "[Strings] C-string length")) { + DN_USize size = DN_CStr8Count("hello"); + DN_TestVerifyUSizeEqF(&result, size, 5, "size=%zu", size); + } + + char arena_base[512]; + for (DN_TestScopeF(&result, "[Strings] Format from arena")) { + DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_Str8 str8 = DN_Str8FromFmtArena(&arena, "Foo Bar %d", 5); + DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); + DN_TestVerifyStr8EqF(&result, str8, expect, "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Format from pool")) { + DN_MemList mem = DN_MemListFromBuffer(arena_base, sizeof(arena_base), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromMemList(&mem); + DN_Pool pool = DN_PoolFromArena(&arena, 0); + DN_Str8 str8 = DN_Str8FromFmtPool(&pool, "Foo Bar %d", 5); + DN_Str8 expect = DN_Str8Lit("Foo Bar 5"); + DN_TestVerifyStr8EqF(&result, str8, expect, "str8=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Str8x32 from U64")) { + DN_Str8x32 str8 = DN_Str8x32FromU64(123456, ' '); + DN_Str8 expect = DN_Str8Lit("123 456"); + DN_TestVerifyStr8EqF(&result, DN_Str8FromStruct(&str8), expect, "buf_str8=%.*s, expect=%.*s", DN_Str8PrintFmt(str8), DN_Str8PrintFmt(expect)); + } + + for (DN_TestScopeF(&result, "[Strings] Initialise with format string")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8FromFmtArena(&scratch.arena, "%s", "AB"); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TestVerifyExprF(&result, string.data[0] == 'A', "string[0]: %c", string.data[0]); + DN_TestVerifyExprF(&result, string.data[1] == 'B', "string[1]: %c", string.data[1]); + DN_TestVerifyExprF(&result, string.data[2] == 0, "string[2]: %c", string.data[2]); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Copy string")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8Lit("AB"); + DN_Str8 copy = DN_Str8FromStr8Arena(string, &scratch.arena); + DN_TestVerifyUSizeEqF(&result, copy.count, 2, "size: %zu", copy.count); + DN_TestVerifyExprF(&result, copy.data[0] == 'A', "copy[0]: %c", copy.data[0]); + DN_TestVerifyExprF(&result, copy.data[1] == 'B', "copy[1]: %c", copy.data[1]); + DN_TestVerifyExprF(&result, copy.data[2] == 0, "copy[2]: %c", copy.data[2]); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Trim whitespace around string")) { + DN_Str8 string = DN_Str8TrimWhitespaceAround(DN_Str8Lit(" AB ")); + DN_TestVerifyStr8EqF(&result, string, DN_Str8Lit("AB"), "[string=%.*s]", DN_Str8PrintFmt(string)); + } + + for (DN_TestScopeF(&result, "[Strings] Allocate string from arena")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 string = DN_Str8AllocArena(2, DN_ZMem_No, &scratch.arena); + DN_TestVerifyUSizeEqF(&result, string.count, 2, "size: %zu", string.count); + DN_TCScratchEnd(&scratch); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix with matching prefix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(input, DN_Str8Lit("nft/")); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("abc"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix with non matching prefix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(input, DN_Str8Lit(" ft/")); + DN_TestVerifyStr8EqF(&result, str_result, input, "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim suffix with matching suffix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimSuffixSensitive(input, DN_Str8Lit("abc")); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("nft/"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim suffix with non matching suffix")) { + DN_Str8 input = DN_Str8Lit("nft/abc"); + DN_Str8 str_result = DN_Str8TrimSuffixSensitive(input, DN_Str8Lit("ab")); + DN_TestVerifyStr8EqF(&result, str_result, input, "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on non-digit string")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("@123string"), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, str_result == false); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on nullptr")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(nullptr, 0), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, str_result == false); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on string w/ 0 size")) { + char const buf[] = "@123string"; + DN_B32 str_result = DN_Str8IsAll(DN_Str8FromPtr(buf, 0), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, !str_result); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits success")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23"), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, DN_Cast(bool) str_result == true); + } + + for (DN_TestScopeF(&result, "[Strings] Is all digits fails on whitespace")) { + DN_B32 str_result = DN_Str8IsAll(DN_Str8Lit("23 "), DN_Str8IsAllType_Digits); + DN_TestVerifyExpr(&result, DN_Cast(bool) str_result == false); + } + + // NOTE: DN_Str8BSplit + { + DN_Str8 delimiter = DN_Str8Lit("/"); + DN_Str8 input = DN_Str8Lit("abcdef"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("abcdef"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit(""), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + input = DN_Str8Lit("abc/def"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("abc"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("def"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + input = DN_Str8Lit("/abcdef"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit(""), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("abcdef"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + + delimiter = DN_Str8Lit("-=-"); + input = DN_Str8Lit("123-=-456"); + for (DN_TestScopeF(&result, "[Strings] Binary split \"%.*s\" with \"%.*s\"", DN_Str8PrintFmt(input), DN_Str8PrintFmt(delimiter))) { + DN_Str8BSplitResult split = DN_Str8BSplit(input, delimiter); + DN_TestVerifyStr8EqF(&result, split.lhs, DN_Str8Lit("123"), "[lhs=%.*s]", DN_Str8PrintFmt(split.lhs)); + DN_TestVerifyStr8EqF(&result, split.rhs, DN_Str8Lit("456"), "[rhs=%.*s]", DN_Str8PrintFmt(split.rhs)); + } + } + + // NOTE: DN_I64FromStr8 + for (DN_TestScopeF(&result, "[Strings] To I64: Convert empty string")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit(""), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 0); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"-0\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-0"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 0); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"-1\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("-1"), 0); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == -1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1.2\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1.2"), 0); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1,234\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,234"), ','); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 1234); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"1,2\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("1,2"), ','); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 12); + } + + for (DN_TestScopeF(&result, "[Strings] To I64: Convert \"12a3\"")) { + DN_I64FromResult str_result = DN_I64FromStr8(DN_Str8Lit("12a3"), 0); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExpr(&result, str_result.value == 12); + } + + // NOTE: DN_U64FromStr8 + for (DN_TestScopeF(&result, "[Strings] To U64: Convert empty string")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"-0\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-0")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"-1\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("-1")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 0, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1.2\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("1.2")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1,234\"")) { + DN_U64FromResult str_result = DN_U64FromStr8Delimiter(DN_Str8Lit("1,234"), DN_Str8Lit(",")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 1234, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"1,2\"")) { + DN_U64FromResult str_result = DN_U64FromStr8Delimiter(DN_Str8Lit("1,2"), DN_Str8Lit(",")); + DN_TestVerifyExpr(&result, str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + for (DN_TestScopeF(&result, "[Strings] To U64: Convert \"12a3\"")) { + DN_U64FromResult str_result = DN_U64FromStr8(DN_Str8Lit("12a3")); + DN_TestVerifyExpr(&result, !str_result.success); + DN_TestVerifyExprF(&result, str_result.value == 12, "result: %" PRIu64, str_result.value); + } + + // NOTE: DN_Str8Find + for (DN_TestScopeF(&result, "[Strings] Find char is not in buffer")) { + DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_Str8Lit("2"); + DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_TestVerifyExpr(&result, !str_result.found); + DN_TestVerifyUSizeEq(&result, str_result.index, 0); + DN_TestVerifyExpr(&result, str_result.match.data == nullptr); + DN_TestVerifyUSizeEq(&result, str_result.match.count, 0); + } + + for (DN_TestScopeF(&result, "[Strings] Find char is in buffer")) { + DN_Str8 buf = DN_Str8Lit("836a35becd4e74b66a0d6844d51f1a63018c7ebc44cf7e109e8e4bba57eefb55"); + DN_Str8 find = DN_Str8Lit("6"); + DN_Str8FindResult str_result = DN_Str8FindStr8(buf, find, DN_Str8EqCase_Sensitive); + DN_TestVerifyExpr(&result, str_result.found); + DN_TestVerifyUSizeEq(&result, str_result.index, 2); + DN_TestVerifyExpr(&result, str_result.match.data[0] == '6'); + } + + // NOTE: DN_Str8FileNameFromPath + for (DN_TestScopeF(&result, "[Strings] File name from Windows path")) { + DN_Str8 buf = DN_Str8Lit("C:\\ABC\\str_result.exe"); + DN_Str8 str_result = DN_Str8FileNameFromPath(buf); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("str_result.exe"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] File name from Linux path")) { + DN_Str8 buf = DN_Str8Lit("/ABC/str_result.exe"); + DN_Str8 str_result = DN_Str8FileNameFromPath(buf); + DN_TestVerifyStr8EqF(&result, str_result, DN_Str8Lit("str_result.exe"), "%.*s", DN_Str8PrintFmt(str_result)); + } + + for (DN_TestScopeF(&result, "[Strings] Trim prefix")) { + DN_Str8 prefix = DN_Str8Lit("@123"); + DN_Str8 buf = DN_Str8Lit("@123string"); + DN_Str8 str_result = DN_Str8TrimPrefixSensitive(buf, prefix); + DN_TestVerifyStr8Eq(&result, str_result, DN_Str8Lit("string")); + } + + // NOTE: DN_Str8TruncMiddle + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Short string is not truncated")) { + DN_Str8 str = DN_Str8Lit("Hello"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Exact boundary is not truncated")) { + DN_Str8 str = DN_Str8Lit("HelloWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 10); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("HelloWorld"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Long string is truncated in the middle")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Empty truncator concatenates head and tail")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit(""); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 10); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("HelloWorld"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: side_size of 0 returns just truncator")) { + DN_Str8 str = DN_Str8Lit("HelloWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 0, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 3); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("..."), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Null dest calculates size without writing")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyExpr(&result, res.str8.data == nullptr); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: count_req is consistent between dry-run and actual")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult dry = DN_Str8TruncMiddlePtr(str, 5, trunc, nullptr, 0); + char dest[64] = {}; + DN_Str8TruncResult actual = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyUSizeEq(&result, dry.count_req, actual.count_req); + DN_TestVerifyExpr(&result, dry.truncated == actual.truncated); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Minimum buffer size is sufficient")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[14] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 5, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Single character side size")) { + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 1, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("H...d"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddlePtr: Large side_size falls back to copy")) { + DN_Str8 str = DN_Str8Lit("Hello"); + DN_Str8 trunc = DN_Str8Lit("..."); + char dest[64] = {}; + DN_Str8TruncResult res = DN_Str8TruncMiddlePtr(str, 100, trunc, dest, sizeof(dest)); + DN_TestVerifyExpr(&result, !res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 5); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello"), "%.*s", DN_Str8PrintFmt(res.str8)); + } + + for (DN_TestScopeF(&result, "[Strings] TruncMiddle: Arena wrapper allocates and truncates correctly")) { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 str = DN_Str8Lit("HelloBeautifulWorld"); + DN_Str8 trunc = DN_Str8Lit("..."); + DN_Str8TruncResult res = DN_Str8TruncMiddle(str, 5, trunc, &scratch.arena); + DN_TestVerifyExpr(&result, res.truncated); + DN_TestVerifyUSizeEq(&result, res.count_req, 13); + DN_TestVerifyStr8EqF(&result, res.str8, DN_Str8Lit("Hello...World"), "%.*s", DN_Str8PrintFmt(res.str8)); + DN_TestVerifyExpr(&result, res.str8.data[res.str8.count] == '\0'); + DN_TCScratchEnd(&scratch); + } + } + + // NOTE: Win + #if defined(DN_PLATFORM_WIN32) + { + DN_TCScratch scratch = DN_TCScratchBeginArena(&arena_, 1); + DN_Str8 input8 = DN_Str8Lit("String"); + DN_Str16 input16 = DN_Str16{(wchar_t *)(L"String"), sizeof(L"String") / sizeof(L"String"[0]) - 1}; + + for (DN_TestScopeF(&result, "[Win] Str8 to Str16")) { + DN_Str16 str_result = DN_OS_W32Str8ToStr16(&scratch.arena, input8); + DN_TestVerifyExpr(&result, DN_Str16Eq(str_result, input16)); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8")) { + DN_Str8 str_result = DN_OS_W32Str16ToStr8(&scratch.arena, input16); + DN_TestVerifyStr8Eq(&result, str_result, input8); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8: Null terminates string")) { + int size_required = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); + char *string = DN_ArenaNewArray(&scratch.arena, char, size_required + 1, DN_ZMem_No); + + DN_Memset(string, 'Z', size_required + 1); + + int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, string, size_required + 1); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_TestVerifyUSizeEqF(&result, size_required, size_returned, "string_size: %d, result: %d", size_required, size_returned); + DN_TestVerifyUSizeEqF(&result, size_returned, DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", size_returned, DN_ArrayCountU(EXPECTED) - 1); + DN_TestVerifyExpr(&result, DN_Memcmp(EXPECTED, string, sizeof(EXPECTED)) == 0); + } + + for (DN_TestScopeF(&result, "[Win] Str16 to Str8: Arena null terminates string")) { + DN_Str8 string8 = DN_OS_W32Str16ToStr8(&scratch.arena, input16); + int size_returned = DN_OS_W32Str16ToStr8Buffer(input16, nullptr, 0); + char const EXPECTED[] = {'S', 't', 'r', 'i', 'n', 'g', 0}; + + DN_TestVerifyUSizeEqF(&result, DN_Cast(int) string8.count, size_returned, "string_size: %d, result: %d", DN_Cast(int) string8.count, size_returned); + DN_TestVerifyUSizeEqF(&result, DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1, "string_size: %d, expected: %zu", DN_Cast(int) string8.count, DN_ArrayCountU(EXPECTED) - 1); + DN_TestVerifyExpr(&result, DN_Memcmp(EXPECTED, string8.data, sizeof(EXPECTED)) == 0); + } + + DN_TCScratchEnd(&scratch); + } + #endif // DN_PLATFORM_WIN32 + + // NOTE: NET + #if DN_WITH_NET + { + struct NETEntry + { + DN_Str8 label; + DN_NETInterface net_interface; + }; + + NETEntry entries[2] = {}; + DN_USize entries_count = 0; + + { + #if defined(DN_PLATFORM_EMSCRIPTEN) + NETEntry *entry = entries + entries_count++; + entry->label = DN_Str8Lit("[NET] Emscripten"); + entry->net_interface = DN_NET_EmcInterface(); + #endif + } + + { + #if DN_WITH_NET_CURL + NETEntry *entry = entries + entries_count++; + entry->label = DN_Str8Lit("[NET] CURL"); + entry->net_interface = DN_NET_CurlInterface(); + #endif + } + + for (DN_ForItSize(entry_it, NETEntry, entries, entries_count)) { + NETEntry *entry = entry_it.data; + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); + DN_Str8 remote_ws_server_url = DN_Str8Lit("wss://echo.websocket.org"); + DN_Str8 remote_http_server_url = DN_Str8Lit("https://google.com"); + + DN_USize net_base_size = DN_Megabytes(1); + char *net_base = DN_ArenaNewArray(&arena, char, net_base_size, DN_ZMem_Yes); + DN_NETCore net = {}; + DN_NETInterface *net_interface = &entry->net_interface; + net_interface->init(&net, net_base, net_base_size); + + DN_U64 arena_reset_p = DN_MemListPos(arena.mem); + for (DN_TestScopeF(&result, "%.*s: WaitForResponse HTTP GET request", DN_Str8PrintFmt(entry->label))) { + DN_NETRequestHandle request = net_interface->do_http(&net, remote_http_server_url, DN_Str8Lit("GET"), nullptr); + DN_NETResponse response = net_interface->wait_for_response(request, &arena, UINT32_MAX); + DN_TestVerifyUSizeNotEq (&result, response.http_status, 0); + DN_TestVerifyUSizeEq (&result, response.state, DN_NETResponseState_HTTP); + DN_TestVerifyUSizeEq (&result, response.error_str8.count, 0); + DN_TestVerifyUSizeGreaterThan(&result, response.body.count, 0); + } + + for (DN_TestScopeF(&result, "%.*s: WaitForResponse HTTP POST request", DN_Str8PrintFmt(entry->label))) { + net_interface->do_http(&net, remote_http_server_url, DN_Str8Lit("POST"), nullptr); + DN_NETResponse response = net_interface->wait_for_any_response(&net, &arena, UINT32_MAX); + DN_TestVerifyUSizeNotEq (&result, response.http_status, 0); + DN_TestVerifyUSizeEq (&result, response.state, DN_NETResponseState_HTTP); + DN_TestVerifyUSizeEq (&result, response.error_str8.count, 0); + DN_TestVerifyUSizeGreaterThan(&result, response.body.count, 0); + } + + for (DN_TestScopeF(&result, "%.*s: WaitForResponse WS request", DN_Str8PrintFmt(entry->label))) { + DN_NETRequestHandle request = net_interface->do_ws(&net, remote_ws_server_url); + DN_USize const WS_TIMEOUT_MS = 16; + + // NOTE: Wait for WS connection to open + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSOpen, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + done = true; + } + + // NOTE: Receive the initial text from the echo server + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSText, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + net_interface->do_ws_send(request, DN_Str8Lit(""), DN_NETWSSend_Close); + done = true; + } + + // NOTE: Expect to hear the close + for (bool done = false; !done; DN_MemListPopTo(arena.mem, arena_reset_p)) { + DN_NETResponse response = net_interface->wait_for_response(request, &arena, WS_TIMEOUT_MS); + if (response.state == DN_NETResponseState_Nil) // NOTE: Timeout + continue; + if (response.state == DN_NETResponseState_Error) + DN_TestVerifyUSizeEqF(&result, response.state, DN_NETResponseState_WSClose, "ERROR: %.*s", DN_Str8PrintFmt(response.error_str8)); + done = true; + } + } + net_interface->deinit(&net); + DN_MemListDeinit(arena.mem); + } + } + #endif // #if DN_WITH_NET + } + return result; +} +DN_MSVC_WARNING_POP +#endif // #if DN_WITH_TESTS + DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) { if (!ptr) @@ -9157,11 +11220,12 @@ DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, b DN_TicketMutex_Begin(&leak->alloc_table_mutex); - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapMakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); - DN_LeakAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { + DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_HTable* alloc_table = &leak->alloc_table; + DN_UPtr uptr = DN_Cast(DN_UPtr)ptr; + DN_HTableAddResult alloc_entry = DN_HTableMake(alloc_table, &uptr); + DN_LeakAlloc* alloc = DN_Cast(DN_LeakAlloc *)alloc_entry.slot.value; + if (alloc_entry.existed) { if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { DN_Str8x32 alloc_size = DN_Str8x32FromByteCountU64Auto(alloc->size); DN_Str8x32 new_alloc_size = DN_Str8x32FromByteCountU64Auto(size); @@ -9210,16 +11274,16 @@ DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) DN_TicketMutex_Begin(&leak->alloc_table_mutex); - DN_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &leak->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMapFindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); - DN_AssertAlwaysF(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_Str8 stack_trace = DN_Str8FromStackTraceNowHeap(128, 3 /*skip*/); + DN_HTable* alloc_table = &leak->alloc_table; + DN_UPtr uptr = DN_Cast(DN_UPtr)ptr; + DN_LeakAlloc* alloc = DN_Cast(DN_LeakAlloc *)DN_HTableValueFromFind(alloc_table, &uptr); + DN_AssertAlwaysF(alloc, + "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_LeakAlloc *alloc = alloc_entry.value; if (alloc->flags & DN_LeakAllocFlag_Freed) { DN_Str8x32 freed_size = DN_Str8x32FromByteCountU64Auto(alloc->freed_size); DN_AssertAlwaysF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, @@ -9256,11 +11320,14 @@ DN_API void DN_LeakDump_(DN_LeakTracker *leak) { DN_U64 leak_count = 0; DN_U64 leaked_bytes = 0; - for (DN_USize index = 1; index < leak->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = leak->alloc_table.slots + index; - DN_LeakAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); + for (DN_ForItSize(it, DN_LeakTrackerKV, leak->alloc_table_kvs, leak->alloc_table.max)) { + DN_LeakTrackerKV *kv = it.data; + if (kv->hash < DN_HTableHashSentinel_FirstValid) + continue; + + DN_LeakAlloc *alloc = &kv->value; + bool alloc_leaked = (alloc->flags & DN_LeakAllocFlag_Freed) == 0; + bool leak_permitted = (alloc->flags & DN_LeakAllocFlag_LeakPermitted); if (alloc_leaked && !leak_permitted) { leaked_bytes += alloc->size; leak_count++; @@ -9286,127 +11353,7 @@ DN_API void DN_LeakDump_(DN_LeakTracker *leak) #include // getpagesize #endif -static void *DN_OS_MemFuncsHeapAllocShim_(DN_USize size) -{ - void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsFromType(DN_MemFuncsType type) -{ - DN_MemFuncs result = {}; - result.type = type; - switch (type) { - case DN_MemFuncsType_Nil: break; - case DN_MemFuncsType_Heap: { - result.heap_alloc = DN_OS_MemFuncsHeapAllocShim_; - result.heap_dealloc = DN_OS_MemDealloc; - } break; - - case DN_MemFuncsType_Virtual: { - DN_Core *dn = DN_Get(); - DN_Assert(dn->init_flags & DN_InitFlags_OS); - result.virtual_page_size = dn->os.page_size; - result.virtual_reserve = DN_OS_MemReserve; - result.virtual_commit = DN_OS_MemCommit; - result.virtual_release = DN_OS_MemRelease; - } break; - } - return result; -} - -DN_API DN_MemFuncs DN_MemFuncsDefault() -{ - DN_Core *dn = DN_Get(); - DN_MemFuncsType type = DN_MemFuncsType_Heap; - if (dn->os_init) { -#if !defined(DN_PLATFORM_EMSCRIPTEN) - type = DN_MemFuncsType_Virtual; -#endif - } - DN_MemFuncs result = DN_MemFuncsFromType(type); - return result; -} - -DN_API DN_MemList DN_MemListFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Heap); - DN_MemList result = DN_MemListFromMemFuncs(size, size, flags, mem_funcs); - return result; -} - -DN_API DN_MemList DN_MemListFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemFuncs mem_funcs = DN_MemFuncsFromType(DN_MemFuncsType_Virtual); - DN_MemList result = DN_MemListFromMemFuncs(reserve, commit, flags, mem_funcs); - return result; -} - -DN_API DN_Arena DN_ArenaFromHeap(DN_U64 size, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromHeap(size, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_MemFlags flags) -{ - DN_MemList mem = DN_MemListFromVMem(reserve, commit, flags); - DN_Arena result = {}; - result.flags |= DN_ArenaFlags_OwnsMemList; - result.mem = DN_MemListNewCopy(&mem, DN_MemList, &mem); - return result; -} - -DN_API void DN_ArenaDeinit(DN_Arena *arena) -{ - if (arena->flags & DN_ArenaFlags_OwnsMemList) - DN_MemListDeinit(arena->mem); -} - -DN_API DN_Str8 DN_Str8FromHeapF(DN_FMT_ATTRIB char const *fmt, ...) -{ - va_list args; - va_start(args, fmt); - DN_USize count = DN_FmtVCount(fmt, args); - DN_Str8 result = DN_Str8FromHeap(count, DN_ZMem_No); - DN_VSNPrintF(result.data, DN_Cast(int)(result.count + 1), fmt, args); - va_end(args); - return result; -} - -DN_API DN_Str8 DN_Str8FromHeap(DN_USize count, DN_ZMem z_mem) -{ - DN_Str8 result = {}; - result.data = DN_Cast(char *)DN_OS_MemAlloc(count + 1, z_mem); - if (result.data) { - result.count = count; - result.data[result.count] = 0; - } - return result; -} - -DN_API DN_Str8 DN_Str8PadNewLines(DN_Arena *arena, DN_Str8 src, DN_Str8 pad) -{ - // TODO: Implement this without requiring TLS so it can go into base strings - DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - DN_Str8BSplitResult split = DN_Str8BSplit(src, DN_Str8Lit("\n")); - while (split.lhs.count) { - DN_Str8BuilderAppendRef(&builder, pad); - DN_Str8BuilderAppendRef(&builder, split.lhs); - split = DN_Str8BSplit(split.rhs, DN_Str8Lit("\n")); - if (split.lhs.count) - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("\n")); - } - DN_Str8 result = DN_Str8FromStr8BuilderArena(&builder, arena); - DN_TCScratchEnd(&scratch); - return result; -} - -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap(DN_Str8Builder const *builder) +DN_API DN_Str8 DN_OS_Str8FromStr8BuilderHeap(DN_Str8Builder const *builder) { DN_Str8 result = DN_ZeroInit; if (!builder || builder->string_size <= 0 || builder->count <= 0) @@ -9519,6 +11466,44 @@ DN_API void DN_OS_SetLogPrintFuncToOS() DN_LogSetPrintFunc(DN_OS_LogPrint, &dn->os); } + +DN_API void *DN_OS_HeapBasicAlloc(DN_USize size) +{ + void *result = DN_OS_MemAlloc(size, DN_ZMem_Yes); + return result; +} + +DN_API void DN_OS_HeapBasicDealloc(void *ptr) +{ + DN_OS_MemDealloc(ptr); +} + +DN_API DN_Heap DN_OS_HeapInitBasic() +{ + DN_Heap result = DN_HeapInitBasic(DN_OS_HeapBasicAlloc, DN_OS_HeapBasicDealloc); + return result; +} + +DN_API DN_Heap DN_OS_HeapInitVirtual() +{ + DN_Core *dn = DN_Get(); + DN_Assert(dn->init_flags & DN_InitFlags_OS); + DN_Assert(dn->os.page_size); + DN_Heap result = DN_HeapInitVirtual(dn->os.page_size, DN_OS_MemReserve, DN_OS_MemCommit, DN_OS_MemRelease); + return result; +} + +DN_API DN_Heap DN_OS_HeapInitDefault() +{ + DN_Heap result = {}; + #if defined(DN_PLATFORM_EMSCRIPTEN) + result = DN_OS_HeapInitBasic(); + #else + result = DN_OS_HeapInitVirtual(); + #endif + return result; +} + // NOTE: Date DN_API DN_Str8x32 DN_OS_DateLocalTimeStr8(DN_Date time, char date_separator, char hms_separator) { @@ -10014,7 +11999,7 @@ DN_API DN_OSExecResult DN_OS_ExecOrAbort(DN_Str8Slice cmd_line, DN_OSExecArgs *a static void DN_OS_ThreadExecute_(void *user_context) { DN_OSThread *thread = DN_Cast(DN_OSThread *) user_context; - DN_TCInitFromMemFuncs(&thread->context, thread->thread_id, thread->tc_init_args, DN_MemFuncsDefault()); + DN_TCInitFromHeap(&thread->context, thread->thread_id, thread->tc_init_args, DN_OS_HeapInitDefault()); DN_TCEquip(&thread->context); if (thread->is_lane_set) { DN_OS_TCThreadLaneEquip(thread->lane); @@ -10026,6 +12011,19 @@ static void DN_OS_ThreadExecute_(void *user_context) thread->func(thread); } +DN_API DN_OSThreadInitArgs DN_OS_ThreadInitArgsDefault() +{ + DN_OSThreadInitArgs result = {}; + result.tc_args = DN_TCInitArgsDefault(); + return result; +} + +DN_API bool DN_OS_ThreadInit(DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadInitArgs init_args, void *user_context) +{ + bool result = DN_OS_ThreadInitLane(thread, func, nullptr, init_args, user_context); + return result; +} + DN_API void DN_OS_ThreadSetNameFmt(char const *fmt, ...) { DN_TCCore *tls = DN_TCGet(); @@ -10120,11 +12118,11 @@ DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena(DN_USize threads_count, D return result; } -DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context) +DN_API void DN_OS_ThreadLanewayDispatch(DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_OSThreadInitArgs init_args, void *user_context) { for (DN_ForItSize(it, DN_OSThread, laneway->threads, laneway->threads_count)) { DN_OSThreadLane lane = DN_OS_ThreadLaneInit(it.index, laneway->threads_count, laneway->barrier, laneway->shared_mem); - DN_OS_ThreadInit(it.data, entry_point, &lane, tc_init_args, user_context); + DN_OS_ThreadInitLane(it.data, entry_point, &lane, init_args, user_context); } } @@ -10205,7 +12203,7 @@ DN_API void DN_OS_AsyncInit(DN_OSAsyncCore *async, char *base, DN_USize base_siz async->threads = threads; for (DN_ForIndexU(index, async->thread_count)) { DN_OSThread *thread = async->threads + index; - DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, /*lane=*/ nullptr, DN_TCInitArgsDefault(), async); + DN_OS_ThreadInit(thread, DN_OS_AsyncThreadEntryPoint_, DN_OS_ThreadInitArgsDefault(), async); } } @@ -10594,11 +12592,11 @@ DN_API DN_Str8 DN_Str8FromStackTraceNowArena(DN_Arena *arena, DN_U16 limit, DN_U DN_API DN_Str8 DN_Str8FromStackTraceNowHeap(DN_U16 limit, DN_U16 skip) { - DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_MemFlags_NoAllocTrack); + DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_Kilobytes(64), DN_MemFlags_NoAllocTrack, DN_OS_HeapInitBasic()); DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); DN_StackTrace walk = DN_StackTraceFromArena(&arena, limit); DN_StackTraceAddToStr8Builder_(&walk, &builder, skip); - DN_Str8 result = DN_Str8BuilderBuildFromHeap(&builder); + DN_Str8 result = DN_OS_Str8FromStr8BuilderHeap(&builder); DN_ArenaDeinit(&arena); return result; } @@ -10697,10 +12695,6 @@ DN_API void DN_StackTraceReloadSymbols() #endif #endif // DN_WITH_OS -#if DN_WITH_TESTS -#include "Extra/dn_tests.cpp" -#endif - #if DN_WITH_NET DN_Str8 DN_NET_Str8FromResponseState(DN_NETResponseState state) { @@ -10764,15 +12758,14 @@ DN_Str8 DN_NET_Str8DiagnosticFromResponse(DN_NETResponse const* resp, DN_Arena * { DN_TCScratch scratch = DN_TCScratchBeginArena(&arena, 1); DN_Str8Builder builder = DN_Str8BuilderFromArena(&scratch.arena); - bool resp_failed = DN_NET_ResponseHasFailed(resp); - DN_Str8BuilderAppendF(&builder, "Request %s (%s", resp_failed ? "failed" : "succeeded", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); + DN_Str8BuilderAppendF(&builder, "Request (%s", resp->type == DN_NETRequestType_HTTP ? "HTTP" : "WS"); if (resp->type == DN_NETRequestType_HTTP) { if (resp->http_status) DN_Str8BuilderAppendF(&builder, " %u", resp->http_status); } DN_Str8BuilderAppendF(&builder, ")"); if (resp->body.count || resp->error_str8.count) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" with ")); + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" reported: ")); if (resp->body.count) DN_Str8BuilderAppendF(&builder, "%.*s", DN_Str8PrintFmt(resp->body)); if (resp->error_str8.count) @@ -10798,7 +12791,7 @@ DN_NETRequestHandle DN_NET_SetupRequest(DN_NETRequest *request, DN_Str8 url, DN_ DN_Assert(request); if (request) { if (!request->mem.curr) - request->mem = DN_MemListFromVMem(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil); + request->mem = DN_MemListFromHeap(DN_Megabytes(1), DN_Kilobytes(1), DN_MemFlags_Nil, DN_OS_HeapInitVirtual()); request->arena = DN_ArenaTempBeginFromMemList(&request->mem); request->type = type; request->gen = DN_Max(request->gen + 1, 1); @@ -11262,7 +13255,7 @@ void DN_NET_CurlInit(DN_NETCore *net, char *base, DN_U64 base_size) curl->thread_curlm = DN_Cast(CURLM *) curl_multi_init(); DN_FmtAppend(curl->thread.name.data, &curl->thread.name.count, sizeof(curl->thread.name.data), "NET (CURL)"); - DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, nullptr, DN_TCInitArgsDefault(), net); + DN_OS_ThreadInit(&curl->thread, DN_NET_CurlThreadEntryPoint_, DN_OS_ThreadInitArgsDefault(), net); } void DN_NET_CurlDeinit(DN_NETCore *net) diff --git a/Source/dn.h b/Source/dn.h index 8ec373b..8243d14 100644 --- a/Source/dn.h +++ b/Source/dn.h @@ -3,208 +3,232 @@ // NOTE: DN -// NOTE: Getting Started -// - Copy the entire DN folder to your project and include `dn.h` alongside your headers and -// include `dn.cpp` in one of your implementation files that has `dn.h` included in its scope, -// an example. There are #defines to selectively enable features of the library (see the -// configuration section for more info), example: +// Getting Started +// Copy the entire DN folder to your project and include `dn.h` alongside your headers and include +// `dn.cpp` in one of your implementation files that has `dn.h` included in its scope. +// There are #defines to selectively enable/disable features of the library. +// +// For this example, we enable _every_ feature possible to show all the possibilities upfront. +// All the #defines can be omitted and a default value will be set (in general everything defaults +// to off/disabled or fairly conservative). For more information see the configuration section. /* - #define DN_WITH_OS 1 // Enable OS features (like virtual mem, TLS, file system, threads) - #include "dn.h" + #define DN_WITH_OS 1 // Enable OS features (e.g.: virtual mem, TLS, file system, threads) + #define DN_WITH_STR8_AVX512F 1 // Enable AVX512F accelerated string functions + #define DN_WITH_NET 0 // Enable NET interface for HTTP/WS requests functionality. Requires CURL or Emscrpten + #define DN_WITH_NET_CURL 0 // Enable NET interface implementation using CURL's networking APIs (you must link libcurl to this TU unit) + #define DN_WITH_NET_EMSCRIPTEN 0 // Enable NET interface implementation using Emscripten's networking APIs (you must link emscripten to this TU unit) + #define DN_WITH_TESTS 1 // Enable the unit tests that can be run with `DN_TestSuite(arena)` + #define DN_ASAN_POISON 0 // Poison uncommitted memory in an Arena with Address Sanitizer's poisoning API + #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD // Temporary memory freed by an arena is scrubbed with this value to help catch UAFs + #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 // Enable Arenas to detect UAF when using temporary memory regions + #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 0 // Record stack traces when a UAF guard on an arena is created for better UAF crash diagnostics + #define DN_PARANOIA_LEVEL 1 // Enables various aforementioned debug functionality automatically, supported values [0..2] + #include "dn.h" + #include "dn.cpp" - #define DN_CPP_WITH_TESTS 1 - #include "dn.cpp" - - int main() - { - DN_Core core = {}; - DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); - return 0; - } + int main(int argc, char **argv) + { + DN_Core core = {}; + DN_Init(&core, DN_InitFlags_Nil, DN_TCInitArgsDefault()); + return 0; + } */ -// - `DN/Standalone` contains utilities that are self-contained and can be used without `dn.h` in -// a similar manner, typically a single header and single implementation file. +// `DN/Standalone` contains utilities that are self-contained and can be used without `dn.h` in +// a similar manner, typically a single header and single implementation file. -// NOTE: Configuration +// Configuration -// NOTE: OS layer -// Enable the operating system layer which enables thread context, file system, threads, e.t.c: +// OS layer +// Enable the operating system layer which enables thread context, file system, threads, e.t.c +// (default: 0) // -// #define DN_WITH_OS 1 +// #define DN_WITH_OS 1 -// NOTE: Networking layer -// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending -// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. +// Str8 AVX512F Layer +// AVX512 string functions can be compiled and made available (default: 0) // -// #define DN_WITH_NET 1 -// #define DN_WITH_NET_CURL 1 -// #define DN_WITH_NET_EMSCRIPTEN 1 -// -// For CURL, ensure that you the target application links to a libcurl and that the -// #include is visible before this translation unit. -// -// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. +// #define DN_WITH_STR8_AVX512F 1 -// NOTE: Platform Target -// Define one of the following directives to configure this library to compile for that -// platform. By default, the library will auto-detect the current host platform and select that -// as the target platform. +// Unit Tests +// Unit tests can be compiled and run using the `DN_TestSuite(...)` function (default: 0) // -// DN_PLATFORM_EMSCRIPTEN -// DN_PLATFORM_POSIX -// DN_PLATFORM_WIN32 -// -// For example -// -// #define DN_PLATFORM_WIN32 -// -// Will ensure that is included and the OS layer is implemented using Win32 -// primitives. +// #define DN_WITH_TESTS 1 -// NOTE: Static functions -// All public functions in the DN library are prefixed with the macro '#define DN_API'. By -// default 'DN_API' is not defined to anything. Define +// Networking layer +// Enable the networking layer (pre-requisite that the OS layer is enabled) that allows sending +// HTTP/Websocket requests either using CURL or Emscripten's networking APIs. // -// DN_STATIC_API +// #define DN_WITH_NET 1 +// #define DN_WITH_NET_CURL 1 +// #define DN_WITH_NET_EMSCRIPTEN 1 // -// To replace all the functions prefixed with DN_API to be prefixed with 'static' ensuring that -// the functions in the library do not export an entry into the linking table. -// translation units. +// For CURL, ensure that you the target application links to a libcurl and that the +// #include is visible before this translation unit. +// +// For Emscripten ensure that this translation unit is compiled with `emcc` to have it enabled. -// NOTE: Disabling the in-built (if #define DN_WITH_OS 1) -// If you are building DN for the Windows platform, is a large legacy header that -// applications have to include to use Windows APIs. By default this library uses a replacement -// header for all the Windows functions that it uses in the OS layer removing the need to -// include to improve compilation times. This mini header will conflict with -// if it needs to be included in your project. The mini header can be disabled by -// defining: +// Platform Target +// Define one of the following directives to configure this library to compile for that +// platform. By default, the library will auto-detect the current host platform and select that +// as the target platform. // -// DN_NO_WINDOWS_H_REPLACEMENT_HEADER +// DN_PLATFORM_EMSCRIPTEN +// DN_PLATFORM_POSIX +// DN_PLATFORM_WIN32 // -// To instead use . DN automatically detects if is included in an earlier -// translation unit and will automatically disable the in-built replacement header in which case -// this does not need to be defined. +// For example +// +// #define DN_PLATFORM_WIN32 +// +// Will ensure that is included and the OS layer is implemented using Win32 +// primitives. -// NOTE: Freestanding -// The base layer can be used without an OS implementation by defining DN_FREESTANDING like: +// Static functions +// All public functions in the DN library are prefixed with the macro '#define DN_API'. By +// default 'DN_API' is not defined to anything (default: nil) // -// #define DN_FREESTANDING +// #define DN_API // -// This means functionality that relies on the OS like printing, memory allocation, stack traces -// and so forth are disabled. +// To replace all the functions prefixed with DN_API to be prefixed with 'static' ensuring that +// the functions in the library do not export an entry into the linking table. +// translation units. +// +// #define DN_API static -// NOTE: ASAN Arena Poisoning -// When compiled with address sanitizer (.e.g -fsanitize=address) you can optionally enable -// memory region poisoning on the inbuilt arena's to catch in certain scenarios, use-after-free +// Disabling the in-built (if #define DN_WITH_OS 1) +// If you are building DN for the Windows platform, is a large legacy header that +// applications have to include to use Windows APIs. By default this library uses a replacement +// header for all the Windows functions that it uses in the OS layer removing the need to +// include to improve compilation times. This mini header will conflict with +// if it needs to be included in your project. The mini header can be disabled by +// defining: // -// #define DN_ASAN_POISON 1 +// #define DN_NO_WINDOWS_H_REPLACEMENT_HEADER // -// Since arenas manage their own block of memory it does not automatically benefit from ASAN's -// memory markup that ASAN does and so it is implemented manually by using the ASAN user-level -// poisoning APIs. Similarly, since the arena recycles its own memory rather than release back -// to the OS, poisoning is not as effective for arenas but every little bit helps. +// To instead use . DN automatically detects if is included in an earlier +// translation unit and will automatically disable the in-built replacement header in which case +// this does not need to be defined. -// NOTE: Scrub Uninitialised Memory -// If this macro is defined, temp memory that is returned to an arena, or allocations freed by -// a pool are scrubbed to this specified byte, in absence of this bytes returned to the -// allocators are left as-is or memset to 0. For example to scrub bytes to 0xCD (MSVC's -// pattern) define as follows: +// Freestanding +// TODO: This is not really supported or dealt with properly. Re-review it +// The base layer can be used without an OS implementation by defining DN_FREESTANDING like: // -// #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD +// #define DN_FREESTANDING // -// Due to the recycling of memory in arenas and pool, similarly to ASAN poisoning this reduces -// the window in which a use-after-free can be detected using this guard, however every little -// bit helps. +// This means functionality that relies on the OS like printing, memory allocation, stack traces +// and so forth are disabled. -// NOTE: Arena temp memory use-after-free (UAF) tooling -// UAF Guard -// Set the following preprocessor value to 1 to enable UAF protection when using -// scratch/temporary memory functionality. Defaults to off, or 0 if not specified +// ASAN Arena Poisoning +// When compiled with address sanitizer (.e.g -fsanitize=address) you can optionally enable +// memory region poisoning on the inbuilt arena's to catch in certain scenarios, use-after-free +// (default: ON if PARANOIA level is >=1, OFF otherwise) // -// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 +// #define DN_ASAN_POISON 1 // -// This enables arenas to markup itself with the active memory region and subsequent -// allocations check if the allocation belongs to the same or different region. Different -// regions cause a UAF violation. -// -// More detailed diagnostics can be enabled by setting the flag DN_ArenaFlags_TempMemUAFGuard -// on the affected arenas. Note that this incurs a performance penalty as each memory region -// will store a stacktrace of its creation. It's recommended in development builds to always -// run with temp-memory guarding, if a violation occurs, then enable tracing on the arena to -// pinpoint the issue. -// -// Enabling memory guard incurs additional memory requirements from the arena's backing -// memory block and additional book-keeping fields on each arena and their temp memory -// instances. -// -// NOTE: UAF Tracing -// Set the following preprocessor value to 1 to enable tracing when the UAF guard triggers. -// Defaults to off, or 0 if not specified. -// -// #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 -// -// This opts in all arenas to tracing functionality by default globally. Arenas can opt out -// by setting the flag DN_ArenaFlags_TempMemUAFTraceDisable on the arena. The disable flag -// takes precedence over all other settings including the global preprocessor macro and the -// enablement flag (DN_ArenaFlags_TempMemUAFTrace). -// -// Tracing incurs an additional much heavier performance penalty than the UAF guard due to -// the stacktrace that is stored per region to report to the user when a UAF guard violation -// occurs. +// Since arenas manage their own block of memory it does not automatically benefit from ASAN's +// memory markup that ASAN does and so it is implemented manually by using the ASAN user-level +// poisoning APIs. Similarly, since the arena recycles its own memory rather than release back +// to the OS, poisoning is not as effective for arenas but every little bit helps. -// NOTE: Paranoia Level -// Set the `DN_PARANOIA_LEVEL` to an integer value to enable various validation layers and -// error checking mechanisms in the codebase and primitives exposed by the library. Defaults to -// paranoia level 0 in release builds and level 1 for debug. +// Scrub Uninitialised Memory +// If this macro is defined, temp memory that is returned to an arena, or allocations freed by +// a pool are scrubbed to this specified byte, in absence of this bytes returned to the +// allocators are left as-is or memset to 0. For example to scrub bytes to 0xCD (MSVC's +// pattern) (default: 0xCD if PARANOIA level is >=1, 0x0 otherwise) // -// #define DN_PARANOIA_LEVEL 1 +// #define DN_SCRUB_UNINIT_MEM_BYTE 0xCD // -// Each level activates the following debug mechanisms. Note that any of the following #defines -// enabled by a paranoia level can be overridden by defining the preprocessor definition before -// the inclusion of this file. +// Due to the recycling of memory in arenas and pool, similarly to ASAN poisoning this reduces +// the window in which a use-after-free can be detected using this guard, however every little +// bit helps. + +// Arena temp memory use-after-free (UAF) tooling +// UAF Guard +// Set the following preprocessor value to 1 to enable UAF protection when using +// scratch/temporary memory functionality (default: 1 if PARANOIA level >=1, 0 otherwise) // -// Level 0 -// `DN_Assert` calls are compiled out +// #define DN_ARENA_TEMP_MEM_UAF_GUARD 1 // -// `DN_Verify` calls logs an error and continues +// This enables arenas to markup itself with the active memory region and subsequent +// allocations check if the allocation belongs to the same or different region. Different +// regions cause a UAF violation. // -// `DN_VerifyWarning` calls logs a warning and continues +// More detailed diagnostics can be enabled by setting the flag DN_ArenaFlags_TempMemUAFGuard +// on the affected arenas. Note that this incurs a performance penalty as each memory region +// will store a stacktrace of its creation. It's recommended in development builds to always +// run with temp-memory guarding, if a violation occurs, then enable tracing on the arena to +// pinpoint the issue. // -// Level 1 -// `DN_Assert` calls are compiled in +// Enabling memory guard incurs additional memory requirements from the arena's backing +// memory block and additional book-keeping fields on each arena and their temp memory +// instances. // -// `DN_Verify` calls a debug trap rather than just logging and continuing +// UAF Tracing +// Set the following preprocessor value to 1 to enable tracing when the UAF guard triggers +// (default: 1 if PARANOIA level >=2, 0 otherwise) // -// `DN_Verify` calls dump a stack trace when triggered +// #define DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT 1 // -// `DN_ASAN_POISON` is set. When an arena allocates memory unallocated bytes from the -// memory owned by the arena are manually poisoned using ASAN. A fault will be triggered if -// the memory is written to (UAF e.g. use-after-free). Address sanitizer must be enabled or -// otherwise this is a no-op. This incurs a performance penalty on-top of the overhead of -// running ASAN on your binary as recycling memory calls into ASAN to poison the region. +// This opts in all arenas to tracing functionality by default globally. Arenas can opt out +// by setting the flag DN_ArenaFlags_TempMemUAFTraceDisable on the arena. The disable flag +// takes precedence over all other settings including the global preprocessor macro and the +// enablement flag (DN_ArenaFlags_TempMemUAFTrace). // -// `DN_ARENA_TEMP_MEM_UAF_GUARD` is set. When an arena uses temporary memory it will record -// the active temporary memory region and compare them when allocating to ensure that -// memory is allocated in the active region otherwise a UAF fault is triggered. This has a -// small runtime performance penalty. +// Tracing incurs an additional much heavier performance penalty than the UAF guard due to +// the stacktrace that is stored per region to report to the user when a UAF guard violation +// occurs. + +// Paranoia Level +// Set the `DN_PARANOIA_LEVEL` to an integer value to enable various validation layers and +// error checking mechanisms in the codebase and primitives exposed by the library +// (default: PARANOID level 1 in debug builds, 0 otherwise) +// paranoia level 0 in release builds and level 1 for debug. // -// `DN_SCRUB_UNINIT_MEM_BYTE` is set to `0xCD`. When memory is cleared in an arena or a -// pool backed by an arena upon deallocation if the `DN_ZMem_Yes` flag is passed then the -// bytes are scrubbed to this byte to make UAF more salient. +// #define DN_PARANOIA_LEVEL 1 // -// Level 2 -// `DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT` is set. When an arena uses temporary memory -// regions that region's a stack trace of the call site is recorded. This is very expensive -// but when a temporary memory region is used after it has been deallocated, a full stack -// trace diagnostic is available of where the various regions where created and freed. +// Each level activates the following debug mechanisms. Note that any of the following #defines +// enabled by a paranoia level can be overridden by defining the preprocessor definition before +// the inclusion of this file. // -// NOTE: Str8 AVX512F variants -// We have some AVX512 string functions that can be enabled by defining the following +// Level 0 +// `DN_Assert` calls are compiled out +// +// `DN_Verify` calls logs an error and continues +// +// `DN_VerifyWarning` calls logs a warning and continues +// +// Level 1 +// `DN_Assert` calls are compiled in +// +// `DN_Verify` calls a debug trap rather than just logging and continuing +// +// `DN_Verify` calls dump a stack trace when triggered +// +// `DN_ASAN_POISON` is set. When an arena allocates memory unallocated bytes from the +// memory owned by the arena are manually poisoned using ASAN. A fault will be triggered if +// the memory is written to (UAF e.g. use-after-free). Address sanitizer must be enabled or +// otherwise this is a no-op. This incurs a performance penalty on-top of the overhead of +// running ASAN on your binary as recycling memory calls into ASAN to poison the region. +// +// `DN_ARENA_TEMP_MEM_UAF_GUARD` is set. When an arena uses temporary memory it will record +// the active temporary memory region and compare them when allocating to ensure that +// memory is allocated in the active region otherwise a UAF fault is triggered. This has a +// small runtime performance penalty. +// +// `DN_SCRUB_UNINIT_MEM_BYTE` is set to `0xCD`. When memory is cleared in an arena or a +// pool backed by an arena upon deallocation if the `DN_ZMem_Yes` flag is passed then the +// bytes are scrubbed to this byte to make UAF more salient. +// +// Level 2 +// `DN_ARENA_TEMP_MEM_UAF_TRACE_ON_BY_DEFAULT` is set. When an arena uses temporary memory +// regions that region's a stack trace of the call site is recorded. This is very expensive +// but when a temporary memory region is used after it has been deallocated, a full stack +// trace diagnostic is available of where the various regions where created and freed. // -// #define DN_STR8_AVX512F 1 #if defined(_CLANGD) #define DN_WITH_OS 1 + #define DN_WITH_TESTS 1 #define DN_STR8_AVX512F 1 #define DN_PARANOIA_LEVEL 1 #endif @@ -304,10 +328,8 @@ #define DN_FORCE_INLINE inline __attribute__((always_inline)) #endif -// NOTE: Function/Variable Annotations -#if defined(DN_STATIC_API) - #define DN_API static -#else +// NOTE: Function Prefix +#if !defined(DN_API) #define DN_API #endif @@ -1088,28 +1110,33 @@ enum DN_MemPage_ #define DN_ARENA_COMMIT_SIZE DN_Kilobytes(64) #endif -enum DN_MemFuncsType +enum DN_HeapType { - DN_MemFuncsType_Nil, - DN_MemFuncsType_Heap, - DN_MemFuncsType_Virtual, + DN_HeapType_Nil, + DN_HeapType_Basic, + DN_HeapType_Virtual, }; -typedef void *(DN_MemHeapAllocFunc)(DN_USize count); -typedef void (DN_MemHeapDeallocFunc)(void *ptr); -typedef void *(DN_MemVirtualReserveFunc)(DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); -typedef bool (DN_MemVirtualCommitFunc)(void *ptr, DN_USize count, DN_U32 page_flags); -typedef void (DN_MemVirtualReleaseFunc)(void *ptr, DN_USize count); -struct DN_MemFuncs +typedef void *(DN_HeapBasicAllocFunc) (DN_USize count); +typedef void (DN_HeapBasicDeallocFunc)(void *ptr); + +typedef void *(DN_HeapVirtualReserveFunc)(DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); +typedef bool (DN_HeapVirtualCommitFunc)(void *ptr, DN_USize count, DN_U32 page_flags); +typedef void (DN_HeapVirtualReleaseFunc)(void *ptr, DN_USize count); +struct DN_Heap { - DN_MemFuncsType type; - DN_MemHeapAllocFunc *heap_alloc; - DN_MemHeapDeallocFunc *heap_dealloc; + DN_HeapType type; + DN_USize bytes_alloc_total; + DN_USize bytes_alloc; + DN_USize bytes_freed; + + DN_HeapBasicAllocFunc* basic_alloc; + DN_HeapBasicDeallocFunc* basic_dealloc; DN_U32 virtual_page_size; - DN_MemVirtualReserveFunc *virtual_reserve; - DN_MemVirtualCommitFunc *virtual_commit; - DN_MemVirtualReleaseFunc *virtual_release; + DN_HeapVirtualReserveFunc *virtual_reserve; + DN_HeapVirtualCommitFunc *virtual_commit; + DN_HeapVirtualReleaseFunc *virtual_release; }; struct DN_MemBlock @@ -1165,15 +1192,15 @@ enum DN_MemFlags_ DN_MemFlags_TempMemUAFTraceDisable = 1 << 6, // NOTE: Internal flags. Do not use - DN_MemFlags_UserBuffer = 1 << 7, - DN_MemFlags_MemFuncs = 1 << 8, + DN_MemFlags_UserBuffer = 1 << 7, + DN_MemFlags_Heap = 1 << 8, }; struct DN_MemList { DN_MemBlock* curr; DN_MemFlags flags; - DN_MemFuncs funcs; + DN_Heap heap; DN_MemStats stats; DN_Str8 label; @@ -1833,60 +1860,109 @@ struct DN_ArrayFindResult }; typedef bool (DN_ArrayFindEqFunc)(void const *lhs, void const *find); -enum DN_DSMapKeyType +enum DN_HTableDeallocPoolOnDeinit { - // 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 + DN_HTableDeallocPoolOnDeinit_No, + DN_HTableDeallocPoolOnDeinit_Yes, }; -struct DN_DSMapKey +enum DN_HTableHashSentinel { - 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; + DN_HTableHashSentinel_Empty = 0, + DN_HTableHashSentinel_Tomb, + DN_HTableHashSentinel_FirstValid, }; -template -struct DN_DSMapSlot +enum DN_HTableFlags_ { - DN_DSMapKey key; // Hash table lookup key - T value; // Hash table value + DN_HTableFlags_DeallocPoolOnDeinit = 1 << 0, + DN_HTableFlags_UsingHeap = 1 << 1, }; -typedef DN_U32 DN_DSMapFlags; -enum DN_DSMapFlags_ +typedef DN_U32 DN_HTableHashType; +typedef DN_U32 DN_HTableFlags; + +typedef DN_U32(DN_HTableHashFunc) (void const *key, DN_USize size); +typedef bool (DN_HTableKeyEqFunc)(void const *lhs, void const *rhs, DN_USize size); +struct DN_HTable { - DN_DSMapFlags_Nil = 0, - DN_DSMapFlags_DontFreeArenaOnResize = 1 << 0, + // NOTE: The total number of slots used by the table is defined as `count` + `tombs_count` + DN_USize count; // Count of `kvs` that are filled with active values (e.g. hash >= `FirstValid`) + DN_USize tombs_count; // Count of `kvs` that are tombstoned (e.g. hash == `Tomb`) + DN_USize max; // Max capacity of `kvs` + DN_F32 load_factor01; + DN_HTableHashFunc* hash_func; + DN_HTableKeyEqFunc* key_eq_func; + + DN_HTableFlags flags; + DN_Heap heap; + DN_Pool pool; + + // NOTE: Metadata for handling genericism (in C that means type-erasure) + void** kvs; + DN_USize offset_of_hash; + DN_USize size_of_kv; + DN_USize offset_of_key; + DN_USize size_of_key; + DN_USize offset_of_value; + DN_USize size_of_value; }; -using DN_DSMapHashFunction = DN_U32(DN_DSMapKey key, DN_U32 seed); -template struct DN_DSMap +struct DN_HTableInitArgs { - 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; + DN_F32 load_factor; + DN_HTableHashFunc* hash_func; + DN_HTableKeyEqFunc* key_eq_func; + DN_USize max; + void** kvs; + DN_USize offset_of_hash; + DN_USize size_of_kv; + DN_USize offset_of_key; + DN_USize size_of_key; + DN_USize offset_of_value; + DN_USize size_of_value; }; -template struct DN_DSMapResult +struct DN_HTableSlot { - bool found; - DN_DSMapSlot *slot; - T *value; + // NOTE: Type-erased lookup of the {hash, key, value} object in the hash table + DN_HTableHashType hash_u32; // Hash copied out to a U32 for convenience + DN_USize kvs_index; // Index into `kvs` that the following {hash, key, value} pointers are pointing to + void* kv_struct; // Pointer to the matched kv struct + void* hash; // Pointer to the hash embedded in the user's `kvs` struct + void* key; // Pointer to the key embedded in the user's `kvs` struct + void* value; // Pointer to the value embedded in the user's `kvs` struct +}; + +struct DN_HTableAddResult +{ + bool success; // Adding fails if adding required a table resize and resizing failed (out-of-memory) + bool existed; // Set if the item you attempted to add, already existed in the table and it was updated instead + DN_HTableSlot slot; // Table slot containing the value you just added, zero initialised struct on failure +}; + +struct DN_HTableLookupResult +{ + DN_HTableHashType key_hash; // hash(key) that was used to look up `slot` + DN_HTableSlot slot; // KV object from `kvs`, always set (either empty, tombed, or active slot) +}; + +struct DN_HTableResizeResult +{ + bool success; + bool needed_resize; +}; + +struct DN_HTableInitResult +{ + DN_HTable table; + bool success; // Fails if the allocator could not allocate memory for the table +}; + +enum DN_HTableAllowTombstone +{ + DN_HTableAllowTombstone_No, + DN_HTableAllowTombstone_Yes, }; enum DN_LeakAllocFlag @@ -1909,13 +1985,19 @@ struct DN_LeakAlloc // expensive. Enforce that there is no unexpected padding. DN_StaticAssert(sizeof(DN_LeakAlloc) == 64 || sizeof(DN_LeakAlloc) == 32); // NOTE: 64 bit vs 32 bit pointers respectively +struct DN_LeakTrackerKV +{ + DN_HTableHashType hash; + DN_UPtr key; + DN_LeakAlloc value; +}; + struct DN_LeakTracker { - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_MemList alloc_table_mem; - DN_Arena alloc_table_arena; - DN_U64 alloc_table_bytes_allocated_for_stack_traces; + DN_LeakTrackerKV *alloc_table_kvs; + DN_HTable alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; }; typedef DN_USize DN_InitFlags; @@ -1932,6 +2014,103 @@ enum DN_InitFlags_ DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, }; +enum DN_BinPackMode +{ + DN_BinPackMode_Serialise, + DN_BinPackMode_Deserialise, +}; + +struct DN_BinPack +{ + DN_Str8Builder writer; + DN_Str8 read; + DN_USize read_index; +}; + +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; +}; + +struct DN_TestDiagnosticRow +{ + DN_Str8 expr; + DN_Str8 invariant; + DN_CallSite call_site; + DN_Str8 message; +}; + +struct DN_TestEntry +{ + bool failed; + DN_Str8 name; + DN_TestDiagnosticRow* diagnostics; + DN_USize diagnostics_count; + DN_USize diagnostics_max; + DN_U64 ts_begin; + DN_U64 ts_end; +}; + +struct DN_TestGroup +{ + DN_Str8 name; + DN_TestEntry* entries; // Array of tests cases that have been executed/are being executed + DN_USize entries_count; + DN_USize entries_max; + DN_U64 ts_begin; // Timestamp of when the test started (using DN_OS_PerfCounter) + DN_U64 ts_end; // Timestamp of when the test ended + DN_TestEntry* curr_entry; // Active test being executed and results should be updated into +}; + +struct DN_TestCore +{ + DN_Arena* arena; + DN_Pool pool; + DN_USize total_passed; // Total number of tests (`TestEntry`) across all groups that passed + DN_USize total_failed; // Total number of tests (`TestEntry`) across all groups that failed + DN_USize total_count; // Total number of tests (`TestEntry`) across all groups + + DN_TestGroup* groups; + DN_USize groups_count; + DN_USize groups_max; + + DN_TestGroup* curr_group; // Active test group being executed and test entries are being added to +}; + +enum DN_TestLogic +{ + DN_TestLogic_GreaterThan, + DN_TestLogic_GreaterThanEq, + DN_TestLogic_LessThan, + DN_TestLogic_LessThanEq, + DN_TestLogic_Eq, + DN_TestLogic_NotEq, +}; + +typedef DN_U32 DN_Str8FromTestCoreFlags; +enum DN_Str8FromTestCoreFlags_ +{ + DN_Str8FromTestCoreFlags_Nil, + DN_Str8FromTestCoreFlags_Colour, +}; + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC @@ -2132,8 +2311,18 @@ struct DN_OSConditionVariable DN_U64 handle; }; -// NOTE: DN_OSThread typedef DN_I32(DN_OSThreadFunc)(struct DN_OSThread *); +struct DN_OSThreadInitArgs +{ + // NOTE: Customise much memory the thread's TLS context will be initialised with which contains + // persistent/temporary allocators and misc facilities that each thread has exclusive access to. + DN_TCInitArgs tc_args; + + // NOTE: Set how much stack space in bytes the thread will have. Leave this value to 0 to defer + // to the OS's default stack size. + DN_USize stack_size; +}; + struct DN_OSThreadLane { DN_USize index; @@ -2260,14 +2449,26 @@ struct DN_Core #endif }; -// NOTE: Library initialisation. This must be called before using the library once to setup TLS and -// query the OS for information (such as page size) for tuning allocations and so forth. The caller -// should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// NOTE: Library initialisation +// Overview +// You must initialise the library to setup pre-requisite data structures that some APIs in the +// library take advantage of. The `DN_Core` object must remain valid for the lifetime of the +// library. // -// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used -// internally by the library. If you have an application that has the concept of frames, you may -// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it tracks -// the number of memory allocations for the current frame and that counter can be reset. +// API +// DN_Init +// This must be called before using the library once to setup TLS and query the OS for +// information (such as page size) for tuning allocations and so forth. The +// caller should pass in a zero-initialised `DN_Core` that should persist for program lifetime. +// +// A reference to the core passed in is kept which can be queried with `DN_Get` and may be used +// internally by the library. If you have an application that has the concept of frames, you may +// optionally call `DN_BeginFrame` which resets some metrics that are counted for example it +// tracks the number of memory allocations for the current frame and that counter can be reset. +// +// For convenience you can opt into some sane defaults for the `DN_TCInitArgs` which customises +// how thread-local storage is setup with `DN_TCInitArgsDefault()` which produces a +// `DN_TCInitArgs` object you can pass in by value. DN_API void DN_Init (DN_Core *dn, DN_InitFlags flags, DN_TCInitArgs args); DN_API void DN_Set (DN_Core *dn); DN_API DN_Core* DN_Get (); @@ -2418,20 +2619,48 @@ DN_API DN_F32 DN_EpsilonClampF32 DN_API DN_MemStats DN_MemStatsSum (DN_MemStats lhs, DN_MemStats rhs); DN_API DN_MemStats DN_MemStatsSumArray (DN_MemStats const *array, DN_USize count); +// NOTE: Heap +// Overview +// Generic heap allocators that can do arbitrary memory allocations and deallocations. It's a very +// thin wrapper that tracks the number of bytes allocated and deallocated and is accepted by some +// data structures in the codebase that opt out or have an alternative to arena allocations (of +// which arenas are the predominant where it makes sense to). +// +// Basic heap takes an API for routines in the likes of malloc/free (CRT/POSIX) or +// HeapAlloc/HeapFree (Win32) which take a size and a pointer to free. +// +// Virtual heap takes an API for routines in the likes of VirtualAlloc (Win32) or mmap (POSIX) and +// those family of functions. + +// API +// DN_HeapAlloc +// DN_HeapDealloc +// Allocate and deallocate memory. Note that `DN_HeapAlloc` takes reserve and commit even for +// the basic allocator. In the basic allocator which does not have a concept of reserving and +// committing, the `commit` value is ignored and the `reserve` amount is the amount that will be +// allocated. +DN_API DN_Heap DN_HeapInitBasic (DN_HeapBasicAllocFunc *basic_alloc, DN_HeapBasicDeallocFunc *basic_dealloc); +DN_API DN_Heap DN_HeapInitVirtual (DN_U32 page_size, DN_HeapVirtualReserveFunc *virtual_reserve, DN_HeapVirtualCommitFunc *virtual_commit, DN_HeapVirtualReleaseFunc *virtual_release); +DN_API void* DN_HeapAlloc (DN_Heap *heap, DN_USize reserve, DN_USize commit); +DN_API void DN_HeapDealloc (DN_Heap *heap, void *ptr, DN_USize size); + // NOTE: MemList // Overview // `MemList` is an implementation of a classical `Arena` (e.g. bump allocator, can dynamically // grow, frees by bumping pointer back, sub-divides a block of memory). The term `Arena` is // reserved as a thin-layer over the functionality here to provide some use-after-free protection. // See `Arena` for more info. +// +// In general for 99% of use-cases you want an `Arena` and if some API is not exposed on the +// `Arena` to then use the `MemList` API on the embedded `mem_list` backing the Arena. DN_API DN_MemList DN_MemListFromBuffer (void *buffer, DN_USize size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromMemFuncs (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_MemFuncs mem_funcs); +DN_API DN_MemList DN_MemListFromHeap (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap); DN_API void DN_MemListDeinit (DN_MemList *mem); DN_API bool DN_MemListCommit (DN_MemList *mem, DN_U64 size); DN_API bool DN_MemListCommitTo (DN_MemList *mem, DN_U64 pos); DN_API bool DN_MemListGrow (DN_MemList *mem, DN_U64 reserve, DN_U64 commit); -DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); -DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, uint8_t align, DN_ZMem zmem); +DN_API void * DN_MemListAlloc (DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem zmem); +DN_API void * DN_MemListAllocContiguous (DN_MemList *mem, DN_U64 size, DN_U8 align, DN_ZMem zmem); DN_API void * DN_MemListCopy (DN_MemList *mem, void const *data, DN_U64 size, uint8_t align); DN_API void DN_MemListPopTo (DN_MemList *mem, DN_U64 init_used); DN_API void DN_MemListPop (DN_MemList *mem, DN_U64 amount); @@ -2471,10 +2700,10 @@ DN_API void DN_MemListTempEnd // To get UAF protection, all allocations _must_ go through the `Arena` API, using the `MemList` // field directly in the `Arena` will bypass these checks and lead to unusual behaviour. If you // want to forgo any of this infrastructure store and use the `MemList` directly in your codebase. -// + // UAF Example /* - DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_MemFlags_Nil); + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitDefault()); DN_Arena temp = DN_ArenaTempBeginFromArena(&arena); { // NOTE: You can also `TempBegin` with `&temp`, either is valid. They both have pointers to @@ -2486,8 +2715,9 @@ DN_API void DN_MemListTempEnd // the same underlying memory block sitting in `arena`. // // But the intent here is that the caller is resetting `nested_temp`'s allocations and not - // `temp` hence the UAF protection triggers. - DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); + // `temp` hence the UAF protection triggers. The only permissible way to allocate in this + // region is to allocate from `nested_temp` (the active region). + DN_U64 *u64 = DN_ArenaNewZ(&temp, DN_U64); DN_ArenaTempEnd(&nested_temp); } @@ -2495,12 +2725,13 @@ DN_API void DN_MemListTempEnd DN_ArenaDeinit(&arena); // Frees the memory */ DN_API DN_Arena DN_ArenaFromMemList (DN_MemList *mem); +DN_API DN_Arena DN_ArenaFromHeap (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags, DN_Heap heap); DN_API DN_Arena DN_ArenaTempBeginFromMemList (DN_MemList *mem); DN_API DN_Arena DN_ArenaTempBeginFromArena (DN_Arena *arena); DN_API void DN_ArenaTempEnd (DN_Arena *arena, DN_ArenaReset reset); -DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_mem); -DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZMem z_arena); -DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, uint8_t align); +DN_API void* DN_ArenaAlloc (DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_mem); +DN_API void* DN_ArenaAllocContiguous (DN_Arena *arena, DN_U64 size, DN_U8 align, DN_ZMem z_arena); +DN_API void* DN_ArenaCopy (DN_Arena *arena, void const *data, DN_U64 size, DN_U8 align); DN_API void DN_ArenaDeinit (DN_Arena *arena); #define DN_ArenaNew(arena, T, zmem) (T *)DN_ArenaAlloc(arena, sizeof(T), alignof(T), zmem) @@ -2547,7 +2778,7 @@ DN_API void DN_ErrSinkAppendF_ DN_API DN_TCInitArgs DN_TCInitArgsDefault (); DN_API void DN_TCInit (DN_TCCore *tc, DN_U64 thread_id, DN_Arena *main_arena, DN_Arena *temp_arenas, DN_USize temp_arenas_count, DN_Arena *err_sink_arena); -DN_API void DN_TCInitFromMemFuncs (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_MemFuncs mem_funcs); +DN_API void DN_TCInitFromHeap (DN_TCCore *tc, DN_U64 thread_id, DN_TCInitArgs args, DN_Heap heap); DN_API void DN_TCDeinit (DN_TCCore *tc, DN_TCDeinitArenas deinit_arenas); DN_API void DN_TCEquip (DN_TCCore *tc); DN_API DN_TCCore* DN_TCGet (); @@ -2572,9 +2803,13 @@ DN_API bool DN_CharIsHex DN_API char DN_CharToLower (char ch); DN_API char DN_CharToUpper (char ch); -DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string, char separator); -DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size, char separator); -DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size, char separator); +DN_API DN_U64FromResult DN_U64FromStr8Delimiters (DN_Str8 string, DN_Str8 const *delimiters, DN_USize delimiters_count); +DN_API DN_U64FromResult DN_U64FromStr8Delimiter (DN_Str8 string, DN_Str8 delimiter); +DN_API DN_U64FromResult DN_U64FromStr8 (DN_Str8 string); +DN_API DN_U64FromResult DN_U64FromPtrDelimiter (void const *data, DN_USize size, DN_Str8 delimiter); +DN_API DN_U64FromResult DN_U64FromPtr (void const *data, DN_USize size); +DN_API DN_U64 DN_U64FromPtrUnsafeDelimiter (void const *data, DN_USize size, DN_Str8 delimiter); +DN_API DN_U64 DN_U64FromPtrUnsafe (void const *data, DN_USize size); DN_API DN_U64FromResult DN_U64FromHexPtr (void const *hex, DN_USize hex_count); DN_API DN_U64 DN_U64FromHexPtrUnsafe (void const *hex, DN_USize hex_count); DN_API DN_U64FromResult DN_U64FromHexStr8 (DN_Str8 hex); @@ -2679,18 +2914,27 @@ DN_API DN_Str8FindResult DN_Str8FindStr8 DN_API DN_Str8FindResult DN_Str8Find (DN_Str8 string, DN_Str8FindFlag flags); DN_API DN_Str8 DN_Str8Segment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); DN_API DN_Str8 DN_Str8ReverseSegment (DN_Arena *arena, DN_Str8 src, DN_USize segment_size, char segment_char); -DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8Eq (DN_Str8 lhs, DN_Str8 rhs, DN_Str8EqCase eq_case); +DN_API bool DN_Str8EqSensitive (DN_Str8 lhs, DN_Str8 rhs); DN_API bool DN_Str8EqInsensitive (DN_Str8 lhs, DN_Str8 rhs); -DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8StartsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API bool DN_Str8StartsWithSensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8StartsWithInsensitive (DN_Str8 string, DN_Str8 prefix); -DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API bool DN_Str8EndsWith (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API bool DN_Str8EndsWithSensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8EndsWithInsensitive (DN_Str8 string, DN_Str8 prefix); DN_API bool DN_Str8HasChar (DN_Str8 string, char ch); -DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); +DN_API DN_Str8 DN_Str8TrimPrefix (DN_Str8 string, DN_Str8 prefix, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimPrefixSensitive (DN_Str8 string, DN_Str8 prefix); +DN_API DN_Str8 DN_Str8TrimPrefixInsensitive (DN_Str8 string, DN_Str8 prefix); DN_API DN_Str8 DN_Str8TrimHexPrefix (DN_Str8 string); -DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case = DN_Str8EqCase_Sensitive); -DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8TrimSuffix (DN_Str8 string, DN_Str8 suffix, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimSuffixSensitive (DN_Str8 string, DN_Str8 suffix); +DN_API DN_Str8 DN_Str8TrimSuffixInsensitive (DN_Str8 string, DN_Str8 suffix); +DN_API DN_Str8 DN_Str8TrimAround (DN_Str8 string, DN_Str8 trim_string, DN_Str8EqCase eq_case); +DN_API DN_Str8 DN_Str8TrimAroundSensitive (DN_Str8 string, DN_Str8 trim_string); +DN_API DN_Str8 DN_Str8TrimAroundInsensitive (DN_Str8 string, DN_Str8 trim_string); DN_API DN_Str8 DN_Str8TrimHeadWhitespace (DN_Str8 string); DN_API DN_Str8 DN_Str8TrimTailWhitespace (DN_Str8 string); DN_API DN_Str8 DN_Str8TrimWhitespaceAround (DN_Str8 string); @@ -2719,7 +2963,7 @@ DN_API DN_Str8 DN_Str8LineBreakAllocator DN_API DN_Str8 DN_Str8LineBreakArena (DN_Str8 src, DN_USize desired_width, DN_Str8 delimiter, DN_Str8LineBreakMode mode, DN_Arena *arena); DN_API DN_Str8 DN_Str8Table (DN_Str8 const* rows, DN_USize num_rows, DN_USize num_cols, DN_Str8TableFlags flags, DN_Arena *arena); -#if DN_STR8_AVX512F +#if DN_WITH_STR8_AVX512F DN_API DN_Str8FindResult DN_Str8FindStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8FindResult DN_Str8FindLastStr8AVX512F (DN_Str8 string, DN_Str8 find); DN_API DN_Str8BSplitResult DN_Str8BSplitAVX512F (DN_Str8 string, DN_Str8 find); @@ -3492,10 +3736,14 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_TArrayFindMemEq(ptr, size, ptr_find) #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_TArrayResizeFromPool(&(ptr), ptr_size, ptr_max, pool, new_max) #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_TArrayResizeFromArena(&(ptr), ptr_size, ptr_max, arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), size, ptr_max, pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), size, ptr_max, arena, new_max) - #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) - #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) + #define DN_PArrayGrowFromPool(ptr, ptr_max, pool, new_max) DN_TArrayGrowFromPool(&(ptr), ptr_max, pool, new_max) + #define DN_PArrayGrowFromArena(ptr, ptr_max, arena, new_max) DN_TArrayGrowFromArena(&(ptr), ptr_max, arena, new_max) + #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_TArrayGrowIfNeededFromPool(&(ptr), size, ptr_max, pool, add_count) + #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_TArrayGrowIfNeededFromArena(&(ptr), size, ptr_max, arena, add_count) + #define DN_PArrayCopyPtrPool(dest, src, count_, pool) DN_TArrayCopyPtrPool(&(dest), src, count_, pool) + #define DN_PArrayCopyPtrPoolAssert(dest, src, count_, pool) DN_TArrayCopyPtrPoolAssert(&(dest), src, count_, pool, DN_CallSiteNow) + #define DN_PArrayCopyPtrArena(dest, src, count_, arena) DN_TArrayCopyPtrArena(&(dest), src, count_, arena) + #define DN_PArrayCopyPtrArenaAssert(dest, src, count_, arena) DN_TArrayCopyPtrArenaAssert(&(dest), src, count_, arena, DN_CallSiteNow) #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) DN_TArrayMakeArray(ptr, ptr_size, max, count, z_mem) #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) DN_TArrayMakeArray(ptr, ptr_size, max, count, DN_ZMem_Yes) @@ -3530,10 +3778,14 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_PArrayFindMemEq(ptr, size, ptr_find) DN_ArrayFindMemEq(ptr, size, sizeof(*(ptr)), ptr_find) #define DN_PArrayResizeFromPool(ptr, ptr_size, ptr_max, pool, new_max) DN_ArrayResizeFromPool((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), pool, new_max) #define DN_PArrayResizeFromArena(ptr, ptr_size, ptr_max, arena, new_max) DN_ArrayResizeFromArena((void **)&(ptr), ptr_size, ptr_max, sizeof((ptr)[0]), arena, new_max) - #define DN_PArrayGrowFromPool(ptr, size, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), pool, new_max) - #define DN_PArrayGrowFromArena(ptr, size, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), size, ptr_max, sizeof((ptr)[0]), arena, new_max) + #define DN_PArrayGrowFromPool(ptr, ptr_max, pool, new_max) DN_ArrayGrowFromPool((void **)&(ptr), ptr_max, sizeof((ptr)[0]), pool, new_max) + #define DN_PArrayGrowFromArena(ptr, ptr_max, arena, new_max) DN_ArrayGrowFromArena((void **)&(ptr), ptr_max, sizeof((ptr)[0]), arena, new_max) #define DN_PArrayGrowIfNeededFromPool(ptr, size, ptr_max, pool, add_count) DN_ArrayGrowIfNeededFromPool((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), pool, add_count) #define DN_PArrayGrowIfNeededFromArena(ptr, size, ptr_max, arena, add_count) DN_ArrayGrowIfNeededFromArena((void **)(ptr), size, ptr_max, sizeof((*ptr)[0]), arena, add_count) + #define DN_PArrayCopyPtrPool(ptr_array, src, count_, pool) DN_ArrayCopyPtrPool(ptr_array, src, count_, sizeof((*ptr)[0]), pool) + #define DN_PArrayCopyPtrPoolAssert(ptr_array, src, count_, pool) DN_ArrayCopyPtrPool(ptr_array, src, count_, sizeof((*ptr)[0]), pool, DN_CallSiteNow) + #define DN_PArrayCopyPtrArena(ptr_array, src, count_, arena) DN_ArrayCopyPtrArena(ptr_array, src, count_, sizeof((*ptr)[0]), arena) + #define DN_PArrayCopyPtrArenaAssert(ptr_array, src, count_, arena) DN_ArrayCopyPtrArena(ptr_array, src, count_, sizeof((*ptr)[0]), arena, DN_CallSiteNow) #define DN_PArrayMakeArray(ptr, ptr_size, max, count, z_mem) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, z_mem) #define DN_PArrayMakeArrayZ(ptr, ptr_size, max, count) (DN_CppDeclType(&(ptr)[0]))DN_ArrayMakeArray(ptr, ptr_size, max, sizeof((ptr)[0]), count, DN_ZMem_Yes) @@ -3567,12 +3819,6 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_LArrayFind(c_array, size, ptr_find, eq_func) DN_PArrayFind(c_array, size, ptr_find, eq_func) #define DN_LArrayFindMemEq(c_array, size, ptr_find) DN_PArrayFindMemEq(c_array, size, ptr_find) -#define DN_LArrayResizeFromPool(c_array, size, pool, new_max) DN_PArrayResizeFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayResizeFromArena(c_array, size, arena, new_max) DN_PArrayResizeFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowFromPool(c_array, size, pool, new_max) DN_PArrayGrowFromPool(c_array, size, DN_ArrayCountU(c_array), pool, new_max) -#define DN_LArrayGrowFromArena(c_array, size, arena, new_max) DN_PArrayGrowFromArena(c_array, size, DN_ArrayCountU(c_array), arena, new_max) -#define DN_LArrayGrowIfNeededFromPool(c_array, size, pool, add_count) DN_PArrayGrowIfNeededFromPool(c_array, size, DN_ArrayCountU(c_array), pool, add_count) -#define DN_LArrayGrowIfNeededFromArena(c_array, size, arena, add_count) DN_PArrayGrowIfNeededFromArena(c_array, size, DN_ArrayCountU(c_array), arena, add_count) #define DN_LArrayMakeArray(c_array, ptr_size, count, z_mem) DN_PArrayMakeArray(c_array, ptr_size, DN_ArrayCountU(c_array), count, z_mem) #define DN_LArrayMakeArrayZ(c_array, ptr_size, count) DN_PArrayMakeArrayZ(c_array, ptr_size, DN_ArrayCountU(c_array), count) @@ -3606,17 +3852,21 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_IArrayFindMemEq(ptr_array, ptr_find) DN_PArrayFindMemEq((ptr_array)->data, (ptr_array)->count, ptr_find) #define DN_IArrayResizeFromPool(ptr_array, pool, new_max) DN_PArrayResizeFromPool((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, pool, new_max) #define DN_IArrayResizeFromArena(ptr_array, arena, new_max) DN_PArrayResizeFromArena((ptr_array)->data, &(ptr_array)->count, &(ptr_array)->max, arena, new_max) -#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, new_max) -#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, new_max) +#define DN_IArrayGrowFromPool(ptr_array, pool, new_max) DN_PArrayGrowFromPool((ptr_array)->data, &(ptr_array)->max, pool, new_max) +#define DN_IArrayGrowFromArena(ptr_array, arena, new_max) DN_PArrayGrowFromArena((ptr_array)->data, &(ptr_array)->max, arena, new_max) #define DN_IArrayGrowIfNeededFromPool(ptr_array, pool, add_count) DN_PArrayGrowIfNeededFromPool(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, pool, add_count) #define DN_IArrayGrowIfNeededFromArena(ptr_array, arena, add_count) DN_PArrayGrowIfNeededFromArena(&(ptr_array)->data, (ptr_array)->count, &(ptr_array)->max, arena, add_count) +#define DN_IArrayCopyPtrPool(ptr_array, src, count_, pool) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrPool((ptr_array), src, count_, pool) +#define DN_IArrayCopyPtrPoolAssert(ptr_array, src, count_, pool) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrPoolAssert((ptr_array), src, count_, pool) +#define DN_IArrayCopyPtrArena(ptr_array, src, count_, arena) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrArena((ptr_array), src, count_, arena) +#define DN_IArrayCopyPtrArenaAssert(ptr_array, src, count_, arena) (ptr_array)->max = (ptr_array)->count = DN_PArrayCopyPtrArenaAssert((ptr_array), src, count_, arena) -#define DN_IArrayMakeArray(ptr_array, count, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayZ(ptr_array, count) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayNoZ(ptr_array, count) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssert(ptr_array, count, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count, z_mem) -#define DN_IArrayMakeArrayAssertZ(ptr_array, count) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayMakeArray(ptr_array, count_, z_mem) DN_PArrayMakeArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_, z_mem) +#define DN_IArrayMakeArrayZ(ptr_array, count_) DN_PArrayMakeArrayZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayNoZ(ptr_array, count_) DN_PArrayMakeArrayNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayAssert(ptr_array, count_, z_mem) DN_PArrayMakeArrayAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_, z_mem) +#define DN_IArrayMakeArrayAssertZ(ptr_array, count_) DN_PArrayMakeArrayAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayMakeArrayAssertNoZ(ptr_array, count_) DN_PArrayMakeArrayAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) #define DN_IArrayMake(ptr_array, z_mem) DN_PArrayMake((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, z_mem) #define DN_IArrayMakeZ(ptr_array) DN_PArrayMakeZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) @@ -3625,19 +3875,19 @@ DN_API DN_RaycastV2 DN_RaycastLineIntersectV2 #define DN_IArrayMakeAssertZ(ptr_array) DN_PArrayMakeAssertZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) #define DN_IArrayMakeAssertNoZ(ptr_array) DN_PArrayMakeAssertNoZ((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max) -#define DN_IArrayAddArray(ptr_array, items, count, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count, add) +#define DN_IArrayAddArray(ptr_array, items, count_, add) DN_PArrayAddArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_, add) #define DN_IArrayAdd(ptr_array, item, add) DN_PArrayAdd((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item, add) -#define DN_IArrayAppendArray(ptr_array, items, count) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayAppendArray(ptr_array, items, count_) DN_PArrayAppendArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_) #define DN_IArrayAppend(ptr_array, item) DN_PArrayAppend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) #define DN_IArrayAppendAssert(ptr_array, item) DN_PArrayAppendAssert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayPrependArray(ptr_array, items, count) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count) +#define DN_IArrayPrependArray(ptr_array, items, count_) DN_PArrayPrependArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, items, count_) #define DN_IArrayPrepend(ptr_array, item) DN_PArrayPrepend((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, item) -#define DN_IArrayEraseRange(ptr_array, begin_index, count, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count, erase) +#define DN_IArrayEraseRange(ptr_array, begin_index, count_, erase) DN_PArrayEraseRange((ptr_array)->data, &(ptr_array)->count, begin_index, count_, erase) #define DN_IArrayErase(ptr_array, index, erase) DN_PArrayErase((ptr_array)->data, &(ptr_array)->count, index, erase) -#define DN_IArrayInsertArray(ptr_array, index, items, count) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count) +#define DN_IArrayInsertArray(ptr_array, index, items, count_) DN_PArrayInsertArray((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, items, count_) #define DN_IArrayInsert(ptr_array, index, item) DN_PArrayInsert((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, index, item) -#define DN_IArrayPopFront(ptr_array, count) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) -#define DN_IArrayPopBack(ptr_array, count) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count) +#define DN_IArrayPopFront(ptr_array, count_) DN_PArrayPopFront((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) +#define DN_IArrayPopBack(ptr_array, count_) DN_PArrayPopBack((ptr_array)->data, &(ptr_array)->count, (ptr_array)->max, count_) // NOTE: Slices // @@ -3663,10 +3913,14 @@ DN_API void* DN_ArrayAddArray (void * DN_API void* DN_ArrayAddArrayAssert (void *data, DN_USize *count, DN_USize max, DN_USize elem_size, void const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); DN_API bool DN_ArrayResizeFromPool (void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); DN_API bool DN_ArrayResizeFromArena (void **data, DN_USize *count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); -DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); -DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); +DN_API bool DN_ArrayGrowFromPool (void **data, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize new_max); +DN_API bool DN_ArrayGrowFromArena (void **data, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize new_max); DN_API bool DN_ArrayGrowIfNeededFromPool (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Pool *pool, DN_USize add_count); DN_API bool DN_ArrayGrowIfNeededFromArena (void **data, DN_USize count, DN_USize *max, DN_USize elem_size, DN_Arena *arena, DN_USize add_count); +DN_API DN_USize DN_ArrayCopyPtrArena (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena); +DN_API DN_USize DN_ArrayCopyPtrArenaAssert (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Arena *arena, DN_CallSite call_site); +DN_API DN_USize DN_ArrayCopyPtrPool (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool); +DN_API DN_USize DN_ArrayCopyPtrPoolAssert (void **data, void const *src, DN_USize count, DN_USize elem_size, DN_Pool *pool, DN_CallSite call_site); #if defined (__cplusplus) template DN_ArrayFindResult DN_TArrayFind (T *data, DN_USize count, void const *find, DN_ArrayFindEqFunc *eq_func); @@ -3683,141 +3937,276 @@ template T* DN_TArrayAddArray (T *dat template T* DN_TArrayAddArrayAssert (T *data, DN_USize *count, DN_USize max, T const *elems, DN_USize elems_count, DN_ArrayAdd add, DN_CallSite call_site); template bool DN_TArrayResizeFromPool (T **data, DN_USize *count, DN_USize *max, DN_Pool *pool, DN_USize new_max); template bool DN_TArrayResizeFromArena (T **data, DN_USize *count, DN_USize *max, DN_Arena *arena, DN_USize new_max); -template bool DN_TArrayGrowFromPool (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max); -template bool DN_TArrayGrowFromArena (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowFromPool (T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max); +template bool DN_TArrayGrowFromArena (T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max); template bool DN_TArrayGrowIfNeededFromPool (T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize add_count); template bool DN_TArrayGrowIfNeededFromArena (T **data, DN_USize count, DN_USize *max, DN_Arena *pool, DN_USize add_count); +template DN_USize DN_TArrayCopyPtrArena (T **data, T const *src, DN_USize count, DN_Arena *arena); +template DN_USize DN_TArrayCopyPtrArenaAssert (T **data, T const *src, DN_USize count, DN_Arena *arena, DN_CallSite call_site); +template DN_USize DN_TArrayCopyPtrPool (T **data, T const *src, DN_USize count, DN_Pool *pool); +template DN_USize DN_TArrayCopyPtrPoolAssert (T **data, T const *src, DN_USize count, DN_Pool *pool, DN_CallSite call_site); #endif -DN_API void* DN_SinglyLLDetach (void **link, void **next); -DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); -DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); -DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); -#define DN_RingWriteStruct(ring, item) DN_RingWrite((ring), (item), sizeof(*(item))) -DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); -#define DN_RingReadStruct(ring, dest) DN_RingRead((ring), (dest), sizeof(*(dest))) +DN_API void* DN_SinglyLLDetach (void **link, void **next); +DN_API bool DN_RingHasSpace (DN_Ring const *ring, DN_U64 size); +DN_API bool DN_RingHasData (DN_Ring const *ring, DN_U64 size); +DN_API void DN_RingWrite (DN_Ring *ring, void const *src, DN_U64 src_size); +#define DN_RingWriteStruct(ring_ptr, item_ptr) DN_RingWrite((ring_ptr), (item_ptr), sizeof(*(item_ptr))) +DN_API void DN_RingRead (DN_Ring *ring, void *dest, DN_U64 dest_size); +#define DN_RingReadStruct(ring_ptr, dest_ptr) DN_RingRead((ring_ptr), (dest_ptr), sizeof(*(dest_ptr))) -// TODO: Replace with a C-style hash table -#if defined(__cplusplus) -DN_U32 const DN_DS_MAP_DEFAULT_HASH_SEED = 0x8a1ced49; -DN_U32 const DN_DS_MAP_SENTINEL_SLOT = 0; -template DN_DSMap DN_DSMapInit (DN_Arena *arena, DN_U32 size, DN_DSMapFlags flags); -template void DN_DSMapDeinit (DN_DSMap *map, DN_ZMem z_mem); -template bool DN_DSMapIsValid (DN_DSMap const *map); -template DN_U32 DN_DSMapHash (DN_DSMap const *map, DN_DSMapKey key); -template DN_U32 DN_DSMapHashToSlotIndex (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapFind (DN_DSMap const *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapMake (DN_DSMap *map, DN_DSMapKey key); -template DN_DSMapResult DN_DSMapSet (DN_DSMap *map, DN_DSMapKey key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyU64 (DN_DSMap const *map, DN_U64 key); -template DN_DSMapResult DN_DSMapMakeKeyU64 (DN_DSMap *map, DN_U64 key); -template DN_DSMapResult DN_DSMapSetKeyU64 (DN_DSMap *map, DN_U64 key, T const &value); -template DN_DSMapResult DN_DSMapFindKeyStr8 (DN_DSMap const *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapMakeKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapResult DN_DSMapSetKeyStr8 (DN_DSMap *map, DN_Str8 key, T const &value); -template bool DN_DSMapResize (DN_DSMap *map, DN_U32 size); -template bool DN_DSMapErase (DN_DSMap *map, DN_DSMapKey key); -template bool DN_DSMapEraseKeyU64 (DN_DSMap *map, DN_U64 key); -template bool DN_DSMapEraseKeyStr8 (DN_DSMap *map, DN_Str8 key); -template DN_DSMapKey DN_DSMapKeyBuffer (DN_DSMap const *map, void const *data, DN_USize count); -template DN_DSMapKey DN_DSMapKeyBufferAsU64NoHash (DN_DSMap const *map, void const *data, DN_USize count); -template DN_DSMapKey DN_DSMapKeyU64 (DN_DSMap const *map, DN_U64 u64); -template DN_DSMapKey DN_DSMapKeyStr8 (DN_DSMap const *map, DN_Str8 string); -#define DN_DSMapKeyCStr8(map, string) DN_DSMapKeyBuffer(map, string, sizeof((string))/sizeof((string)[0]) - 1) -DN_API DN_DSMapKey DN_DSMapKeyU64NoHash (DN_U64 u64); -DN_API bool DN_DSMapKeyEquals (DN_DSMapKey lhs, DN_DSMapKey rhs); -DN_API bool operator== (DN_DSMapKey lhs, DN_DSMapKey rhs); -#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 bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); -DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); -DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); -DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); -DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); -DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); -DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); -DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); -DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); -DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); -DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); -DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); -DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); -DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); -DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); -DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); -DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); -DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); -DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); -DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); -DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); - -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; -}; - -// NOTE: Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. -// Using "" to escape quotes inside a quoted string). +// NOTE: Hash Table +// Overview +// A hash table implemented in C-style C++ that is based off and heavily derivative of Jonathan +// Blow's "simple hash table" implementation from JAI that he posted on Pastebin +// https://pastebin.com/raw/xMUQXshn The goal of this hash table is to provide a relatively +// generic table that covers the 99% of use cases. For the 1% you should design a custom table +// suited for that workload. // +// The key features of the simple hash-table, paraphrased +// +// Open-addressed table with each table slot consists of {hash, key, value} for +// cache-coherency. Default load factor of 0.7 and uses a 4 byte hash +// +// Tombstoning with quadratic probing using triangular numbers. Less cache friendly than linear +// probing but more cache friendly than double hashing. It mitigates clustering of collisions +// around the insertion point but early probes maintain cache locality to the initial collision. +// Quote from the reference implementation: +// +// We use the 'triangular numbers' version of quadratic probing. It yields more collisions per +// insertion than double hashing (double hashing gives us 0.74, triangular numbers give us +// 0.84) but the collisions are cheaper because the initial probes are cache-coherent, unlike +// with double hashing. At large table sizes this ends up mattering substantially. +// (examples/hash_table_test.jai, at the 128 million entry table size, takes 15.5 seconds with +// triangular numbers, 17.8 seconds with double hashing.) +// +// Triangular numbers are guaranteed to hit every entry in the table: +// https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ +// +// This hash table requires that the caller sets up their own struct with the key, value and hash +// located in the struct and pass the offsets to these fields into the table such that it can +// updated the values in a type-erased manner. By keeping all the table slot data into one struct +// ensures that the optimal path of looking up an item requires exactly one cache miss to fetch +// the necessary data. As long as the table maintains a 0.7 load factor, probabilistically all +// lookups are resolved in 1 shot. +// +// Other tables that opt for {key, key...} {value, value...} {hash, hash...} have benefits and +// make varying trade-offs. But assuming that the user is not memory bound and most lookups are +// resolved in 1 lookup this optimises for the common case where a simple hash table covers the +// 99% of use-cases. +// +// Note that this table requires that the user passes a double pointer to where store key-value +// structs that the table maintains on their behalf. This is an oddity necessitated because of the +// table being implemented using C constructs e.g.: +// +// MyKeyValue *my_kvs = NULL; +// DN_HTableInitArgsDefault(MyKeyValue, &my_kvs, ...) +// +// By forcing the user to store the pointer of `my_kvs` we allow you to have a type-safe view +// into the objects that the table is manipulating. This is a useful default mode of operation +// that makes it easier to debug having a type-safe pointer on-hand to inspect the contents of the +// table. This is rather than having the user need to cast `table.kvs` back to its concrete type +// every time you inspect the table. +// +// This would not be necessary if this was implemented in basically any other language that +// supports generics as a first class citizen. +// +// Example +/* + // NOTE: A hash table that maps (U64 -> bool) using the OS heap allocator + struct MyKeyValue + { + DN_HTableHashType hash; // Must always be `DN_HTableHashType` which is a 4 byte hash + DN_U64 key; // Any arbitrary key as long as it's in the same struct + bool value; // Any arbitrary value as long as it's in the same struct + }; + + MyKeyValue* table_kvs = {}; + DN_HTableInitArgs table_args = DN_HTableInitArgsDefault(MyKeyValue, table_kvs, hash, key, value); + DN_HTableInitResult table_init = DN_HTableInitHeap(table_args, DN_OS_HeapInitDefault()); + if (table_init.success) { + DN_HTable table = table_init.table; + P0I_U8x20 key = {}; + bool value = true; + DN_HTableAdd(&table, &key, &value); + } +*/ +DN_API DN_U32 DN_HTableHashFuncDefault (void const* key, DN_USize size); +DN_API bool DN_HTableKeyEqFuncMemcmp (void const* lhs, void const* rhs, DN_USize size); +DN_API bool DN_HTableKeyEqFuncStr8Eq (void const* lhs, void const* rhs, DN_USize size); + +#define DN_HTableInitArgsDefault(KeyValueT, kvs_ptr, HashField, KeyField, ValueField) DN_HTableInitArgsDefault_(DN_Cast(void **)&kvs_ptr, sizeof(*kvs_ptr), offsetof(KeyValueT, HashField), sizeof(kvs_ptr->KeyField), offsetof(KeyValueT, KeyField), sizeof(kvs_ptr->ValueField), offsetof(KeyValueT, ValueField)) +DN_API DN_HTableInitArgs DN_HTableInitArgsDefault_(void** kvs, DN_USize size_of_kvs, DN_USize offset_of_hash, DN_USize size_of_key, DN_USize offset_of_key, DN_USize size_of_value, DN_USize offset_of_value); +DN_API DN_HTableInitResult DN_HTableInitHeap (DN_HTableInitArgs args, DN_Heap heap); +DN_API DN_HTable DN_HTableInitHeapAssert (DN_HTableInitArgs args, DN_Heap heap); +DN_API DN_HTableInitResult DN_HTableInitPool (DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool); +DN_API DN_HTable DN_HTableInitPoolAssert (DN_HTableInitArgs args, DN_Pool pool, DN_HTableDeallocPoolOnDeinit dealloc_pool); +DN_API void DN_HTableDeinit (DN_HTable* table); + +DN_API DN_HTableLookupResult DN_HTableLookup (DN_HTable const* table, void const* key, DN_HTableAllowTombstone allow_tombstone); +DN_API DN_HTableSlot DN_HTableFind (DN_HTable const* table, void const* key); +DN_API void* DN_HTableValueFromFind (DN_HTable const* table, void const* key); +DN_API DN_HTableSlot DN_HTableSlotFromIndex (DN_HTable const* table, DN_USize index); + +DN_API bool DN_HTableResize (DN_HTable* table, DN_USize new_max); +DN_API void DN_HTableClear (DN_HTable* table); +DN_API DN_HTableResizeResult DN_HTableGrowMaybe (DN_HTable* table, DN_USize add_count); +DN_API DN_HTableAddResult DN_HTableMake (DN_HTable* table, void* key); +DN_API DN_HTableAddResult DN_HTableAdd (DN_HTable* table, void* key, void* value); +DN_API bool DN_HTableDel (DN_HTable* table, void* key); + +// NOTE: Binary Packer +DN_API bool DN_BinPackIsEndOfReadStream(DN_BinPack const *pack); +DN_API void DN_BinPackUSize (DN_BinPack *pack, DN_BinPackMode mode, DN_USize *item); +DN_API void DN_BinPackU64 (DN_BinPack *pack, DN_BinPackMode mode, DN_U64 *item); +DN_API void DN_BinPackU32 (DN_BinPack *pack, DN_BinPackMode mode, DN_U32 *item); +DN_API void DN_BinPackU16 (DN_BinPack *pack, DN_BinPackMode mode, DN_U16 *item); +DN_API void DN_BinPackU8 (DN_BinPack *pack, DN_BinPackMode mode, DN_U8 *item); +DN_API void DN_BinPackI64 (DN_BinPack *pack, DN_BinPackMode mode, DN_I64 *item); +DN_API void DN_BinPackI32 (DN_BinPack *pack, DN_BinPackMode mode, DN_I32 *item); +DN_API void DN_BinPackI16 (DN_BinPack *pack, DN_BinPackMode mode, DN_I16 *item); +DN_API void DN_BinPackI8 (DN_BinPack *pack, DN_BinPackMode mode, DN_I8 *item); +DN_API void DN_BinPackF64 (DN_BinPack *pack, DN_BinPackMode mode, DN_F64 *item); +DN_API void DN_BinPackF32 (DN_BinPack *pack, DN_BinPackMode mode, DN_F32 *item); +DN_API void DN_BinPackV2 (DN_BinPack *pack, DN_BinPackMode mode, DN_V2F32 *item); +DN_API void DN_BinPackV4 (DN_BinPack *pack, DN_BinPackMode mode, DN_V4F32 *item); +DN_API void DN_BinPackBool (DN_BinPack *pack, DN_BinPackMode mode, bool *item); +DN_API void DN_BinPackStr8FromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, DN_Str8 *string); +DN_API void DN_BinPackStr8FromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, DN_Str8 *string); +DN_API DN_Str8 DN_BinPackStr8FromBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API void DN_BinPackBytesFromArena (DN_BinPack *pack, DN_Arena *arena, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackBytesFromPool (DN_BinPack *pack, DN_Pool *pool, DN_BinPackMode mode, void **ptr, DN_USize *size); +DN_API void DN_BinPackCArray (DN_BinPack *pack, DN_BinPackMode mode, void *ptr, DN_USize size); +DN_API void DN_BinPackCBuffer (DN_BinPack *pack, DN_BinPackMode mode, char *ptr, DN_USize *size, DN_USize max); +DN_API DN_Str8 DN_BinPackBuild (DN_BinPack const *pack, DN_Arena *arena); + +// NOTE: CSV Tokeniser/Packer +// Overview +// Data structures to create and parse CSV files, supports Python style escaped quotes (e.g. Using +// "" to escape quotes inside a quoted string). + // API -// DN_CSVTokeniserNextN: Reads the next N consecutive fields from the parser. If `column_iterator` -// is `false` then the read of the N consecutive fields does not proceed past the end of the -// current CSV row. If `true` then it reads the next N fields even if reading would progress onto -// the next row. -DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); -DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); -DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); -DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); -DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); -DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); -DN_API int DN_CSVTokeniserNextColumnN(DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); -DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); -DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); -DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); -DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); -DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); -DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); -DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); -DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); -DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); -DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); -DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); -DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); -DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); +// DN_CSVTokeniserNextN +// Reads the next N consecutive fields from the parser. If `column_iterator` is `false` then the +// read of the N consecutive fields does not proceed past the end of the current CSV row. If `true` +// then it reads the next N fields even if reading would progress onto the next row. +DN_API DN_CSVTokeniser DN_CSVTokeniserInit (DN_Str8 string, char delimiter); +DN_API bool DN_CSVTokeniserValid (DN_CSVTokeniser *tokeniser); +DN_API bool DN_CSVTokeniserNextRow (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextField (DN_CSVTokeniser *tokeniser); +DN_API DN_Str8 DN_CSVTokeniserNextColumn (DN_CSVTokeniser *tokeniser); +DN_API void DN_CSVTokeniserSkipLine (DN_CSVTokeniser *tokeniser); +DN_API int DN_CSVTokeniserNextN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size, bool column_iterator); +DN_API int DN_CSVTokeniserNextColumnN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API int DN_CSVTokeniserNextFieldN (DN_CSVTokeniser *tokeniser, DN_Str8 *fields, int fields_size); +DN_API void DN_CSVTokeniserSkipLineN (DN_CSVTokeniser *tokeniser, int count); +DN_API void DN_CSVPackU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U64 *value); +DN_API void DN_CSVPackI64 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I64 *value); +DN_API void DN_CSVPackI32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I32 *value); +DN_API void DN_CSVPackI16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I16 *value); +DN_API void DN_CSVPackI8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_I8 *value); +DN_API void DN_CSVPackU32 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U32 *value); +DN_API void DN_CSVPackU16 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_U16 *value); +DN_API void DN_CSVPackBoolAsU64 (DN_CSVPack *pack, DN_CSVSerialise serialise, bool *value); +DN_API void DN_CSVPackStr8 (DN_CSVPack *pack, DN_CSVSerialise serialise, DN_Str8 *str8, DN_Arena *arena); +DN_API void DN_CSVPackBuffer (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size); +DN_API void DN_CSVPackBufferWithMax (DN_CSVPack *pack, DN_CSVSerialise serialise, void *dest, size_t *size, size_t max); +DN_API bool DN_CSVPackNewLine (DN_CSVPack *pack, DN_CSVSerialise serialise); + +// NOTE: Test Framework +// Overview +// A simple barebones testing framework that we use to unit test this library. This framework +// allows grouping of unit tests into 1 top-level group with an arbitrary number of tests inside +// the group. It uses a begin and end pattern to scope tests into groups. + +// Example +/* + DN_Arena arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_Kilobytes(64), DN_MemFlags_Nil, DN_OS_HeapInitDefault()); + DN_TestCore result = DN_TestInit(&arena); + for (DN_TestGroupScopeF(&result, "Base")) { + for (DN_TestScopeF(&result, "Check that foo != bar!")) + DN_TestVerifyStr8NotEq(&result, DN_Str8Lit("foo"), DN_Str8Lit("bar")); // Test passes + for (DN_TestScopeF(&result, "Does 1 equal 2?")) + DN_TestVerifyUSizeEq(&result, 1, 2); // This test will fail! + for (DN_TestScopeF(&result, "Float test, 1 is definitely less than 2")) + DN_TestVerifyF64LessThan(&result, 1.f, 2.f); // Floats work as well + } + DN_Str8 test_output = DN_Str8FromTestCore(&result, &arena, DN_Str8FromTestCoreFlags_Colour); + printf("%.*s", DN_Str8PrintFmt(test_output)); + DN_ArenaDeinit(&arena); + */ + +DN_API DN_TestCore DN_TestInit (DN_Arena *arena); +DN_API void DN_TestGroupBeginF (DN_TestCore *test, char const *fmt, ...); +DN_API void DN_TestGroupEnd (DN_TestCore *test); +#define DN_TestGroupScopeF(test_ptr, fmt, ...) bool group_once_ = (DN_TestGroupBeginF(test_ptr, fmt, ##__VA_ARGS__), false); !group_once_; DN_TestGroupEnd(test_ptr), group_once_ = true +DN_API void DN_TestBeginF (DN_TestCore *test, char const *fmt, ...); +DN_API void DN_TestEnd (DN_TestCore *test); +#define DN_TestScopeF(test_ptr, fmt, ...) bool test_once_ = (DN_TestBeginF(test_ptr, fmt, ##__VA_ARGS__), false); !test_once_; DN_TestEnd(test_ptr), test_once_ = true + +DN_API void DN_TestVerifyExprF_ (DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, bool expr_result, char const *fmt, ...); +#define DN_TestVerifyExprF(test_ptr, expr, fmt, ...) DN_TestVerifyExprF_(test_ptr, DN_CallSiteNow, DN_Str8Lit(#expr), (expr), fmt, ##__VA_ARGS__) +#define DN_TestVerifyExpr(test_ptr, expr) DN_TestVerifyExprF_(test_ptr, DN_CallSiteNow, DN_Str8Lit(#expr), (expr), "") + +DN_API void DN_TestVerifyF64Fmt (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_F64 val, DN_F64 expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyF64GreaterThanEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyF64GreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64GreaterThan(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyF64GreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64LessThan(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyF64LessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64LessThanEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyF64LessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64Eq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyF64EqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyF64NotEq(test_ptr, val, expect) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyF64NotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyF64Fmt(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyISizeF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_ISize val, DN_ISize expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyISizeGreaterThanEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyISizeGreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeGreaterThan(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyISizeGreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeLessThan(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyISizeLessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeLessThanEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyISizeLessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyISizeEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyISizeNotEq(test_ptr, val, expect) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyISizeNotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyISizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyUSizeF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 val_str8, DN_Str8 expect_str8, DN_USize val, DN_USize expect, DN_TestLogic logic, char const *fmt, ...); +#define DN_TestVerifyUSizeGreaterThanEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, "") +#define DN_TestVerifyUSizeGreaterThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeGreaterThan(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, "") +#define DN_TestVerifyUSizeGreaterThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_GreaterThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeLessThan(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, "") +#define DN_TestVerifyUSizeLessThanF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThan, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeLessThanEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, "") +#define DN_TestVerifyUSizeLessThanEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_LessThanEq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, "") +#define DN_TestVerifyUSizeEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_Eq, fmt, ##__VA_ARGS__) +#define DN_TestVerifyUSizeNotEq(test_ptr, val, expect) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, "") +#define DN_TestVerifyUSizeNotEqF(test_ptr, val, expect, fmt, ...) DN_TestVerifyUSizeF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#val), DN_Str8Lit(#expect), val, expect, DN_TestLogic_NotEq, fmt, ##__VA_ARGS__) + +DN_API void DN_TestVerifyStr8F (DN_TestCore *test, DN_CallSite call_site, DN_Str8 expr, DN_Str8 str8, DN_Str8 expect, bool expect_eq, char const *fmt, ...); +#define DN_TestVerifyStr8EqF(test_ptr, str8, expect, fmt, ...) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " == " #expect), (str8), (expect), /*expect_eq=*/ true, fmt, ##__VA_ARGS__) +#define DN_TestVerifyStr8Eq(test_ptr, str8, expect) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " == " #expect), (str8), (expect), /*expect_eq=*/ true, "") +#define DN_TestVerifyStr8NotEqF(test_ptr, str8, expect, fmt, ...) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " != " #expect), (str8), (expect), /*expect_eq=*/ false, fmt, ##__VA_ARGS__) +#define DN_TestVerifyStr8NotEq(test_ptr, str8, expect) DN_TestVerifyStr8F(test_ptr, DN_CallSiteNow, DN_Str8Lit(#str8 " != " #expect), (str8), (expect), /*expect_eq=*/ false, "") + +DN_API void DN_TestVerifyBytesF (DN_TestCore *test, DN_CallSite call_site, DN_Str8 bytes, DN_Str8 expect, bool expect_eq, char const *fmt, ...); +#define DN_TestVerifyBytesEqF(test_ptr, bytes, expect, fmt, ...) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " == " #expect), (bytes), (expect), /*expect_eq=*/ true, fmt, ##__VA_ARGS__) +#define DN_TestVerifyBytesEq(test_ptr, bytes, expect) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " == " #expect), (bytes), (expect), /*expect_eq=*/ true, "") +#define DN_TestVerifyBytesNotEqF(test_ptr, bytes, expect, fmt, ...) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " != " #expect), (bytes), (expect), /*expect_eq=*/ false, fmt, ##__VA_ARGS__) +#define DN_TestVerifyBytesNotEq(test_ptr, bytes, expect) DN_TestVerifyBytesF(test_ptr, DN_CallSiteNow, DN_Str8Lit(#bytes " != " #expect), (bytes), (expect), /*expect_eq=*/ false, "") + +DN_API DN_Str8 DN_Str8FromTestCore (DN_TestCore const *test, DN_Arena *arena, DN_Str8FromTestCoreFlags flags); + +#if DN_WITH_TESTS +DN_API DN_TestCore DN_TestSuite (DN_Arena *arena_); +#endif // TODO: Replace with a C implementation template @@ -3868,16 +4257,16 @@ template DN_BinarySearchResult DN_BinarySearch (T co template bool DN_BinarySearch_DefaultLessThan(T const &lhs, T const &rhs) { - bool result = lhs < rhs; - return result; + 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_USize array_size, + T const &find, + DN_BinarySearchType type, + DN_BinarySearchLessThanProc less_than) { DN_BinarySearchResult result = {}; if (!array || array_size <= 0 || !less_than) @@ -3917,35 +4306,55 @@ DN_BinarySearchResult DN_BinarySearch(T const *array, return result; } -DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); -DN_API void DN_LeakDump_ (DN_LeakTracker *leak); +DN_API void DN_LeakTrackAlloc_ (DN_LeakTracker *leak, void *ptr, DN_USize size, bool alloc_can_leak); +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr); +DN_API void DN_LeakDump_ (DN_LeakTracker *leak); #if defined(DN_LEAK_TRACKING) -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) -#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) -#define DN_LeakDump(leak) DN_LeakDump_(leak) +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) DN_LeakTrackAlloc_(leak, ptr, size, alloc_can_leak) +#define DN_LeakTrackDealloc(leak, ptr) DN_LeakTrackDealloc_(leak, ptr) +#define DN_LeakDump(leak) DN_LeakDump_(leak) #else -#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) -#define DN_LeakDump(leak) do { } while (0) +#define DN_LeakTrackAlloc(leak, ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) +#define DN_LeakTrackDealloc(leak, ptr) do { (void)ptr; } while (0) +#define DN_LeakDump(leak) do { } while (0) #endif #if DN_WITH_OS -DN_API DN_MemFuncs DN_MemFuncsFromType (DN_MemFuncsType type); -DN_API DN_MemFuncs DN_MemFuncsDefault (); -DN_API DN_MemList DN_MemListFromHeap (DN_U64 size, DN_MemFlags flags); -DN_API DN_MemList DN_MemListFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromHeap (DN_U64 wize, DN_MemFlags flags); -DN_API DN_Arena DN_ArenaFromVMem (DN_U64 reserve, DN_U64 commit, DN_MemFlags flags); - -DN_API DN_Str8 DN_Str8FromHeapF (DN_FMT_ATTRIB char const *fmt, ...); -DN_API DN_Str8 DN_Str8FromHeap (DN_USize count, DN_ZMem z_mem); -DN_API DN_Str8 DN_Str8BuilderBuildFromHeap (DN_Str8Builder const *builder); - +DN_API DN_Str8 DN_OS_Str8FromStr8BuilderHeap (DN_Str8Builder const *builder); DN_API void DN_OS_LogPrint (DN_LogTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args); DN_API void DN_OS_SetLogPrintFuncToOS (); +// NOTE: Heap +// Overview +// Helper functions to create a heap allocator that utilises OS memory allocators. + +// API +// DN_OS_HeapBasicAlloc +// DN_OS_HeapBasicDealloc +// Helper functions that can be used with as the `basic_alloc` and `basic_dealloc` functions for +// `DN_Heap`. These functions essentially call directly into `DN_OS_MemAlloc` and +// `DN_OS_MemDealloc` + +// DN_OS_HeapInitVirtual +// Create a heap allocator using the OS's native virtual memory APIs (e.g.: reserve, commit, +// release) + +// DN_OS_HeapInitBasic +// Create a heap allocator using the OS's native malloc-like APIs (e.g.: HeapAlloc on Windows, +// malloc on linux) + +// DN_OS_HeapInitDefault +// Create a heap allocator using the OS's native memory APIs. Defaults to virtual memory +// always unless you are on Emscripten where it'll use basic allocator (virtual memory is not +// supported through Emscripten). + +DN_API void * DN_OS_HeapBasicAlloc (DN_USize size); +DN_API void DN_OS_HeapBasicDealloc (void *ptr); +DN_API DN_Heap DN_OS_HeapInitBasic (); +DN_API DN_Heap DN_OS_HeapInitVirtual (); +DN_API DN_Heap DN_OS_HeapInitDefault (); + DN_API void * DN_OS_MemReserve (DN_USize count, DN_MemCommit commit, DN_MemPage page_flags); DN_API bool DN_OS_MemCommit (void *ptr, DN_USize count, DN_U32 page_flags); DN_API void DN_OS_MemDecommit (void *ptr, DN_USize count); @@ -4061,30 +4470,65 @@ DN_API bool DN_OS_ConditionVariableWaitUntil (D DN_API void DN_OS_ConditionVariableSignal (DN_OSConditionVariable *cv); DN_API void DN_OS_ConditionVariableBroadcast (DN_OSConditionVariable *cv); -DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_TCInitArgs tc_init_args, void *user_context); +// NOTE: Thread +// Overview +// Threading primitives that uses CreateThread on Win32 and pthreads everywhere else. Threads +// are also the primitives to implement lanes, a multi-core by default paradigm. More +// information is detailed in the OS's Thread Lane APIs. +// API +// DN_OS_ThreadInit +// Create a thread that will immediately execute `func`. Pass in a zero initialised `thread` +// that will get its fields initialised with the thread information. +// +// `tc_init_args` customises how much memory is allocated for the thread's TLS context which +// contains allocators that are exclusively owned by the thread. You may use +// DN_TCInitArgsDefault() for sensible default values. +// +// Note not all fields in the thread will be initialised until the thread has itself run its +// initialisation code which is entirely dependent on the OS scheduler, scheduling the thread to +// execute. +// +// TODO: Allow the caller to wait on the thread initialisation's completion + +// DN_OS_ThreadInitLane +// The same as `DN_OS_ThreadInit` but allows setting the lane information. `DN_OS_ThreadInit` +// calls into this function with a `lane == NULL` value. +// +// If you are intended to execute code in a lane-esque manner (e.g. GPU style programming on the +// CPU, multiple threads, single/linear program execution) pass in `lane` with the fields +// populated (the thread's index, how many threads there are in the laneway and the barrier to +// share across lanes must be initialised to synchronise different threads). +// +// For the general use-case if you wish to use lanes then prefer the `DN_OS_ThreadLane` APIs +// below which create the threads with the lane information setup accordingly. +DN_API bool DN_OS_ThreadInitLane (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadLane *lane, DN_OSThreadInitArgs init_args, void *user_context); +DN_API bool DN_OS_ThreadInit (DN_OSThread *thread, DN_OSThreadFunc *func, DN_OSThreadInitArgs init_args, void *user_context); DN_API bool DN_OS_ThreadJoin (DN_OSThread *thread, DN_TCDeinitArenas deinit_arenas); DN_API DN_U32 DN_OS_ThreadID (); +DN_API DN_OSThreadInitArgs DN_OS_ThreadInitArgsDefault (); DN_API void DN_OS_ThreadSetNameFmt (char const *fmt, ...); -// NOTE: Thread lanes provide an abstraction to represent the concept of programming a CPU like a -// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan -// Fleury. SIMT is formally defined as +// NOTE: Thread Lanes +// Overview +// Thread lanes provide an abstraction to represent the concept of programming a CPU like a +// GPU, e.g. SIMT (Single Instruction Multiple Threads). The lane terminology is popularised by Ryan +// Fleury. SIMT is formally defined as // -// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same -// instruction in lockstep, but each thread operates on different data and maintains its own state +// Threads are grouped into warps/wavefronts (typically 32 or 64 threads) that execute the same +// instruction in lockstep, but each thread operates on different data and maintains its own state // -// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a -// thread lane here contains the necessary state to facilitate this such as the current index in the -// wavefront and synchronisation primitives to coordinate the different lanes together. +// The individual threads in a wavefront on the CPU side are colloquially dubbed "lanes" and a +// thread lane here contains the necessary state to facilitate this such as the current index in the +// wavefront and synchronisation primitives to coordinate the different lanes together. // -// The idea is to write code in a single-threaded manner (linear execution) but across multiple -// threads so that the default is all execution paths are inherently multi-threaded by default. Opt -// out of parallelism instead of opt in. This optimises for the trend of core counts increasing -// whilst clock counts remain static. +// The idea is to write code in a single-threaded manner (linear execution) but across multiple +// threads so that the default is all execution paths are inherently multi-threaded by default. Opt +// out of parallelism instead of opt in. This optimises for the trend of core counts increasing +// whilst clock counts remain static. // -// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and -// setup the required synchronisation primitives. It can then be dispatched all the threads which -// start executing the `entry_point` in parallel. +// A laneway is a helper function to initialise the number of requested OS threads/lanes upfront and +// setup the required synchronisation primitives. It can then be dispatched all the threads which +// start executing the `entry_point` in parallel. // // API // DN_OS_ThreadLaneSync @@ -4132,19 +4576,18 @@ DN_API DN_V2USize DN_OS_ThreadLaneRange (D DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArgs (DN_OSThread* threads, DN_USize threads_count, DN_UPtr* shared_mem); DN_API DN_OSThreadLaneway DN_OS_ThreadLanewayFromArena (DN_USize threads_count, DN_Arena* arena); -DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_TCInitArgs tc_init_args, void *user_context); +DN_API void DN_OS_ThreadLanewayDispatch (DN_OSThreadLaneway *laneway, DN_OSThreadFunc *entry_point, DN_OSThreadInitArgs init_args, void *user_context); DN_API void DN_OS_ThreadLanewayJoin (DN_OSThreadLaneway *laneway, DN_TCDeinitArenas deinit_arenas); DN_API DN_OSThreadLane* DN_OS_TCThreadLane (); DN_API void DN_OS_TCThreadLaneSync (void **ptr_to_share); DN_API DN_OSThreadLane DN_OS_TCThreadLaneEquip (DN_OSThreadLane lane); - -DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); -DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); -DN_API bool DN_OS_AsyncQueueWork(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask(DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); -DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); +DN_API void DN_OS_AsyncInit (DN_OSAsyncCore *async, char *base, DN_USize base_size, DN_OSThread *threads, DN_U32 threads_size); +DN_API void DN_OS_AsyncDeinit (DN_OSAsyncCore *async); +DN_API bool DN_OS_AsyncQueueWork (DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API DN_OSAsyncTask DN_OS_AsyncQueueTask (DN_OSAsyncCore *async, DN_OSAsyncWorkFunc *func, void *input, DN_U64 wait_time_ms); +DN_API bool DN_OS_AsyncWaitTask (DN_OSAsyncTask *task, DN_U32 timeout_ms); // NOTE: DN_OSPrint enum DN_OSPrintDest @@ -4312,16 +4755,16 @@ bool DN_TArrayResizeFromArena(T **data, DN_USize *count, DN_USize *max, DN_Arena } template -bool DN_TArrayGrowFromPool(T **data, DN_USize count, DN_USize *max, DN_Pool *pool, DN_USize new_max) +bool DN_TArrayGrowFromPool(T **data, DN_USize *max, DN_Pool *pool, DN_USize new_max) { - bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, count, max, sizeof(**data), pool, new_max); + bool result = DN_ArrayGrowFromPool(DN_Cast(void **)data, max, sizeof(**data), pool, new_max); return result; } template -bool DN_TArrayGrowFromArena(T **data, DN_USize count, DN_USize *max, DN_Arena *arena, DN_USize new_max) +bool DN_TArrayGrowFromArena(T **data, DN_USize *max, DN_Arena *arena, DN_USize new_max) { - bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, count, max, sizeof(**data), arena, new_max); + bool result = DN_ArrayGrowFromArena(DN_Cast(void **)data, max, sizeof(**data), arena, new_max); return result; } @@ -4338,6 +4781,34 @@ bool DN_TArrayGrowIfNeededFromArena(T **data, DN_USize count, DN_USize *max, DN_ bool result = DN_ArrayGrowIfNeededFromArena(DN_Cast(void **)data, count, max, sizeof(**data), arena, add_count); return result; } + +template +DN_USize DN_TArrayCopyPtrArena(T **data, T const *src, DN_USize count, DN_Arena *arena) +{ + DN_USize result = DN_ArrayCopyPtrArena(DN_Cast(void **)data, src, count, sizeof(T), arena); + return result; +} + +template +DN_USize DN_TArrayCopyPtrArenaAssert(T **data, T const *src, DN_USize count, DN_Arena *arena, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrArenaAssert(DN_Cast(void **)data, src, count, sizeof(T), arena, call_site); + return result; +} + +template +DN_USize DN_TArrayCopyPtrPool(T **data, T const *src, DN_USize count, DN_Pool *pool) +{ + DN_USize result = DN_ArrayCopyPtrPool(DN_Cast(void **)data, src, count, sizeof(T), pool); + return result; +} + +template +DN_USize DN_TArrayCopyPtrPoolAssert(T **data, T const *src, DN_USize count, DN_Pool *pool, DN_CallSite call_site) +{ + DN_USize result = DN_ArrayCopyPtrPoolAssert(DN_Cast(void **)data, src, count, sizeof(T), pool, call_site); + return result; +} #endif // defined(__cplusplus) #if DN_WITH_NET @@ -4549,14 +5020,14 @@ DN_NETResponse DN_NET_EmcWaitForAnyResponse(DN_NETCore *net, DN_Arena *aren #endif // #if DN_WITH_NET_EMSCRIPTEN #if DN_WITH_OS -#if defined(DN_PLATFORM_WIN32) - #include "OS/dn_os_windows.h" - #include "OS/dn_os_w32.h" -#elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - #include "OS/dn_os_posix.h" -#else - #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs -#endif + #if defined(DN_PLATFORM_WIN32) + #include "OS/dn_os_windows.h" + #include "OS/dn_os_w32.h" + #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + #include "OS/dn_os_posix.h" + #else + #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs + #endif #endif // #if DN_WITH_OS #endif // #if !defined(DN_H) diff --git a/Source/dn_tests.cpp b/Source/dn_tests.cpp new file mode 100644 index 0000000..dbf63e3 --- /dev/null +++ b/Source/dn_tests.cpp @@ -0,0 +1,40 @@ +#if DN_WITH_NET_CURL + #define DN_NO_WINDOWS_H_REPLACEMENT_HEADER +#endif + +#define DN_PARANOIA_LEVEL 1 +#define DN_WITH_OS 1 +#define DN_WITH_NET 1 +#define DN_WITH_TESTS 1 +#include "dn.h" + +#if DN_WITH_NET_CURL + #define CURL_STATICLIB + #include +#endif + +#if defined(DN_PLATFORM_EMSCRIPTEN) + #define DN_WITH_NET_EMSCRIPTEN 1 +#endif +#include "dn.cpp" + +#define DN_SHA3_WITH_TESTS +#define DN_SHA3_IMPLEMENTATION +#include "Standalone/dn_sha3.h" + +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 dn = {}; + DN_Init(&dn, DN_InitFlags_LogAllFeatures | DN_InitFlags_OS, DN_TCInitArgsDefault()); + + DN_Arena* arena = DN_TCMainArena(); + DN_TestCore dn_test = DN_TestSuite(arena); + DN_TestCore sha3_test = DN_SHA3_TestSuite(arena); + DN_Str8 dn_str8 = DN_Str8FromTestCore(&dn_test, arena, DN_Str8FromTestCoreFlags_Colour); + DN_Str8 sha3_str8 = DN_Str8FromTestCore(&sha3_test, arena, DN_Str8FromTestCoreFlags_Colour); + DN_OS_PrintOutF("%.*s\n%.*s", DN_Str8PrintFmt(dn_str8), DN_Str8PrintFmt(sha3_str8)); + return 0; +} +DN_MSVC_WARNING_POP diff --git a/build.bat b/build.bat index 368237f..d36fb66 100644 --- a/build.bat +++ b/build.bat @@ -34,17 +34,17 @@ pushd %build_dir% :: GR- Disable C RTTI :: Oi Use CPU Intrinsics :: Z7 Combine multi-debug files to one debug file - set flags=%flags% -D DN_UNIT_TESTS_WITH_KECCAK %script_dir%\Source\Extra\dn_tests_main.cpp + set flags=%flags% %script_dir%\Source\dn_tests.cpp set msvc_driver_flags=-EHa -GR- -Od -Oi -Z7 -wd4201 -W4 -nologo %flags% -fsanitize=address where /q emcc && ( echo [BUILD] Emscripten emcc detected, compiling ... - call emcc -g -msimd128 -msse2 %flags% -o %build_dir%\dn_unit_tests_emcc.js -s FETCH=1 -pthread -s ASYNCIFY=1 -lwebsocket -Wall || echo EMCC build failed&& exit /b 1 + call emcc -g -msimd128 -msse2 %flags% -o %build_dir%\dn_tests_emcc.js -s FETCH=1 -pthread -s ASYNCIFY=1 -lwebsocket -Wall || echo EMCC build failed&& exit /b 1 ) where /q cl && ( echo [BUILD] MSVC cl detected, compiling ... - set msvc_cmd=cl -MTd %msvc_driver_flags% -analyze -Fe:dn_unit_tests_msvc -Fo:dn_unit_tests_msvc + set msvc_cmd=cl -MTd %msvc_driver_flags% -analyze -Fe:dn_tests_msvc -Fo:dn_tests_msvc if exist %build_dir%/Curl/Install/lib/libcurl-d.lib ( set msvc_cmd=!msvc_cmd! -D DN_WITH_NET_CURL=1 -I %build_dir%/Curl/Install/include %build_dir%/Curl/Install/lib/libcurl-d.lib crypt32.lib ws2_32.lib advapi32.lib wldap32.lib iphlpapi.lib secur32.lib ) @@ -63,7 +63,7 @@ pushd %build_dir% where /q clang-cl && ( echo [BUILD] LLVM clang-cl detected, compiling ... - set clang_cmd=clang-cl -MT %msvc_driver_flags% -fsanitize=undefined -Fe:dn_unit_tests_clang -link + set clang_cmd=clang-cl -MT %msvc_driver_flags% -fsanitize=undefined -Fe:dn_tests_clang -link powershell -Command "$time = Measure-Command { !clang_cmd! | Out-Default }; Write-Host '[BUILD] clang-cl:'$time.TotalSeconds's'; exit $LASTEXITCODE" || echo CLANG build failed&& exit /b 1 ) diff --git a/single_header_generator.cpp b/single_header_generator.cpp index 6b6b0c6..6e9528c 100644 --- a/single_header_generator.cpp +++ b/single_header_generator.cpp @@ -65,7 +65,7 @@ static void AppendCppFileLineByLine(DN_Str8Builder *dest, DN_Str8 cpp_path) line = DN_Str8FromFmtArena(&scratch.arena, "%S// DN: Single header generator commented out => %S", find.start_to_before_match, DN_Str8TrimWhitespaceAround(find.match_to_end_of_buffer)); DN_Str8 rel_include_path = DN_Str8TrimWhitespaceAround(find.after_match_to_end_of_buffer); DN_Str8 root_dir = DN_Str8FileDirectoryFromPath(cpp_path); - extra_include_path = DN_OS_PathF(&scratch.arena, "%S/%S", root_dir, DN_Str8TrimSuffix(rel_include_path, DN_Str8Lit("\""))); + extra_include_path = DN_OS_PathF(&scratch.arena, "%S/%S", root_dir, DN_Str8TrimSuffixSensitive(rel_include_path, DN_Str8Lit("\""))); } } }