diff --git a/Single-Header/dn_single_header.cpp b/Single-Header/dn_single_header.cpp index 53ffa8c..1d924ea 100644 --- a/Single-Header/dn_single_header.cpp +++ b/Single-Header/dn_single_header.cpp @@ -1,4 +1,4 @@ -// Generated by the DN single header generator 2025-11-08 18:54:58 +// Generated by the DN single header generator 2025-11-09 13:19:26 #define DN_BASE_INC_CPP @@ -781,7 +781,6 @@ DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) return result; } - static DN_ArenaBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_ArenaMemFuncs mem_funcs) { DN_ArenaBlock *result = nullptr; @@ -828,7 +827,7 @@ static DN_ArenaBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, } if (track_alloc && result) - DN_DBGTrackAlloc(result, result->reserve, alloc_can_leak); + DN_LeakTrackAlloc(dn->leak, result, result->reserve, alloc_can_leak); return result; } @@ -895,7 +894,7 @@ static void DN_ArenaBlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block) { DN_USize release_size = block->reserve; if (DN_BitIsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack)) - DN_DBGTrackDealloc(block); + DN_LeakTrackDealloc(&g_dn_->leak, block); DN_ASanUnpoisonMemoryRegion(block, block->commit); if (arena->flags & DN_ArenaFlags_MemFuncs) { if (arena->mem_funcs.type == DN_ArenaMemFuncType_Basic) @@ -2906,6 +2905,173 @@ DN_API DN_Str8x32 DN_ByteCountStr8x32FromType(DN_U64 bytes, DN_ByteCountType typ DN_Str8x32 result = DN_Str8x32FromFmt("%.2f%.*s", byte_count.bytes, DN_Str8PrintFmt(byte_count.suffix)); return result; } + +DN_API DN_Profiler DN_ProfilerInit(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency) +{ + DN_Profiler result = {}; + result.anchors = anchors; + result.anchors_count = count; + result.anchors_per_frame = anchors_per_frame; + result.tsc_now = tsc_now; + result.tsc_frequency = tsc_frequency; + + DN_AssertF(result.tsc_frequency != 0, + "You must set this to the frequency of the timestamp counter function (TSC) (e.g. how " + "many ticks occur between timestamps). We use this to determine the duration between " + "each zone's recorded TSC. For example if the 'tsc_now' was set to Window's " + "QueryPerformanceCounter then 'tsc_frequency' would be set to the value of " + "QueryPerformanceFrequency which is typically 10mhz (e.g. The duration between two " + "consecutive TSC's is 10mhz)." + "" + "Hence frequency can't be zero otherwise it's a divide by 0. If you don't have a TSC " + "function and pass in null, the profiler defaults to rdtsc() and you must measure the " + "frequency of rdtsc yourself. The reason for this is that measuring rdtsc requires " + "having some alternate timing mechanism to measure the duration between the TSCs " + "provided by rdtsc and this profiler makes no assumption about what timing primitives " + "are available other than rdtsc which is a CPU builtin available on basically all " + "platforms or have an equivalent (e.g. __builtin_readcyclecounter)" + "" + "This codebase provides DN_OS_EstimateTSCPerSecond() as an example of how to that for " + "convenience and is available if compiling with the OS layer. Some platforms like " + "Emscripten don't support rdtsc() so you should use an alternative method like " + "emscripten_get_now() or clock_gettime with CLOCK_MONOTONIC."); + return result; +} + +DN_API DN_USize DN_ProfilerFrameCount(DN_Profiler const *profiler) +{ + DN_USize result = profiler->anchors_count / profiler->anchors_per_frame; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) +{ + DN_ProfilerAnchorArray result = {}; + DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; + result.data = profiler->anchors + anchor_offset; + result.count = profiler->anchors_per_frame; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors(DN_Profiler *profiler) +{ + DN_ProfilerAnchorArray result = DN_ProfilerFrameAnchorsFromIndex(profiler, profiler->frame_index); + return result; +} + +DN_API DN_ProfilerZone DN_ProfilerBeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) +{ + DN_ProfilerZone result = {}; + if (profiler->paused) + return result; + + DN_Assert(anchor_index < profiler->anchors_per_frame); + DN_ProfilerAnchor *anchor = DN_ProfilerFrameAnchors(profiler).data + anchor_index; + anchor->name = name; + + // TODO: We need per-thread-local-storage profiler so that we can use these apis + // across threads. For now, we let them overwrite each other but this is not tenable. + #if 0 + if (anchor->name.size && anchor->name != name) + DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); + #endif + + result.begin_tsc = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + result.anchor_index = anchor_index; + result.parent_zone = profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + profiler->parent_zone = anchor_index; + return result; +} + +DN_API void DN_ProfilerEndZone(DN_Profiler *profiler, DN_ProfilerZone zone) +{ + if (profiler->paused) + return; + + DN_Assert(zone.anchor_index < profiler->anchors_per_frame); + DN_Assert(zone.parent_zone < profiler->anchors_per_frame); + + DN_ProfilerAnchorArray array = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; + DN_U64 tsc_now = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; + + anchor->hit_count++; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + + if (zone.parent_zone != zone.anchor_index) { + DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + } + profiler->parent_zone = zone.parent_zone; +} + +DN_API void DN_ProfilerNewFrame(DN_Profiler *profiler) +{ + if (profiler->paused) + return; + + // NOTE: End the frame's zone + DN_ProfilerEndZone(profiler, profiler->frame_zone); + DN_ProfilerAnchorArray old_frame_anchors = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; + profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; + + // NOTE: Bump to the next frame + DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; + profiler->frame_index = (profiler->frame_index + 1) % frame_count; + + // NOTE: Zero out the anchors + DN_ProfilerAnchorArray next_anchors = DN_ProfilerFrameAnchors(profiler); + DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); + + // NOTE: Start the frame's zone + profiler->frame_zone = DN_ProfilerBeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); +} + +DN_API void DN_ProfilerDump(DN_Profiler *profiler) +{ + if (profiler->frame_index == 0) + return; + + DN_USize frame_index = profiler->frame_index - 1; + DN_Assert(profiler->frame_index < profiler->anchors_per_frame); + + DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); + for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { + DN_ProfilerAnchor const *anchor = anchors + index; + if (!anchor->hit_count) + continue; + + DN_U64 tsc_exclusive = anchor->tsc_exclusive; + DN_U64 tsc_inclusive = anchor->tsc_inclusive; + DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; + if (tsc_exclusive == tsc_inclusive) { + DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); + } else { + DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; + DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", + DN_Str8PrintFmt(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds); + } + } +} + +DN_API DN_F64 DN_ProfilerSecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; + return result; +} + +DN_API DN_F64 DN_ProfilerMsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; + return result; +} // DN: Single header generator inlined this file => #include "Base/dn_base_containers.cpp" #define DN_CONTAINERS_CPP @@ -4513,6 +4679,149 @@ DN_API DN_LOGTypeParam DN_LOG_MakeU32LogTypeParam(DN_LOGType type) result.u32 = type; return result; } +// DN: Single header generator inlined this file => #include "Base/dn_base_leak.cpp" +#define DN_BASE_LEAK_CPP + +// DN: Single header generator commented out this header => #include "../dn_base_inc.h" + +DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&leak->alloc_table_mutex); + }; + + // NOTE: If the entry was not added, we are reusing a pointer that has been freed. + // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it + // already existed. + DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { + DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); + DN_Str8x32 new_alloc_size = DN_ByteCountStr8x32(size); + DN_HardAssertF( + alloc->flags & DN_LeakAllocFlag_Freed, + "This pointer is already in the leak tracker, however it has not been freed yet. This " + "same pointer is being ask to be tracked twice in the allocation table, e.g. one if its " + "previous free calls has not being marked freed with an equivalent call to " + "DN_LeakTrackDealloc()\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is allocating %.*s again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(new_alloc_size), + DN_Str8PrintFmt(stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; + + DN_OS_MemDealloc(alloc->stack_trace.data); + DN_OS_MemDealloc(alloc->freed_stack_trace.data); + *alloc = {}; + } + + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace; + alloc->flags |= leak_permitted ? DN_LeakAllocFlag_LeakPermitted : 0; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; +} + +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&leak->alloc_table_mutex); + }; + + DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); + DN_HardAssertF(alloc_entry.found, + "Allocated pointer can not be removed as it does not exist in the " + "allocation table. When this memory was allocated, the pointer was " + "not added to the allocation table [ptr=%p]", + ptr); + + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc->flags & DN_LeakAllocFlag_Freed) { + DN_Str8x32 freed_size = DN_ByteCountStr8x32(alloc->freed_size); + DN_HardAssertF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, + "Double free detected, pointer to free was already marked " + "as freed. Either the pointer was reallocated but not " + "traced, or, the pointer was freed twice.\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer was freed at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is being freed again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(freed_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(alloc->freed_stack_trace), + DN_Str8PrintFmt(stack_trace)); + } + + DN_Assert(alloc->freed_stack_trace.size == 0); + alloc->flags |= DN_LeakAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; +} + +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); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); + DN_LOG_WarningF( + "Pointer (0x%p) leaked %.*s at:\n" + "%.*s", + alloc->ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace)); + } + } + + if (leak_count) { + DN_Str8x32 leak_size = DN_ByteCountStr8x32(leaked_bytes); + DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); + } +} #define DN_OS_INC_CPP // DN: Single header generator inlined this file => #include "OS/dn_os_tls.cpp" @@ -4924,8 +5233,6 @@ DN_API void DN_OS_ErrSinkAppendF_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_A #include // getpagesize #endif -static DN_OSCore *g_dn_os_core_; - static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) { DN_Assert(user_data); @@ -5001,92 +5308,6 @@ static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_OS_PrintLnFV(dest, fmt, args); } -DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) -{ - g_dn_os_core_ = os; - - // NOTE: OS - { - #if defined(DN_PLATFORM_WIN32) - SYSTEM_INFO system_info = {}; - GetSystemInfo(&system_info); - - os->logical_processor_count = system_info.dwNumberOfProcessors; - os->page_size = system_info.dwPageSize; - os->alloc_granularity = system_info.dwAllocationGranularity; - #else - #if defined(DN_PLATFORM_EMSCRIPTEN) - os->logical_processor_count = 1; - #else - os->logical_processor_count = get_nprocs(); - #endif - os->page_size = getpagesize(); - os->alloc_granularity = os->page_size; - #endif - } - - // NOTE: Setup logging - DN_OS_EmitLogsWithOSPrintFunctions(os); - - { - #if defined(DN_PLATFORM_EMSCRIPTEN) - os->arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_ArenaFlags_NoAllocTrack); - #else - os->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); - #endif - - #if defined(DN_PLATFORM_WIN32) - os->platform_context = DN_ArenaNew(&os->arena, DN_W32Core, DN_ZMem_Yes); - #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - os->platform_context = DN_ArenaNew(&os->arena, DN_POSIXCore, DN_ZMem_Yes); - #endif - - #if defined(DN_PLATFORM_WIN32) - DN_W32Core *w32 = DN_Cast(DN_W32Core *) os->platform_context; - InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); - - QueryPerformanceFrequency(&w32->qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - if (module) { - w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - } - - // NOTE: win32 bcrypt - wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (w32->bcrypt_rng_handle && init_status == 0) - w32->bcrypt_init_success = true; - else - DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); - #else - DN_Posix_Init(DN_Cast(DN_POSIXCore *)os->platform_context); - #endif - } - - // NOTE: Initialise tmem arenas which allocate memory and will be - // recorded to the now initialised allocation table. The initialisation - // of tmem memory may request tmem memory itself in leak tracing mode. - // This is supported as the tmem arenas defer allocation tracking until - // initialisation is done. - DN_OSTLSInitArgs tls_init_args = {}; - if (args) { - tls_init_args.commit = args->tls_commit; - tls_init_args.reserve = args->tls_reserve; - tls_init_args.err_sink_reserve = args->tls_err_sink_reserve; - tls_init_args.err_sink_commit = args->tls_err_sink_commit; - } - - DN_OS_TLSInit(&os->tls, tls_init_args); - DN_OS_TLSSetCurrentThreadTLS(&os->tls); - os->cpu_report = DN_CPUGetReport(); - - #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - DN_Assert(g_dn_os_core_); -} - DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os) { DN_Assert(os); @@ -5237,7 +5458,7 @@ DN_API bool DN_OS_DateIsValid(DN_OSDateTime date) return true; } -// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Other DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) { DN_Str8 result = {}; @@ -5251,7 +5472,7 @@ DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) return result; } -// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +// NOTE: Counters DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) { uint64_t frequency = DN_OS_PerfCounterFrequency(); @@ -5760,7 +5981,7 @@ DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags fl { DN_ArenaMemFuncs mem_funcs = {}; mem_funcs.type = DN_ArenaMemFuncType_VMem; - mem_funcs.vmem_page_size = g_dn_os_core_->page_size; + mem_funcs.vmem_page_size = g_dn_->os.page_size; mem_funcs.vmem_reserve = DN_OS_MemReserve; mem_funcs.vmem_commit = DN_OS_MemCommit; mem_funcs.vmem_release = DN_OS_MemRelease; @@ -5964,7 +6185,7 @@ bool DN_VArray_Reserve(DN_VArray *array, DN_USize count) return false; DN_USize real_commit = (array->size + count) * sizeof(T); - DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_os_core_->page_size); + DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_->os.page_size); if (array->commit >= aligned_commit) return true; bool result = DN_OS_MemCommit(array->data, aligned_commit, DN_MemPage_ReadWrite); @@ -6480,8 +6701,8 @@ DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_fl os_page_flags |= (PROT_READ | PROT_WRITE); void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); if (result == MAP_FAILED) result = nullptr; return result; @@ -6495,8 +6716,8 @@ DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); result = mprotect(ptr, size, os_page_flags) == 0; - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); return result; } @@ -6519,11 +6740,11 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_Str8Lit( "Page protection requires pointers to be page aligned because we " "can only guard memory at a multiple of the page boundary."); - DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_os_core_->page_size), + DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); DN_AssertF( - DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_IsPowerOfTwoAligned(size, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); int result = mprotect(ptr, size, os_page_flags); @@ -6734,8 +6955,8 @@ DN_API DN_U64 DN_OS_PerfCounterFrequency() static DN_POSIXCore *DN_OS_GetPOSIXCore_() { - DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); - DN_POSIXCore *result = DN_Cast(DN_POSIXCore *)g_dn_os_core_->platform_context; + DN_Assert(g_dn_ && g_dn_->os.platform_context); + DN_POSIXCore *result = DN_Cast(DN_POSIXCore *)g_dn_->os.platform_context; return result; } @@ -7466,7 +7687,7 @@ static DN_POSIXSyncPrimitive *DN_POSIX_AllocSyncPrimitive_() posix->sync_primitive_free_list = posix->sync_primitive_free_list->next; result->next = nullptr; } else { - DN_OSCore *os = g_dn_os_core_; + DN_OSCore *os = &g_dn_->os; result = DN_ArenaNew(&os->arena, DN_POSIXSyncPrimitive, DN_ZMem_Yes); } } @@ -8021,9 +8242,9 @@ DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_fl void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); if (flags & MEM_COMMIT) { - DN_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_frame, 1); } return result; } @@ -8035,9 +8256,9 @@ DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) return false; unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; - DN_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_frame, 1); return result; } @@ -8065,8 +8286,8 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_Str8Lit("Page protection requires pointers to be page aligned because we can only guard memory at a multiple of the page boundary."); - DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); - DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); unsigned long prev_flags = 0; @@ -8083,9 +8304,9 @@ DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZMem z_mem) 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_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); return result; } @@ -8195,8 +8416,8 @@ DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(DN_U64 time) DN_API void DN_OS_GenBytesSecure(void *buffer, DN_U32 size) { - DN_Assert(g_dn_os_core_); - DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(g_dn_); + DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_->os.platform_context; DN_Assert(w32->bcrypt_init_success); long gen_status = BCryptGenRandom(w32->bcrypt_rng_handle, DN_Cast(unsigned char *) buffer, size, 0 /*flags*/); @@ -8253,8 +8474,8 @@ DN_API void DN_OS_SleepMs(DN_UInt milliseconds) DN_API DN_U64 DN_OS_PerfCounterFrequency() { - DN_Assert(g_dn_os_core_); - DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(g_dn_); + DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_->os.platform_context; DN_Assert(w32->qpc_frequency.QuadPart); DN_U64 result = w32->qpc_frequency.QuadPart; return result; @@ -8972,8 +9193,8 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExe static DN_W32Core *DN_OS_GetW32Core_() { - DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); - DN_W32Core *result = DN_Cast(DN_W32Core *)g_dn_os_core_->platform_context; + DN_Assert(g_dn_ && g_dn_->os.platform_context); + DN_W32Core *result = DN_Cast(DN_W32Core *)g_dn_->os.platform_context; return result; } @@ -9003,7 +9224,7 @@ static DN_W32SyncPrimitive *DN_W32_AllocSyncPrimitive_() w32->sync_primitive_free_list = w32->sync_primitive_free_list->next; result->next = nullptr; } else { - DN_OSCore *os = g_dn_os_core_; + DN_OSCore *os = &g_dn_->os; result = DN_ArenaNew(&os->arena, DN_W32SyncPrimitive, DN_ZMem_Yes); } } @@ -9817,101 +10038,14 @@ DN_API bool DN_W32_DirWIterate(DN_Str16 path, DN_W32FolderIteratorW *it) #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif -#define DN_CORE_INC_CPP -// DN: Single header generator inlined this file => #include "Core/dn_core.cpp" -static DN_Core *g_dn_core; - -DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) -{ - DN_Assert(g_dn_os_core_); - g_dn_core = core; - - // NOTE Initialise fields - #if defined(DN_LEAK_TRACKING) - // NOTE: Setup the allocation table with allocation tracking turned off on - // the arena we're using to initialise the table. - core->alloc_table_arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); - core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); - #endif - - // NOTE: Print out init features /////////////////////////////////////////////////////////////// - DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); - DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); - if (on_init & DN_CoreOnInit_LogLibFeatures) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("DN initialised:\n")); - - DN_F32 page_size_kib = g_dn_os_core_->page_size / 1024.0f; - DN_F32 alloc_granularity_kib = g_dn_os_core_->alloc_granularity / 1024.0f; - DN_Str8BuilderAppendF(&builder, - " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n" - " Logical Processor Count: %u\n", - page_size_kib, - alloc_granularity_kib, - g_dn_os_core_->logical_processor_count); - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - if (DN_ASAN_POISON) { - DN_Str8BuilderAppendF( - &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); - DN_Str8BuilderAppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); - } - #endif - - #if defined(DN_LEAK_TRACKING) - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" Allocation leak tracing\n")); - #endif - - #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) - DN_POSIXCore *posix = DN_Cast(DN_POSIXCore *)g_dn_os_core_->platform_context; - DN_Str8BuilderAppendF(&builder, " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); - #endif - // TODO(doyle): Add stacktrace feature log - } - - if (on_init & DN_CoreOnInit_LogCPUFeatures) { - DN_CPUReport const *report = &g_dn_os_core_->cpu_report; - DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8BuilderAppendF' Actual type: 'struct DN_Str8'. - DN_Str8BuilderAppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); - DN_MSVC_WARNING_POP - - DN_USize longest_feature_name = 0; - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); - } - - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - bool has_feature = DN_CPUHasFeature(report, feature_decl.value); - DN_Str8BuilderAppendF(&builder, - " %.*s:%*s%s\n", - DN_Str8PrintFmt(feature_decl.label), - DN_Cast(int)(longest_feature_name - feature_decl.label.size), - "", - has_feature ? "available" : "not available"); - } - } - - DN_Str8 info_log = DN_Str8BuilderBuild(&builder, tmem.arena); - if (info_log.size) - DN_LOG_DebugF("%.*s", DN_Str8PrintFmt(info_log)); -} - -DN_API void DN_Core_BeginFrame() -{ - DN_AtomicSetValue64(&g_dn_os_core_->mem_allocs_frame, 0); -} -// DN: Single header generator inlined this file => #include "Core/dn_core_debug.cpp" +// DN: Single header generator inlined this file => #include "OS/dn_os_stacktrace.cpp" #define DN_CORE_DEBUG_CPP // DN: Single header generator commented out this header => #include "../dn_base_inc.h" // DN: Single header generator commented out this header => #include "../dn_os_inc.h" -// DN: Single header generator commented out this header => #include "../dn_core_inc.h" -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limit) +DN_API DN_StackTraceWalkResult DN_StackTraceWalk(DN_Arena *arena, uint16_t limit) { DN_StackTraceWalkResult result = {}; #if defined(DN_OS_WIN32) @@ -9929,8 +10063,8 @@ DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limi w32->sym_initialised = true; SymSetOptions(SYMOPT_LOAD_LINES); if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_W32Error error = DN_W32_LastError(tmem.arena); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_W32Error error = DN_W32_LastError(tmem.arena); DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg)); } } @@ -9975,18 +10109,18 @@ DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limi return result; } -static void DN_StackTrace_AddWalkToStr8Builder(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) +static void DN_StackTraceAddWalkToStr8Builder(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) { DN_StackTraceRawFrame raw_frame = {}; raw_frame.process = walk->process; for (DN_USize index = skip; index < walk->size; index++) { raw_frame.base_addr = walk->base_addr[index]; - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder->arena, raw_frame); + DN_StackTraceFrame frame = DN_StackTraceRawFrameToFrame(builder->arena, raw_frame); DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == walk->size - 1) ? "" : "\n"); } } -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) +DN_API bool DN_StackTraceWalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) { bool result = false; if (!it || !walk || !walk->base_addr || !walk->process) @@ -10001,1613 +10135,336 @@ DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, return result; } -DN_API DN_Str8 DN_StackTrace_WalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) +DN_API DN_Str8 DN_StackTraceWalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) { DN_Str8 result{}; if (!walk || !arena) return result; - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); - DN_StackTrace_AddWalkToStr8Builder(walk, &builder, skip); + DN_StackTraceAddWalkToStr8Builder(walk, &builder, skip); result = DN_Str8BuilderBuild(&builder, arena); return result; } -DN_API DN_Str8 DN_StackTrace_WalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) +DN_API DN_Str8 DN_StackTraceWalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) { - DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); - DN_Str8 result = DN_StackTrace_WalkResultToStr8(arena, &walk, skip); + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(tmem.arena, limit); + DN_Str8 result = DN_StackTraceWalkResultToStr8(arena, &walk, skip); return result; } -DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap(uint16_t limit, uint16_t skip) +DN_API DN_Str8 DN_StackTraceWalkStr8FromHeap(uint16_t limit, uint16_t skip) { // NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which // does not use the OS heap. - DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(&arena, limit); - DN_StackTrace_AddWalkToStr8Builder(&walk, &builder, skip); - DN_Str8 result = DN_Str8BuilderBuildFromOSHeap(&builder); + DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(&arena, limit); + DN_StackTraceAddWalkToStr8Builder(&walk, &builder, skip); + DN_Str8 result = DN_Str8BuilderBuildFromOSHeap(&builder); DN_ArenaDeinit(&arena); return result; } -DN_API DN_Slice DN_StackTrace_GetFrames(DN_Arena *arena, uint16_t limit) +DN_API DN_Slice DN_StackTraceGetFrames(DN_Arena *arena, uint16_t limit) { - DN_Slice result = {}; - if (!arena) - return result; - - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); - if (!walk.size) - return result; - - DN_USize slice_index = 0; - result = DN_Slice_Alloc(arena, walk.size, DN_ZMem_No); - for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { - result.data[slice_index++] = DN_StackTrace_RawFrameToFrame(arena, it.raw_frame); - } + DN_Slice result = {}; + if (!arena) return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(tmem.arena, limit); + if (!walk.size) + return result; + + DN_USize slice_index = 0; + result = DN_Slice_Alloc(arena, walk.size, DN_ZMem_No); + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTraceWalkResultIterate(&it, &walk);) + result.data[slice_index++] = DN_StackTraceRawFrameToFrame(arena, it.raw_frame); + return result; } -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) { - #if defined(DN_OS_WIN32) - // NOTE: Get line+filename ///////////////////////////////////////////////////////////////////// +#if defined(DN_OS_WIN32) + // NOTE: Get line+filename - // TODO: Why does zero-initialising this with `line = {};` cause - // SymGetLineFromAddr64 function to fail once we are at - // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The - // line and file number are still valid in the result which we use, so, - // we silently ignore this error. - IMAGEHLP_LINEW64 line; - line.SizeOfStruct = sizeof(line); - DWORD line_displacement = 0; - if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) { - line = {}; - } + // TODO: Why does zero-initialising this with `line = {};` cause + // SymGetLineFromAddr64 function to fail once we are at + // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The + // line and file number are still valid in the result which we use, so, + // we silently ignore this error. + IMAGEHLP_LINEW64 line; + line.SizeOfStruct = sizeof(line); + DWORD line_displacement = 0; + if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) + line = {}; - // NOTE: Get function name ///////////////////////////////////////////////////////////////////// + // NOTE: Get function name - alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; - SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *)buffer; - symbol->SizeOfStruct = sizeof(*symbol); - symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); + alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; + SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *) buffer; + symbol->SizeOfStruct = sizeof(*symbol); + symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); - uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address - SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); + uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address + SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); - // NOTE: Construct result ////////////////////////////////////////////////////////////////////// + // NOTE: Construct result - DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16Size(line.FileName)}; - DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; + DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16Size(line.FileName)}; + DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; - DN_StackTraceFrame result = {}; - result.address = raw_frame.base_addr; - result.line_number = line.LineNumber; - result.file_name = DN_W32_Str16ToStr8(arena, file_name16); - result.function_name = DN_W32_Str16ToStr8(arena, function_name16); + DN_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = DN_W32_Str16ToStr8(arena, file_name16); + result.function_name = DN_W32_Str16ToStr8(arena, function_name16); - if (result.function_name.size == 0) - result.function_name = DN_Str8Lit(""); - if (result.file_name.size == 0) - result.file_name = DN_Str8Lit(""); + if (result.function_name.size == 0) + result.function_name = DN_Str8Lit(""); + if (result.file_name.size == 0) + result.file_name = DN_Str8Lit(""); +#else + DN_StackTraceFrame result = {}; +#endif + return result; +} + +DN_API void DN_StackTracePrint(uint16_t limit) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Slice stack_trace = DN_StackTraceGetFrames(tmem.arena, limit); + for (DN_StackTraceFrame &frame : stack_trace) + DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); +} + +DN_API void DN_StackTraceReloadSymbols() +{ +#if defined(DN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); +#endif +} +#define DN_INC_CPP + +// DN: Single header generator inlined this file => #include "dn_inc.h" +#if !defined(DN_INC_H) +#define DN_INC_H + +struct DN_Core +{ + DN_USize mem_allocs_frame; + DN_LeakTracker leak; + #if defined(DN_OS_H) + DN_OSCore os; + #endif +}; + +struct DN_InitArgs +{ + DN_U64 os_tls_reserve; + DN_U64 os_tls_commit; + DN_U64 os_tls_err_sink_reserve; + DN_U64 os_tls_err_sink_commit; +}; + +typedef DN_USize DN_InitFlags; +enum DN_InitFlags_ +{ + DN_InitFlags_Nil = 0, + DN_InitFlags_OS = 1 << 0, + DN_InitFlags_LogLibFeatures = 1 << 1, + DN_InitFlags_LogCPUFeatures = 1 << 2, + DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, +}; + +extern DN_Core *g_dn_; + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args); +DN_API void DN_BeginFrame(); + +#endif // !defined(DN_INC_H) + +DN_Core *g_dn_; + +static void DN_InitOS_(DN_OSCore *os, DN_InitArgs *args) +{ + #if defined(DN_OS_H) && defined(DN_OS_CPP) + // NOTE: OS + { + #if defined(DN_PLATFORM_WIN32) + SYSTEM_INFO system_info = {}; + GetSystemInfo(&system_info); + + os->logical_processor_count = system_info.dwNumberOfProcessors; + os->page_size = system_info.dwPageSize; + os->alloc_granularity = system_info.dwAllocationGranularity; #else - DN_StackTraceFrame result = {}; + #if defined(DN_PLATFORM_EMSCRIPTEN) + os->logical_processor_count = 1; + #else + os->logical_processor_count = get_nprocs(); + #endif + os->page_size = getpagesize(); + os->alloc_granularity = os->page_size; #endif - return result; -} - -DN_API void DN_StackTrace_Print(uint16_t limit) -{ - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Slice stack_trace = DN_StackTrace_GetFrames(tmem.arena, limit); - for (DN_StackTraceFrame &frame : stack_trace) - DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); -} - -DN_API void DN_StackTrace_ReloadSymbols() -{ - #if defined(DN_OS_WIN32) - HANDLE process = GetCurrentProcess(); - SymRefreshModuleList(process); - #endif -} - -// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// -#if defined(DN_LEAK_TRACKING) -DN_API void DN_DBGTrackAlloc(void *ptr, DN_USize size, bool leak_permitted) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER - { - DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); - }; - - // NOTE: If the entry was not added, we are reusing a pointer that has been freed. - // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it - // already existed. - DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_Cast(uint64_t) ptr); - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { - if ((alloc->flags & DN_DebugAllocFlag_Freed) == 0) { - DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); - DN_Str8x32 new_alloc_size = DN_ByteCountStr8x32(size); - DN_HardAssertF( - alloc->flags & DN_DebugAllocFlag_Freed, - "This pointer is already in the leak tracker, however it has not been freed yet. This " - "same pointer is being ask to be tracked twice in the allocation table, e.g. one if its " - "previous free calls has not being marked freed with an equivalent call to " - "DN_DBGTrackDealloc()\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is allocating %.*s again at:\n" - "\n" - "%.*s\n", - ptr, - DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(new_alloc_size), - DN_Str8PrintFmt(stack_trace)); - } - - // NOTE: Pointer was reused, clean up the prior entry - g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; - - DN_OS_MemDealloc(alloc->stack_trace.data); - DN_OS_MemDealloc(alloc->freed_stack_trace.data); - *alloc = {}; } - alloc->ptr = ptr; - alloc->size = size; - alloc->stack_trace = stack_trace; - alloc->flags |= leak_permitted ? DN_DebugAllocFlag_LeakPermitted : 0; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; -} + // NOTE: Setup logging + DN_OS_EmitLogsWithOSPrintFunctions(os); -DN_API void DN_DBGTrackDealloc(void *ptr) -{ - if (!ptr) - return; + { + #if defined(DN_PLATFORM_EMSCRIPTEN) + os->arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_ArenaFlags_NoAllocTrack); + #else + os->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); + #endif - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER { DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); }; + #if defined(DN_PLATFORM_WIN32) + os->platform_context = DN_ArenaNew(&os->arena, DN_W32Core, DN_ZMem_Yes); + #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + os->platform_context = DN_ArenaNew(&os->arena, DN_POSIXCore, DN_ZMem_Yes); + #endif - DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); - DN_HardAssertF(alloc_entry.found, - "Allocated pointer can not be removed as it does not exist in the " - "allocation table. When this memory was allocated, the pointer was " - "not added to the allocation table [ptr=%p]", - ptr); + #if defined(DN_PLATFORM_WIN32) + DN_W32Core *w32 = DN_Cast(DN_W32Core *) os->platform_context; + InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc->flags & DN_DebugAllocFlag_Freed) { - DN_Str8x32 freed_size = DN_ByteCountStr8x32(alloc->freed_size); - DN_HardAssertF((alloc->flags & DN_DebugAllocFlag_Freed) == 0, - "Double free detected, pointer to free was already marked " - "as freed. Either the pointer was reallocated but not " - "traced, or, the pointer was freed twice.\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer was freed at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is being freed again at:\n" - "\n" - "%.*s\n" - , - ptr, DN_Str8PrintFmt(freed_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(alloc->freed_stack_trace), - DN_Str8PrintFmt(stack_trace)); + QueryPerformanceFrequency(&w32->qpc_frequency); + HMODULE module = LoadLibraryA("kernel32.dll"); + if (module) { + w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); } - DN_Assert(alloc->freed_stack_trace.size == 0); - alloc->flags |= DN_DebugAllocFlag_Freed; - alloc->freed_stack_trace = stack_trace; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; + // NOTE: win32 bcrypt + wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (w32->bcrypt_rng_handle && init_status == 0) + w32->bcrypt_init_success = true; + else + DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + #else + DN_Posix_Init(DN_Cast(DN_POSIXCore *)os->platform_context); + #endif + } + + // NOTE: Initialise tmem arenas which allocate memory and will be + // recorded to the now initialised allocation table. The initialisation + // of tmem memory may request tmem memory itself in leak tracing mode. + // This is supported as the tmem arenas defer allocation tracking until + // initialisation is done. + DN_OSTLSInitArgs tls_init_args = {}; + if (args) { + tls_init_args.commit = args->os_tls_commit; + tls_init_args.reserve = args->os_tls_reserve; + tls_init_args.err_sink_reserve = args->os_tls_err_sink_reserve; + tls_init_args.err_sink_commit = args->os_tls_err_sink_commit; + } + + DN_OS_TLSInit(&os->tls, tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&os->tls); + os->cpu_report = DN_CPUGetReport(); + + #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_Assert(g_dn_); + #endif // defined(DN_OS_H) && defined(DN_OS_CPP) } -DN_API void DN_DBGDumpLeaks() + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args) { - uint64_t leak_count = 0; - uint64_t leaked_bytes = 0; - for (DN_USize index = 1; index < g_dn_core->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = g_dn_core->alloc_table.slots + index; - DN_DebugAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_DebugAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_DebugAllocFlag_LeakPermitted); - if (alloc_leaked && !leak_permitted) { - leaked_bytes += alloc->size; - leak_count++; - DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); - DN_LOG_WarningF("Pointer (0x%p) leaked %.*s at:\n" - "%.*s", - alloc->ptr, DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace)); - } - } + g_dn_ = dn; - if (leak_count) { - DN_Str8x32 leak_size = DN_ByteCountStr8x32(leaked_bytes); - DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); - } -} -#endif // DN_LEAK_TRACKING - -// NOTE: DN_Profiler -DN_API DN_Profiler DN_Profiler_Init(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSC tsc, DN_U64 tsc_frequency) -{ - DN_Profiler result = {}; - result.anchors = anchors; - result.anchors_count = count; - result.anchors_per_frame = anchors_per_frame; - result.tsc = tsc; - result.tsc_frequency = tsc_frequency; - return result; -} - -DN_API DN_USize DN_Profiler_FrameCount(DN_Profiler const *profiler) -{ - DN_USize result = profiler->anchors_count / profiler->anchors_per_frame; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) -{ - DN_ProfilerAnchorArray result = {}; - DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; - result.data = profiler->anchors + anchor_offset; - result.count = profiler->anchors_per_frame; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchors(DN_Profiler *profiler) -{ - DN_ProfilerAnchorArray result = DN_Profiler_FrameAnchorsFromIndex(profiler, profiler->frame_index); - return result; -} - -DN_API DN_ProfilerZone DN_Profiler_BeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) -{ - DN_ProfilerZone result = {}; - if (profiler->paused) - return result; - - DN_Assert(anchor_index < profiler->anchors_per_frame); - DN_ProfilerAnchor *anchor = DN_Profiler_FrameAnchors(profiler).data + anchor_index; - anchor->name = name; - - // TODO: We need per-thread-local-storage profiler so that we can use these apis - // across threads. For now, we let them overwrite each other but this is not tenable. - #if 0 - if (anchor->name.size && anchor->name != name) - DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); + #if defined(DN_OS_H) && defined(DN_OS_CPP) + DN_InitOS_(&dn->os, args); #endif - if (profiler->tsc == DN_ProfilerTSC_RDTSC) - result.begin_tsc = DN_CPUGetTSC(); - else - result.begin_tsc = DN_OS_PerfCounterNow(); - result.anchor_index = anchor_index; - result.parent_zone = profiler->parent_zone; - result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; - profiler->parent_zone = anchor_index; - return result; -} - -DN_API void DN_Profiler_EndZone(DN_Profiler *profiler, DN_ProfilerZone zone) -{ - if (profiler->paused) - return; - - DN_Assert(zone.anchor_index < profiler->anchors_per_frame); - DN_Assert(zone.parent_zone < profiler->anchors_per_frame); - - DN_ProfilerAnchorArray array = DN_Profiler_FrameAnchors(profiler); - DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; - DN_U64 tsc_now = profiler->tsc == DN_ProfilerTSC_RDTSC ? DN_CPUGetTSC() : DN_OS_PerfCounterNow(); - DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; - - anchor->hit_count++; - anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; - anchor->tsc_exclusive += elapsed_tsc; - - if (zone.parent_zone != zone.anchor_index) { - DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; - parent_anchor->tsc_exclusive -= elapsed_tsc; - } - profiler->parent_zone = zone.parent_zone; -} - -DN_API void DN_Profiler_NewFrame(DN_Profiler *profiler) -{ - if (profiler->paused) - return; - - // NOTE: End the frame's zone - DN_Profiler_EndZone(profiler, profiler->frame_zone); - DN_ProfilerAnchorArray old_frame_anchors = DN_Profiler_FrameAnchors(profiler); - DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; - profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; - - // NOTE: Bump to the next frame - DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; - profiler->frame_index = (profiler->frame_index + 1) % frame_count; - - // NOTE: Zero out the anchors - DN_ProfilerAnchorArray next_anchors = DN_Profiler_FrameAnchors(profiler); - DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); - - // NOTE: Start the frame's zone - profiler->frame_zone = DN_Profiler_BeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); -} - -DN_API void DN_Profiler_Dump(DN_Profiler *profiler) -{ - if (profiler->frame_index == 0) - return; - - DN_USize frame_index = profiler->frame_index - 1; - DN_Assert(profiler->frame_index < profiler->anchors_per_frame); - - DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); - for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { - DN_ProfilerAnchor const *anchor = anchors + index; - if (!anchor->hit_count) - continue; - - DN_U64 tsc_exclusive = anchor->tsc_exclusive; - DN_U64 tsc_inclusive = anchor->tsc_inclusive; - DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; - if (tsc_exclusive == tsc_inclusive) { - DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); - } else { - DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; - DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", - DN_Str8PrintFmt(anchor->name), - anchor->hit_count, - tsc_exclusive_milliseconds, - tsc_inclusive_milliseconds); - } - } -} - -DN_API DN_F64 DN_Profiler_SecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; - return result; -} - -DN_API DN_F64 DN_Profiler_MsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; - return result; -} -// DN: Single header generator inlined this file => #include "Core/dn_core_demo.cpp" -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ -// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| -// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ -// $$ | $$ |$$ | $$ |$$ | \____$$\ -// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | -// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | -// \_______/ \______/ \______/ \______/ -// -// dn_docs.cpp -- Library documentation via real code examples -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Use this file for documentation and examples of the various APIs in this -// library. Normally docs are written as inline comments in header files, -// however, these quickly go out of date as APIs change. Instead, I provide -// some example code that compiles here that serves to also document the API. -// -// The library header files then become a very minimal reference of exactly the -// function prototypes and definitions instead of massive reams of inline -// comments that visually space out the functions and hinders discoverability -// and/or conciseness of being able to learn the breadth of the APIs. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - -DN_MSVC_WARNING_PUSH -DN_MSVC_WARNING_DISABLE(4702) // unreachable code - -void DN_Docs_Demo() -{ -// NOTE: Before using anything in the library, DN_Core_Init() must be -// called, for example: -#if 0 - DN_Core core = {}; - DN_Core_Init(&core, DN_CoreOnInit_Nil); -#endif - - // NOTE: DN_AtomicSetValue64 - // NOTE: DN_AtomicSetValue32 - // Atomically set the value into the target using an atomic compare and swap - // idiom. The return value of the function is the value that was last stored - // in the target. - { - uint64_t target = 8; - uint64_t value_to_set = 0xCAFE; - if (DN_AtomicSetValue64(&target, value_to_set) == 8) { - // Atomic swap was successful, e.g. the last value that this thread - // observed was '8' which is the value we initialised with e.g. no - // other thread has modified the value. - } - } - - // NOTE: DN_HexFromBytes - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - unsigned char bytes[2] = {0xFA, 0xCE}; - DN_Str8 hex = DN_HexFromBytesPtrArena(bytes, sizeof(bytes), tmem.arena); - DN_Assert(DN_Str8Eq(hex, DN_Str8Lit("face"))); // NOTE: Guaranteed to be null-terminated - } - - // NOTE: DN_BytesFromHex - { - unsigned char bytes[2]; - DN_USize bytes_written = DN_BytesFromHexStr8(DN_Str8Lit("0xFACE"), bytes, sizeof(bytes)); - DN_Assert(bytes_written == 2); - DN_Assert(bytes[0] == 0xFA); - DN_Assert(bytes[1] == 0xCE); - } - - // NOTE: DN_Check - // - // Check the expression trapping in debug, whilst in release- trapping is - // removed and the expression is evaluated as if it were a normal 'if' branch. - // - // This allows handling of the condition gracefully when compiled out but - // traps to notify the developer in builds when it's compiled in. - { - bool flag = true; - if (DN_CheckF(flag, "Flag was false!")) { - /// This branch will execute! - } - } - - // NOTE: DN_CPUID - // Execute the 'CPUID' instruction which lets you query the capabilities of - // the current CPU. - - // NOTE: DN_DEFER - // - // A macro that expands to a C++ lambda that executes arbitrary code on - // scope exit. - { - int x = 0; - DN_DEFER - { - x = 3; - }; - x = 1; - // On scope exit, DN_DEFER object executes and assigns x = 3 - } - - // NOTE: DN_DSMap - // - // A hash table configured using the presets recommended by Demitri Spanos - // from the Handmade Network (HMN), - // - // - power of two capacity - // - grow by 2x on load >= 75% - // - open-addressing with linear probing - // - separate large values (esp. variable length values) into a separate table - // - use a well-known hash function: MurmurHash3 (or xxhash, city, spooky ...) - // - chain-repair on delete (rehash items in the probe chain after delete) - // - shrink by 1/2 on load < 25% (suggested by Martins Mmozeiko of HMN) - // - // Source: discord.com/channels/239737791225790464/600063880533770251/941835678424129597 - // - // This hash-table stores slots (values) separate from the hash mapping. - // Hashes are mapped to slots using the hash-to-slot array which is an array - // of slot indexes. This array intentionally only stores indexes to maximise - // usage of the cache line. Linear probing on collision will only cost a - // couple of cycles to fetch from L1 cache the next slot index to attempt. - // - // The slots array stores values contiguously, non-sorted allowing iteration - // of the map. On element erase, the last element is swapped into the - // deleted element causing the non-sorted property of this table. - // - // The 0th slot (DN_DS_MAP_SENTINEL_SLOT) in the slots array is reserved - // for a sentinel value, e.g. all zeros value. After map initialisation the - // 'occupied' value of the array will be set to 1 to exclude the sentinel - // from the capacity of the table. Skip the first value if you are iterating - // the hash table! - // - // This hash-table accept either a U64 or a buffer (ptr + len) as the key. - // In practice this covers a majority of use cases (with string, buffer and - // number keys). It also allows us to minimise our C++ templates to only - // require 1 variable which is the Value part of the hash-table simplifying - // interface complexity and cruft brought by C++. - // - // Keys are value-copied into the hash-table. If the key uses a pointer to a - // buffer, this buffer must be valid throughout the lifetime of the hash - // table! - { - // NOTE: DN_DSMap_Init - // NOTE: DN_DSMap_Deinit - // - // Initialise a hash table where the table size *must* be a - // power-of-two, otherwise an assert will be triggered. If - // initialisation fails (e.g. memory allocation failure) the table is - // returned zero-initialised where a call to 'IsValid' will return - // false. - // - // The map takes ownership of the arena. This means in practice that if the - // map needs to resize (e.g. because the load threshold of the table is - // exceeded), the arena associated with it will be released and the memory - // will be reallocated with the larger capacity and reassigned to the arena. - // - // In simple terms, when the map resizes it invalidates all memory that was - // previously allocated with the given arena! - // - // A 'Deinit' of the map will similarly deallocate the passed in arena (as - // the map takes ownership of the arena). - DN_Arena arena = DN_ArenaFromVMem(0, 0, DN_ArenaFlags_Nil); - DN_DSMap map = DN_DSMap_Init(&arena, /*size*/ 1024, DN_DSMapFlags_Nil); // Size must be PoT! - DN_Assert(DN_DSMap_IsValid(&map)); // Valid if no initialisation failure (e.g. mem alloc failure) - - // NOTE: DN_DSMap_KeyCStringLit - // NOTE: DN_DSMap_KeyU64 - // NOTE: DN_DSMap_KeyU64NoHash - // NOTE: DN_DSMap_KeyBuffer - // NOTE: DN_DSMap_KeyStr8 - // NOTE: DN_DSMap_KeyStr8Copy - // Create a hash-table key where: - // - // KeyCStringLit: Uses a Hash(cstring literal) - // KeyU64: Uses a Hash(U64) - // KeyU64NoHash: Uses a U64 (where it's truncated to 4 bytes) - // KeyBuffer: Uses a Hash(ptr+len) slice of bytes - // KeyStr8: Uses a Hash(string) - // KeyStr8Copy: Uses a Hash(string) that is copied first using the arena - // - // Buffer-based keys memory must persist throughout lifetime of the map. - // Keys are valued copied into the map, alternatively, copy the - // key/buffer before constructing the key. - // - // You *can't* use the map's arena to allocate keys because on resize it - // will deallocate then reallocate the entire arena. - // - // KeyU64NoHash may be useful if you have a source of data that is - // already sufficiently uniformly distributed already (e.g. using 8 - // bytes taken from a SHA256 hash as the key) and the first 4 bytes - // will be used verbatim. - DN_DSMapKey key = DN_DSMap_KeyStr8(&map, DN_Str8Lit("Sample Key")); - - // NOTE: DN_DSMap_Find - // NOTE: DN_DSMap_Make - // NOTE: DN_DSMap_Set - // - // Query or commit key-value pair to the table, where: - // - // Find: does a key-lookup on the table and returns the hash table slot's value - // Make: assigns the key to the table and returns the hash table slot's value - // Set: assigns the key-value to the table and returns the hash table slot's value - // - // A find query will set 'found' to false if it does not exist. - // - // For 'Make' and 'Set', 'found' can be set to 'true' if the item already - // existed in the map prior to the call. If it's the first time the - // key-value pair is being inserted 'found' will be set to 'false'. - // - // If by adding the key-value pair to the table puts the table over 75% load, - // the table will be grown to 2x the current the size before insertion - // completes. - { - DN_DSMapResult set_result = DN_DSMap_Set(&map, key, 0xCAFE); - DN_Assert(!set_result.found); // First time we are setting the key-value pair, it wasn't previously in the table - DN_Assert(map.occupied == 2); // Sentinel + new element == 2 - } - - // Iterating elements in the array, note that index '0' is the sentinel - // slot! You typically don't care about it! - for (DN_USize index = 1; index < map.occupied; index++) { - DN_DSMapSlot *it = map.slots + index; - DN_DSMapKey it_key = it->key; - int *it_value = &it->value; - DN_Assert(*it_value == 0xCAFE); - - DN_Assert(DN_Str8Eq(DN_Str8FromPtr(it_key.buffer_data, it_key.buffer_size), DN_Str8Lit("Sample Key"))); - } - - // NOTE: DN_DSMap_Erase - // - // Remove the key-value pair from the table. If by erasing the key-value - // pair from the table puts the table under 25% load, the table will be - // shrunk by 1/2 the current size after erasing. The table will not shrink - // below the initial size that the table was initialised as. - { - bool erased = DN_DSMap_Erase(&map, key); - DN_Assert(erased); - DN_Assert(map.occupied == 1); // Sentinel element - } - - DN_DSMap_Deinit(&map, DN_ZMem_Yes); // Deallocates the 'arena' for us! - } - -// NOTE: DN_DSMap_Hash -// -// Hash the input key using the custom hash function if it's set on the map, -// otherwise uses the default hashing function (32bit Murmur3). - -// NOTE: DN_DSMap_HashToSlotIndex -// -// Calculate the index into the map's 'slots' array from the given hash. - -// NOTE: DN_DSMap_Resize -// -// Resize the table and move all elements to the new map, note that the new -// size must be a power of two. This function wil fail on memory allocation -// failure, or the requested size is smaller than the current number of -// elements in the map to resize. - -// NOTE: DN_OSErrSink -// -// Error sinks are a way of accumulating errors from API calls related or -// unrelated into 1 unified error handling pattern. The implemenation of a -// sink requires 2 fundamental design constraints on the APIs supporting -// this pattern. -// -// 1. Pipelining of errors -// Errors emitted over the course of several API calls are accumulated -// into a sink which save the error code and message of the first error -// encountered and can be checked later. -// -// 2. Error proof APIs -// Functions that produce errors must return objects/handles that are -// marked to trigger no-ops used in subsequent functions dependent on it. -// -// Consider the following example demonstrating a conventional error -// handling approach (error values by return/sentinel values) and error -// handling using error-proof and pipelining. - -// (A) Conventional error checking patterns using return/sentinel values -#if 0 - DN_OSFile *file = DN_OS_FileOpen("/path/to/file", ...); - if (file) { - if (!DN_OS_FileWrite(file, "abc")) { - // Error handling! - } - Dnq_OS_FileClose(file); - } else { - // Error handling! - } -#endif - - // (B) Error handling using pipelining and and error proof APIs. APIs that - // produce errors take in the error sink as a parameter. - if (0) { - DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); - DN_OSFile file = DN_OS_FileOpen(DN_Str8Lit("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); - DN_OS_FileWrite(&file, DN_Str8Lit("abc"), error); - DN_OS_FileClose(&file); - if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { - // Do error handling! - } - } - - // Pipeling and error-proof APIs lets you write sequence of instructions and - // defer error checking until it is convenient or necessary. Functions are - // *guaranteed* to return an object that is usable. There are no hidden - // exceptions to be thrown. Functions may opt to still return error values - // by way of return values thereby *not* precluding the ability to check - // every API call either. - // - // Ultimately, this error handling approach gives more flexibility on the - // manner in how errors are handled with less code. - // - // Error sinks can nest begin and end statements. This will open a new scope - // whereby the current captured error pushed onto a stack and the sink will - // be populated by the first error encountered in that scope. - - if (0) { - DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); - DN_OSFile file = DN_OS_FileOpen(DN_Str8Lit("/path/to/file"), DN_OSFileOpen_OpenIfExist, DN_OSFileAccess_ReadWrite, error); - DN_OS_FileWrite(&file, DN_Str8Lit("abc"), error); - DN_OS_FileClose(&file); - - { - // NOTE: My error sinks are thread-local, so the returned 'error' is - // the same as the 'error' value above. - DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); - DN_OS_FileWriteAll(DN_Str8Lit("/path/to/another/file"), DN_Str8Lit("123"), error); - DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to another file"); - } - - if (DN_OS_ErrSinkEndAndLogErrorF(error, "Failed to write to file")) { - // Do error handling! - } - } - - // NOTE: DN_JSONBuilder_Build - // - // Convert the internal JSON buffer in the builder into a string. - - // NOTE: DN_JSONBuilder_KeyValue, DN_JSONBuilder_KeyValueF - // - // Add a JSON key value pair untyped. The value is emitted directly without - // checking the contents of value. - // - // All other functions internally call into this function which is the main - // workhorse of the builder. - - // NOTE: DN_JSON_Builder_ObjectEnd - // - // End a JSON object in the builder, generates internally a '}' string - - // NOTE: DN_JSON_Builder_ArrayEnd - // - // End a JSON array in the builder, generates internally a ']' string - - // NOTE: DN_JSONBuilder_LiteralNamed - // - // Add a named JSON key-value object whose value is directly written to - // the following '"": ' (e.g. useful for emitting the 'null' - // value) - - // NOTE: DN_JSONBuilder_U64 - // NOTE: DN_JSONBuilder_U64Named - // NOTE: DN_JSONBuilder_I64 - // NOTE: DN_JSONBuilder_I64Named - // NOTE: DN_JSONBuilder_F64 - // NOTE: DN_JSONBuilder_F64Named - // NOTE: DN_JSONBuilder_Bool - // NOTE: DN_JSONBuilder_BoolNamed - // - // Add the named JSON data type as a key-value object. The named variants - // generates internally the key-value pair, e.g. - // - // "": - // - // And the non-named version emit just the 'value' portion - - // NOTE: DN_List_Iterate - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_List list = DN_List_Init(/*chunk_size*/ 128); - for (DN_ListIterator it = {}; DN_List_Iterate(&list, &it, 0);) { - int *item = it.data; - (void)item; - } - } - - // NOTE: DN_LOGProc - // - // Function prototype of the logging interface exposed by this library. Logs - // emitted using the DN_LOG_* family of functions are routed through this - // routine. - - // NOTE: DN_FNV1A -#if 0 - { - // Using the default hash as defined by DN_FNV1A32_SEED and - // DN_FNV1A64_SEED for 32/64bit hashes respectively - uint32_t buffer1 = 0xCAFE0000; - uint32_t buffer2 = 0xDEAD0000; - { - uint64_t hash = DN_FNV1A64_Hash(&buffer1, sizeof(buffer1)); - hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); // Chained hashing - (void)hash; - } - - // You can use a custom seed by skipping the 'Hash' call and instead - // calling 'Iterate' immediately. - { - uint64_t custom_seed = 0xABCDEF12; - uint64_t hash = DN_FNV1A64_Iterate(&buffer1, sizeof(buffer1), custom_seed); - hash = DN_FNV1A64_Iterate(&buffer2, sizeof(buffer2), hash); - (void)hash; - } - } -#endif - - // NOTE: DN_MurmurHash3 - // MurmurHash3 was written by Austin Appleby, and is placed in the public - // domain. The author (Austin Appleby) hereby disclaims copyright to this source - // code. - // - // Note - The x86 and x64 versions do _not_ produce the same results, as the - // algorithms are optimized for their respective platforms. You can still - // compile and run any of them on any platform, but your performance with the - // non-native version will be less than optimal. - - // NOTE: DN_OS_DateUnixTime - // - // Produce the time elapsed since the unix epoch - { - uint64_t now = DN_OS_DateUnixTimeS(); - (void)now; - } - - // NOTE: DN_OS_DirIterate - // - // Iterate the files within the passed in folder - for (DN_OSDirIterator it = {}; DN_OS_PathIterateDir(DN_Str8Lit("."), &it);) { - // printf("%.*s\n", DN_Str8PrintFmt(it.file_name)); - } - - // NOTE: DN_OS_FileDelete - // - // This function can only delete files and it can *only* delete directories - // if it is empty otherwise this function fails. - - // NOTE: DN_OS_WriteAllSafe - // Writes the file at the path first by appending '.tmp' to the 'path' to - // write to. If the temporary file is written successfully then the file is - // copied into 'path', for example: - // - // path: C:/Home/my.txt - // tmp_path: C:/Home/my.txt.tmp - // - // If 'tmp_path' is written to successfuly, the file will be copied over into - // 'path'. - if (0) { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_OSErrSink *error = DN_OS_ErrSinkBegin(DN_OSErrSinkMode_Nil); - DN_OS_FileWriteAllSafe(/*path*/ DN_Str8Lit("C:/Home/my.txt"), /*buffer*/ DN_Str8Lit("Hello world"), error); - DN_OS_ErrSinkEndAndLogErrorF(error, ""); - } - - // NOTE: DN_OS_EstimateTSCPerSecond - // - // Estimate how many timestamp count's (TSC) there are per second. TSC - // is evaluated by calling __rdtsc() or the equivalent on the platform. This - // value can be used to convert TSC durations into seconds. - // - // The 'duration_ms_to_gauge_tsc_frequency' parameter specifies how many - // milliseconds to spend measuring the TSC rate of the current machine. - // 100ms is sufficient to produce a fairly accurate result with minimal - // blocking in applications if calculated on startup.. - // - // This may return 0 if querying the CPU timestamp counter is not supported - // on the platform (e.g. __rdtsc() or __builtin_readcyclecounter() returns 0). - - // NOTE: DN_OS_EXEDir - // - // Retrieve the executable directory without the trailing '/' or ('\' for - // windows). If this fails an empty string is returned. - - // NOTE: DN_OS_PerfCounterFrequency - // - // Get the number of ticks in the performance counter per second for the - // operating system you're running on. This value can be used to calculate - // duration from OS performance counter ticks. - - // NOTE: DN_OS_Path* - // Construct paths ensuring the native OS path separators are used in the - // string. In 99% of cases you can use 'PathConvertF' which converts the - // given path in one shot ensuring native path separators in the string. - // - // path: C:\Home/My/Folder - // converted: C:/Home/My/Folder (On Unix) - // C:\Home\My\Folder (On Windows) - // - // If you need to construct a path dynamically you can use the builder-esque - // interface to build a path's step-by-step using the 'OSPath' data structure. - // With this API you can append paths piece-meal to build the path after all - // pieces are appended. - // - // You may append a singular or nested path to the builder. In the builder, - // the string is scanned and separated into path separated chunks and stored - // in the builder, e.g. these are all valid to pass into 'PathAdd', - // 'PathAddRef' ... e.t.c - // - // "path/to/your/desired/folder" is valid - // "path" is valid - // "path/to\your/desired\folder" is valid - // - // 'PathPop' removes the last appended path from the current path stored in - // the 'OSPath': - // - // path: path/to/your/desired/folder - // popped_path: path/to/your/desired - - // NOTE: DN_OS_SecureRNGBytes - // - // Generate cryptographically secure bytes - -#if 0 - // NOTE: DN_PCG32 - // - // Random number generator of the PCG family. Implementation taken from - // Martins Mmozeiko from Handmade Network. - // https://gist.github.com/mmozeiko/1561361cd4105749f80bb0b9223e9db8 - { - DN_PCG32 rng = DN_PCG32_Init(0xb917'a66c'1d9b'3bd8); - - // NOTE: DN_PCG32_Range - // - // Generate a value in the [low, high) interval - uint32_t u32_value = DN_PCG32_Range(&rng, 32, 64); - DN_Assert(u32_value >= 32 && u32_value < 64); - - // NOTE: DN_PCG32_NextF32 - // NOTE: DN_PCG32_NextF64 - // - // Generate a float/double in the [0, 1) interval - DN_F64 f64_value = DN_PCG32_NextF64(&rng); - DN_Assert(f64_value >= 0.f && f64_value < 1.f); - - // NOTE: DN_PCG32_Advance - // - // Step the random number generator by 'delta' steps - DN_PCG32_Advance(&rng, /*delta*/ 5); - } -#endif - -#if 0 -#if !defined(DN_NO_PROFILER) - // NOTE: DN_Profiler - // - // A profiler based off Casey Muratori's Computer Enhance course, Performance - // Aware Programming. This profiler measures function elapsed time using the - // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count - // that can be converted into a duration. - // - // This profiler uses a double buffer scheme for storing profiling markers. - // After an application's typical update/frame cycle you can swap the - // profiler's buffer whereby the front buffer contains the previous frames - // profiling metrics and the back buffer will be populated with the new - // frame's profiling metrics. - { - enum Zone - { - Zone_MainLoop, - Zone_Count - }; - - DN_ProfilerZone profiler_zone_main_update = DN_Profiler_BeginZone(Zone_MainLoop); - - // NOTE: DN_Profiler_AnchorBuffer - // - // Retrieve the requested buffer from the profiler for - // writing/reading profiling metrics. Pass in the enum to specify - // which buffer to grab from the profiler. - // - // The front buffer contains the previous frame's profiling metrics - // and the back buffer is where the profiler is currently writing - // to. - // - // For end user intents and purposes, you likely only need to read - // the front buffer which contain the metrics that you can visualise - // regarding the most profiling metrics recorded. - - // NOTE: DN_Profiler_ReadBuffer - // - // Retrieve the buffer of anchors of which there are - // `DN_PROFILER_ANCHOR_BUFFER_SIZE` anchors from the most recent run - // of the profiler after you have called `SwapAnchorBuffer` to trigger - // the double buffer - DN_ProfilerAnchor *read_anchors = DN_Profiler_ReadBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = read_anchors + index; - if (anchor->name.size) { - // ... - } - } - - // NOTE: DN_Profiler_WriteBuffer - // - // Same as `ReadBuffer` however we return the buffer that the profiler - // is currently writing anchors into. - DN_ProfilerAnchor *write_anchors = DN_Profiler_WriteBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = write_anchors + index; - if (anchor->name.size) { - // ... - } - } - - DN_Profiler_EndZone(profiler_zone_main_update); - DN_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! - DN_Memset(g_dn_core->profiler, 0, sizeof(*g_dn_core->profiler)); - } -#endif // !defined(DN_NO_PROFILER) -#endif - - // NOTE: DN_Raycast_LineIntersectV2 - // Calculate the intersection point of 2 rays returning a `t` value - // which is how much along the direction of the 'ray' did the intersection - // occur. - // - // The arguments passed in do not need to be normalised for the function to - // work. - - // NOTE: DN_Safe_* - // - // Performs the arithmetic operation and uses DN_Check on the operation to - // check if it overflows. If it overflows the MAX value of the integer is - // returned in add and multiply operations, and, the minimum is returned in - // subtraction and division. - - // NOTE: DN_SaturateCast* - // - // Truncate the passed in value to the return type clamping the resulting - // value to the max value of the desired data type. It DN_Check's the - // truncation. - // - // The following sentinel values are returned when saturated, - // USize -> Int: INT_MAX - // USize -> I8: INT8_MAX - // USize -> I16: INT16_MAX - // USize -> I32: INT32_MAX - // USize -> I64: INT64_MAX - // - // U64 -> UInt: UINT_MAX - // U64 -> U8: UINT8_MAX - // U64 -> U16: UINT16_MAX - // U64 -> U32: UINT32_MAX - // - // USize -> U8: UINT8_MAX - // USize -> U16: UINT16_MAX - // USize -> U32: UINT32_MAX - // USize -> U64: UINT64_MAX - // - // ISize -> Int: INT_MIN or INT_MAX - // ISize -> I8: INT8_MIN or INT8_MAX - // ISize -> I16: INT16_MIN or INT16_MAX - // ISize -> I32: INT32_MIN or INT32_MAX - // ISize -> I64: INT64_MIN or INT64_MAX - // - // ISize -> UInt: 0 or UINT_MAX - // ISize -> U8: 0 or UINT8_MAX - // ISize -> U16: 0 or UINT16_MAX - // ISize -> U32: 0 or UINT32_MAX - // ISize -> U64: 0 or UINT64_MAX - // - // I64 -> ISize: DN_ISIZE_MIN or DN_ISIZE_MAX - // I64 -> I8: INT8_MIN or INT8_MAX - // I64 -> I16: INT16_MIN or INT16_MAX - // I64 -> I32: INT32_MIN or INT32_MAX - // - // Int -> I8: INT8_MIN or INT8_MAX - // Int -> I16: INT16_MIN or INT16_MAX - // Int -> U8: 0 or UINT8_MAX - // Int -> U16: 0 or UINT16_MAX - // Int -> U32: 0 or UINT32_MAX - // Int -> U64: 0 or UINT64_MAX - - // NOTE: DN_StackTrace - // Emit stack traces at the calling site that these functions are invoked - // from. - // - // For some applications, it may be viable to generate raw stack traces and - // store just the base addresses of the call stack from the 'Walk' - // functions. This reduces the memory overhead and required to hold onto - // stack traces and resolve the addresses on-demand when required. - // - // However if your application is loading and/or unloading shared libraries, - // on Windows it may be impossible for the application to resolve raw base - // addresses if they become invalid over time. In these applications you - // must convert the raw stack traces before the unloading occurs, and when - // loading new shared libraries, 'ReloadSymbols' must be called to ensure - // the debug APIs are aware of how to resolve the new addresses imported - // into the address space. - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - - // NOTE: DN_StackTrace_Walk - // - // Generate a stack trace as a series of addresses to the base of the - // functions on the call-stack at the current instruction pointer. The - // addresses are stored in order from the current executing function - // first to the most ancestor function last in the walk. - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, /*depth limit*/ 128); - - // Loop over the addresses produced in the stack trace - for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk);) { - // NOTE: DN_StackTrace_RawFrameToFrame - // - // Converts the base address into a human readable stack trace - // entry (e.g. address, line number, file and function name). - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(tmem.arena, it.raw_frame); - - // You may then print out the frame like so - if (0) - printf("%.*s(%" PRIu64 "): %.*s\n", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); - } - - // If you load new shared-libraries into the address space it maybe - // necessary to call into 'ReloadSymbols' to ensure that the OS is able - // to resolve the new addresses. - DN_StackTrace_ReloadSymbols(); - - // NOTE: DN_StackTrace_GetFrames - // - // Helper function to create a stack trace and automatically convert the - // raw frames into human readable frames. This function effectively - // calls 'Walk' followed by 'RawFrameToFrame'. - DN_Slice frames = DN_StackTrace_GetFrames(tmem.arena, /*depth limit*/ 128); - (void)frames; - } - - // NOTE: DN_Str8FromArena - // - // Allocates a string with the requested 'size'. An additional byte is - // always requested from the allocator to null-terminate the buffer. This - // allows the string to be used with C-style string APIs. - // - // The returned string's 'size' member variable does *not* include this - // additional null-terminating byte. - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Str8 string = DN_Str8FromArena(tmem.arena, /*size*/ 1, DN_ZMem_Yes); - DN_Assert(string.size == 1); - DN_Assert(string.data[string.size] == 0); // It is null-terminated! - } - - // NOTE: DN_Str8BSplit - // - // Splits a string into 2 substrings occuring prior and after the first - // occurence of the delimiter. Neither strings include the matched - // delimiter. If no delimiter is found, the 'rhs' of the split will be - // empty. - { - DN_Str8BSplitResult dot_split = DN_Str8BSplit(/*string*/ DN_Str8Lit("abc.def.ghi"), /*delimiter*/ DN_Str8Lit(".")); - DN_Str8BSplitResult slash_split = DN_Str8BSplit(/*string*/ DN_Str8Lit("abc.def.ghi"), /*delimiter*/ DN_Str8Lit("/")); - DN_Assert(DN_Str8Eq(dot_split.lhs, DN_Str8Lit("abc")) && DN_Str8Eq(dot_split.rhs, DN_Str8Lit("def.ghi"))); - DN_Assert(DN_Str8Eq(slash_split.lhs, DN_Str8Lit("abc.def.ghi")) && DN_Str8Eq(slash_split.rhs, DN_Str8Lit(""))); - - // Loop that walks the string and produces ("abc", "def", "ghi") - for (DN_Str8 it = DN_Str8Lit("abc.def.ghi"); it.size;) { - DN_Str8BSplitResult split = DN_Str8BSplit(it, DN_Str8Lit(".")); - DN_Str8 chunk = split.lhs; // "abc", "def", ... - it = split.rhs; - (void)chunk; - } - } - - // NOTE: DN_Str8FileNameFromPath - // - // Takes a slice to the file name from a file path. The file name is - // evaluated by searching from the end of the string backwards to the first - // occurring path separator '/' or '\'. If no path separator is found, the - // original string is returned. This function preserves the file extension - // if there were any. - { - { - DN_Str8 string = DN_Str8FileNameFromPath(DN_Str8Lit("C:/Folder/item.txt")); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("item.txt"))); - } - { - // TODO(doyle): Intuitively this seems incorrect. Empty string instead? - DN_Str8 string = DN_Str8FileNameFromPath(DN_Str8Lit("C:/Folder/")); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("C:/Folder"))); - } - { - DN_Str8 string = DN_Str8FileNameFromPath(DN_Str8Lit("C:/Folder")); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("Folder"))); - } - } - - // NOTE: DN_Str8FilePathNoExtension - // - // This function preserves the original string if no extension was found. - // An extension is defined as the substring after the last '.' encountered - // in the string. - { - DN_Str8 string = DN_Str8FilePathNoExtension(DN_Str8Lit("C:/Folder/item.txt.bak")); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("C:/Folder/item.txt"))); - } - - // NOTE: DN_Str8FileNameNoExtension - // - // This function is the same as calling 'FileNameFromPath' followed by - // 'FilePathNoExtension' - { - DN_Str8 string = DN_Str8FileNameNoExtension(DN_Str8Lit("C:/Folder/item.txt.bak")); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("item.txt"))); - } - - // NOTE: DN_Str8Replace - // NOTE: DN_Str8ReplaceInsensitive - // - // Replace any matching substring 'find' with 'replace' in the passed in - // 'string'. The 'start_index' may be specified to offset which index the - // string will start doing replacements from. - // - // String replacements are not done inline and the returned string will - // always be a newly allocated copy, irrespective of if any replacements - // were done or not. - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Str8 string = DN_Str8Replace(/*string*/ DN_Str8Lit("Foo Foo Bar"), - /*find*/ DN_Str8Lit("Foo"), - /*replace*/ DN_Str8Lit("Moo"), - /*start_index*/ 1, - /*arena*/ tmem.arena, - /*eq_case*/ DN_Str8EqCase_Sensitive); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("Foo Moo Bar"))); - } - - // NOTE: DN_Str8Segment - // - // Add a delimiting 'segment_char' every 'segment_size' number of characters - // in the string. - // - // Reverse segment delimits the string counting 'segment_size' from the back - // of the string. - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Str8 string = DN_Str8Segment(tmem.arena, /*string*/ DN_Str8Lit("123456789"), /*segment_size*/ 3, /*segment_char*/ ','); - DN_Assert(DN_Str8Eq(string, DN_Str8Lit("123,456,789"))); - } - - // NOTE: DN_Str8Split - { - // Splits the string at each delimiter into substrings occuring prior and - // after until the next delimiter. - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - { - DN_Str8SplitResult splits = DN_Str8SplitArena(/*arena*/ tmem.arena, - /*string*/ DN_Str8Lit("192.168.8.1"), - /*delimiter*/ DN_Str8Lit("."), - /*mode*/ DN_Str8SplitIncludeEmptyStrings_No); - DN_Assert(splits.count == 4); - DN_Assert(DN_Str8Eq(splits.data[0], DN_Str8Lit("192")) && - DN_Str8Eq(splits.data[1], DN_Str8Lit("168")) && - DN_Str8Eq(splits.data[2], DN_Str8Lit("8")) && - DN_Str8Eq(splits.data[3], DN_Str8Lit("1"))); - } - - // You can include empty strings that occur when splitting by setting - // the split mode to include empty strings. - { - DN_Str8SplitResult splits = DN_Str8SplitArena(/*arena*/ tmem.arena, - /*string*/ DN_Str8Lit("a--b"), - /*delimiter*/ DN_Str8Lit("-"), - /*mode*/ DN_Str8SplitIncludeEmptyStrings_Yes); - DN_Assert(splits.count == 3); - DN_Assert(DN_Str8Eq(splits.data[0], DN_Str8Lit("a")) && - DN_Str8Eq(splits.data[1], DN_Str8Lit("")) && - DN_Str8Eq(splits.data[2], DN_Str8Lit("b"))); - } - } - - // NOTE: DN_I64FromStr8, DN_U64FromStr8 - // - // Convert a number represented as a string to a signed 64 bit number. - // - // The 'separator' is an optional digit separator for example, if - // 'separator' is set to ',' then '1,234' will successfully be parsed to - // '1234'. If no separator is desired, you may pass in '0' in which - // '1,234' will *not* be succesfully parsed. - // - // Real numbers are truncated. Both '-' and '+' prefixed strings are permitted, - // i.e. "+1234" -> 1234 and "-1234" -> -1234. Strings must consist entirely of - // digits, the seperator or the permitted prefixes as previously mentioned - // otherwise this function will return false, i.e. "1234 dog" will cause the - // function to return false, however, the output is greedily converted and - // will be evaluated to "1234". - // - // 'ToU64' only '+' prefix is permitted - // 'ToI64' either '+' or '-' prefix is permitted - { - { - DN_I64FromResult result = DN_I64FromStr8(DN_Str8Lit("-1,234"), /*separator*/ ','); - DN_Assert(result.success && result.value == -1234); - } - { - DN_I64FromResult result = DN_I64FromStr8(DN_Str8Lit("-1,234"), /*separator*/ 0); - DN_Assert(!result.success && result.value == 1); // 1 because it's a greedy conversion - } - } - - // NOTE: DN_Str8TrimByteOrderMark - // - // Removes a leading UTF8, UTF16 BE/LE, UTF32 BE/LE byte order mark from the - // string if it's present. - - // NOTE: DN_Str8PrintFmt - // - // Unpacks a string struct that has the fields {.data, .size} for printing a - // pointer and length style string using the printf format specifier "%.*s" - // - // printf("%.*s\n", DN_Str8PrintFmt(DN_Str8Lit("Hello world"))); - - // NOTE: DN_Str8BuilderAppendF - // NOTE: DN_Str8BuilderAppendFV - // NOTE: DN_Str8BuilderAppendRef - // NOTE: DN_Str8BuilderAppendCopy - // - // - Appends a string to the string builder as follows - // - // AppendRef: Stores the string slice by value - // AppendCopy: Stores the string slice by copy (with builder's arena) - // AppendF/V: Constructs a format string and calls 'AppendRef' - - // NOTE: DN_Str8BuilderBuild - // NOTE: DN_Str8BuilderBuildCRT - // - // Constructs the final string by merging all the appended strings into - // one merged string. - // - // The CRT variant calls into 'malloc' and the string *must* be released - // using 'free'. - - // NOTE: DN_Str8BuilderBuildSlice - // - // Constructs the final string into an array of strings (e.g. a slice) - - // NOTE: DN_TicketMutex - // - // A mutex implemented using an atomic compare and swap on tickets handed - // out for each critical section. - // - // This mutex serves ticket in order and will block all other threads until - // the tickets are returned in order. The thread with the oldest ticket that - // has not been returned has right of way to execute, all other threads will - // be blocked in an atomic compare and swap loop. block execution by going - // into an atomic - // - // When a thread is blocked by this mutex, a spinlock intrinsic '_mm_pause' is - // used to yield the CPU and reduce spinlock on the thread. This mutex is not - // ideal for long blocking operations. This mutex does not issue any syscalls - // and relies entirely on atomic instructions. - { - DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); // Simple procedural mutual exclusion lock - DN_TicketMutex_End(&mutex); - - // NOTE: DN_TicketMutex_MakeTicket - // - // Request the next available ticket for locking from the mutex. - DN_UInt ticket = DN_TicketMutex_MakeTicket(&mutex); - - if (DN_TicketMutex_CanLock(&mutex, ticket)) { - // NOTE: DN_TicketMutex_BeginTicket - // - // Locks the mutex using the given ticket if possible. If it's not - // the next ticket to be locked the executing thread will block - // until the mutex can lock the ticket, i.e. All prior tickets are - // returned, in sequence, to the mutex. - DN_TicketMutex_BeginTicket(&mutex, ticket); - DN_TicketMutex_End(&mutex); - } - } - - // NOTE: DN_ThreadContext - // - // Each thread is assigned in their thread-local storage (TLS) tmem and - // permanent arena allocators. These can be used for allocations with a - // lifetime scoped to the lexical scope or for storing data permanently - // using the arena paradigm. - // - // TLS in this implementation is implemented using the `thread_local` C/C++ - // keyword. - // - // 99% of the time you will want DN_OS_TLSTMem(...) which returns you a - // temporary arena for function lifetime allocations. On scope exit, the - // arena is cleared out. - // - // This library's paradigm revolves heavily around arenas including tmem - // arenas into child functions for temporary calculations. If an arena is - // passed into a function, this poses a problem sometimes known as - // 'arena aliasing'. - // - // If an arena aliases another arena (e.g. the arena passed in) is the same - // as the tmem arena requested in the function, we risk the tmem arena - // on scope exit deallocating memory belonging to the caller. - // - // To avoid this we the 'DN_OS_TLSTMem(...)' API takes in a list of arenas - // to ensure that we provide a tmem arena that *won't* alias with the - // caller's arena. If arena aliasing occurs, with ASAN on, generally - // the library will trap and report use-after-poison once violated. - { - DN_OSTLSTMem tmem_a = DN_OS_TLSTMem(nullptr); - - // Now imagine we call a function where we pass tmem_a.arena down - // into it .. If we call tmem again, we need to pass in the arena - // to prevent aliasing. - DN_OSTLSTMem tmem_b = DN_OS_TLSTMem(tmem_a.arena); - DN_Assert(tmem_a.arena != tmem_b.arena); - } - - // @proc DN_Thread_GetTMem - // @desc Retrieve the per-thread temporary arena allocator that is reset on scope - // exit. - - // The tmem arena must be deconflicted with any existing arenas in the - // function to avoid trampling over each other's memory. Consider the situation - // where the tmem arena is passed into the function. Inside the function, if - // the same arena is reused then, if both arenas allocate, when the inner arena - // is reset, this will undo the passed in arena's allocations in the function. - - // @param[in] conflict_arena A pointer to the arena currently being used in the - // function - - // NOTE: DN_Str8x32FromFmt - { - DN_Str8x32 string = DN_Str8x32FromFmt("%d", 123123); - if (0) // Prints "123123" - printf("%.*s", DN_Str8PrintFmt(string)); - } - - // NOTE: DN_CVT_AgeFromU64 - { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Str8x128 string = DN_AgeStr8FromSecF64(DN_SecFromHours(2) + DN_SecFromMins(30), DN_AgeUnit_All); - DN_Assert(DN_Str8Eq(DN_Str8FromStruct(&string), DN_Str8Lit("2h 30m"))); - } - - // NOTE: DN_VArray - // - // An array that is backed by virtual memory by reserving addressing space - // and comitting pages as items are allocated in the array. This array never - // reallocs, instead you should reserve the upper bound of the memory you - // will possibly ever need (e.g. 16GB) and let the array commit physical - // pages on demand. - // - // On 64 bit operating systems you are given 48 bits of addressable space - // giving you 256 TB of reservable memory. This gives you practically - // an unlimited array capacity that avoids reallocs and only consumes memory - // that is actually occupied by the array. - // - // Each page that is committed into the array will be at page/allocation - // granularity which are always cache aligned. This array essentially retains - // all the benefits of normal arrays, - // - // - contiguous memory - // - O(1) random access - // - O(N) iterate - // - // In addition to no realloc on expansion or shrinking. - // - { - // NOTE: DN_VArray_Init - // NOTE: DN_VArray_InitByteSize - // - // Initialise an array with the requested byte size or item capacity - // respectively. The returned array may have a higher capacity than the - // requested amount since requested memory from the OS may have a certain - // alignment requirement (e.g. on Windows reserve/commit are 64k/4k - // aligned). - DN_VArray array = DN_VArray_Init(1024); - DN_Assert(array.size == 0 && array.max >= 1024); - - // NOTE: DN_VArray_Make - // NOTE: DN_VArray_Add - // NOTE: DN_VArray_MakeArray - // NOTE: DN_VArray_AddArray - // - // Allocate items from the array where: - // - // Make: creates a zero-init item from the array - // Add: creates a zero-init item and memcpy passed in data into the item - // - // If the array has run out of capacity or was never initialised, a null - // pointer is returned. - int *item = DN_VArray_Add(&array, 0xCAFE); - DN_Assert(*item == 0xCAFE && array.size == 1); - - // NOTE: DN_VArray_AddCArray - DN_VArray_AddCArray(&array, {1, 2, 3}); - DN_Assert(array.size == 4); - -// TODO(doyle): There's a bug here with the negative erase! -// Loop over the array items and erase 1 item. -#if 0 - for (DN_USize index = 0; index < array.size; index++) { - if (index != 1) - continue; - - // NOTE: DN_VArray_EraseRange - // - // Erase the next 'count' items at 'begin_index' in the array. - // 'count' can be positive or negative which dictates the if we - // erase forward from the 'begin_index' or in reverse. - // - // This operation will invalidate all pointers to the array! - // - // A stable erase will shift all elements after the erase ranged - // into the range preserving the order of prior elements. Unstable - // erase will move the tail elements into the range being erased. - // - // Erase range returns a result that contains the next iterator - // index that can be used to update the your for loop index if you - // are trying to iterate over the array. - - // TODO(doyle): There's a bug here! This doesn't work. - // Erase index 0 with the negative count! - DN_ArrayEraseResult erase_result = DN_VArray_EraseRange(&array, - /*begin_index*/ index, - /*count*/ -1, - /*erase*/ DN_ArrayErase_Stable); - DN_Assert(erase_result.items_erased == 1); - - // Use the index returned to continue linearly iterating the array - index = erase_result.it_index; - DN_Assert(array.data[index + 1] == 2); // Next loop iteration will process item '2' - } - - DN_Assert(array.size == 3 && - array.data[0] == 1 && - array.data[1] == 2 && - array.data[2] == 3); -#endif - - // NOTE: DN_VArray_Reserve - // - // Ensure that the requested number of items are backed by physical pages - // from the OS. Calling this pre-emptively will minimise syscalls into the - // kernel to request memory. The requested items will be rounded up to the - // in bytes to the allocation granularity of OS allocation APIs hence the - // reserved space may be greater than the requested amount (e.g. this is 4k - // on Windows). - DN_VArray_Reserve(&array, /*count*/ 8); - - DN_VArray_Deinit(&array); - } - - // NOTE: DN_W32_LastError - // NOTE: DN_W32_ErrorCodeToMsg - #if defined(DN_PLATFORM_WIN32) - if (0) { - // Generate the error string for the last Win32 API called that return - // an error value. - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_W32Error get_last_error = DN_W32_LastError(tmem.arena); - printf("Error (%lu): %.*s", get_last_error.code, DN_Str8PrintFmt(get_last_error.msg)); - - // Alternatively, pass in the error code directly - DN_W32Error error_msg_for_code = DN_W32_ErrorCodeToMsg(tmem.arena, /*error_code*/ 0); - printf("Error (%lu): %.*s", error_msg_for_code.code, DN_Str8PrintFmt(error_msg_for_code.msg)); - } - - // NOTE: DN_W32_MakeProcessDPIAware - // - // Call once at application start-up to ensure that the application is DPI - // aware on Windows and ensure that application UI is scaled up - // appropriately for the monitor. - - // NOTE: DN_W32_Str8ToStr16 - // NOTE: DN_W32_Str8ToStr16Buffer - // NOTE: DN_W32_Str16ToStr8 - // NOTE: DN_W32_Str16ToStr8Buffer - // - // Convert a UTF8 <-> UTF16 string. - // - // The exact size buffer required for this function can be determined by - // calling this function with the 'dest' set to null and 'dest_size' set to - // 0, the return size is the size required for conversion not-including - // space for the null-terminator. This function *always* null-terminates the - // input buffer. - // - // Returns the number of u8's (for UTF16->8) OR u16's (for UTF8->16) - // written/required for conversion. 0 if there was a conversion error and can be - // queried using 'DN_W32_LastError' + // NOTE Initialise fields + #if defined(DN_LEAK_TRACKING) + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + core->alloc_table_arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); + core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); #endif + + // NOTE: Print out init features + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); + if (flags & DN_InitFlags_LogLibFeatures) { + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("DN initialised:\n")); + #if defined(DN_OS_CPP) + DN_F32 page_size_kib = dn->os.page_size / 1024.0f; + DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f; + DN_Str8BuilderAppendF(&builder, + " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n" + " Logical Processor Count: %u\n", + page_size_kib, + alloc_granularity_kib, + dn->os.logical_processor_count); + #endif + + #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (DN_ASAN_POISON) { + DN_Str8BuilderAppendF( + &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + DN_Str8BuilderAppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); + } + #endif + + #if defined(DN_LEAK_TRACKING) + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" Allocation leak tracing\n")); + #endif + + #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) + DN_POSIXCore *posix = DN_Cast(DN_POSIXCore *)g_dn_->os.platform_context; + DN_Str8BuilderAppendF(&builder, " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); + #endif + // TODO(doyle): Add stacktrace feature log + } + + if (flags & DN_InitFlags_LogCPUFeatures) { + DN_CPUReport const *report = &dn->os.cpu_report; + DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8BuilderAppendF' Actual type: 'struct DN_Str8'. + DN_Str8BuilderAppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); + DN_MSVC_WARNING_POP + + DN_USize longest_feature_name = 0; + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); + } + + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + bool has_feature = DN_CPUHasFeature(report, feature_decl.value); + DN_Str8BuilderAppendF(&builder, + " %.*s:%*s%s\n", + DN_Str8PrintFmt(feature_decl.label), + DN_Cast(int)(longest_feature_name - feature_decl.label.size), + "", + has_feature ? "available" : "not available"); + } + } + + DN_Str8 info_log = DN_Str8BuilderBuild(&builder, tmem.arena); + if (info_log.size) + DN_LOG_DebugF("%.*s", DN_Str8PrintFmt(info_log)); + } -DN_MSVC_WARNING_POP +DN_API void DN_BeginFrame() +{ + DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0); +} #define DN_MATH_CPP // DN: Single header generator commented out this header => #include "dn_math.h" diff --git a/Single-Header/dn_single_header.h b/Single-Header/dn_single_header.h index 1d5ae6a..e75e905 100644 --- a/Single-Header/dn_single_header.h +++ b/Single-Header/dn_single_header.h @@ -1,4 +1,4 @@ -// Generated by the DN single header generator 2025-11-08 18:54:58 +// Generated by the DN single header generator 2025-11-09 13:19:26 #if !defined(DN_BASE_INC_H) #define DN_BASE_INC_H @@ -983,6 +983,50 @@ struct DN_FmtAppendResult bool truncated; }; +struct DN_ProfilerAnchor +{ + // Inclusive refers to the time spent to complete the function call + // including all children functions. + // + // Exclusive refers to the time spent in the function, not including any + // time spent in children functions that we call that are also being + // profiled. If we recursively call into ourselves, the time we spent in + // our function is accumulated. + DN_U64 tsc_inclusive; + DN_U64 tsc_exclusive; + DN_U16 hit_count; + DN_Str8 name; +}; + +struct DN_ProfilerZone +{ + DN_U16 anchor_index; + DN_U64 begin_tsc; + DN_U16 parent_zone; + DN_U64 elapsed_tsc_at_zone_start; +}; + +struct DN_ProfilerAnchorArray +{ + DN_ProfilerAnchor *data; + DN_USize count; +}; + +typedef DN_U64 (DN_ProfilerTSCNowFunc)(); +struct DN_Profiler +{ + DN_USize frame_index; + DN_ProfilerAnchor *anchors; + DN_USize anchors_count; + DN_USize anchors_per_frame; + DN_U16 parent_zone; + bool paused; + DN_ProfilerTSCNowFunc *tsc_now; + DN_U64 tsc_frequency; + DN_ProfilerZone frame_zone; + DN_F64 frame_avg_tsc; +}; + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC @@ -2924,6 +2968,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. DN_GCC_WARNING_POP DN_MSVC_WARNING_POP +DN_API void DN_BeginFrame (); + #define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) #define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) #define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) @@ -3215,6 +3261,24 @@ DN_API DN_ByteCountResult DN_ByteCountFromType (DN_U64 bytes, DN_By DN_API DN_Str8x32 DN_ByteCountStr8x32FromType (DN_U64 bytes, DN_ByteCountType type); #define DN_ByteCountStr8x32(bytes) DN_ByteCountStr8x32FromType(bytes, DN_ByteCountType_Auto) +#define DN_ProfilerZoneLoop(prof, name, index) \ + DN_ProfilerZone DN_UniqueName(zone_) = DN_ProfilerBeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ + DN_UniqueName(dummy_).begin_tsc == 0; \ + DN_ProfilerEndZone(prof, DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 + +#define DN_ProfilerZoneLoopAuto(prof, name) DN_ProfilerZoneLoop(prof, name, __COUNTER__ + 1) +DN_API DN_Profiler DN_ProfilerInit (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency); +DN_API DN_ProfilerZone DN_ProfilerBeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); +#define DN_ProfilerBeginZoneAuto(prof, name) DN_ProfilerBeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) +DN_API void DN_ProfilerEndZone (DN_Profiler *profiler, DN_ProfilerZone zone); +DN_API DN_USize DN_ProfilerFrameCount (DN_Profiler const *profiler); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors (DN_Profiler *profiler); +DN_API void DN_ProfilerNewFrame (DN_Profiler *profiler); +DN_API void DN_ProfilerDump (DN_Profiler *profiler); +DN_API DN_F64 DN_ProfilerSecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); +DN_API DN_F64 DN_ProfilerMsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); + #endif // !defined(DN_BASE_H) // DN: Single header generator inlined this file => #include "Base/dn_base_os.h" #if !defined(DN_BASE_OS_H) @@ -3253,26 +3317,26 @@ struct DN_StackTraceWalkResultIterator #if defined(DN_FREESTANDING) -#define DN_StackTrace_WalkStr8FromHeap(...) DN_Str8Lit("N/A") -#define DN_StackTrace_Walk(...) -#define DN_StackTrace_WalkResultIterate(...) -#define DN_StackTrace_WalkResultToStr8(...) DN_Str8Lit("N/A") -#define DN_StackTrace_WalkStr8(...) DN_Str8Lit("N/A") -#define DN_StackTrace_WalkStr8FromHeap(...) DN_Str8Lit("N/A") -#define DN_StackTrace_GetFrames(...) -#define DN_StackTrace_RawFrameToFrame(...) -#define DN_StackTrace_Print(...) -#define DN_StackTrace_ReloadSymbols(...) +#define DN_StackTraceWalkStr8FromHeap(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalk(...) +#define DN_StackTraceWalkResultIterate(...) +#define DN_StackTraceWalkResultToStr8(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalkStr8(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalkStr8FromHeap(...) DN_Str8Lit("N/A") +#define DN_StackTraceGetFrames(...) +#define DN_StackTraceRawFrameToFrame(...) +#define DN_StackTracePrint(...) +#define DN_StackTraceReloadSymbols(...) #else -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk (struct DN_Arena *arena, DN_U16 limit); -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); -DN_API DN_Str8 DN_StackTrace_WalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); -DN_API DN_Str8 DN_StackTrace_WalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); -DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); -DN_API DN_Slice DN_StackTrace_GetFrames (struct DN_Arena *arena, DN_U16 limit); -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); -DN_API void DN_StackTrace_Print (DN_U16 limit); -DN_API void DN_StackTrace_ReloadSymbols (); +DN_API DN_StackTraceWalkResult DN_StackTraceWalk (struct DN_Arena *arena, DN_U16 limit); +DN_API bool DN_StackTraceWalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); +DN_API DN_Str8 DN_StackTraceWalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); +DN_API DN_Str8 DN_StackTraceWalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_StackTraceWalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_Slice DN_StackTraceGetFrames (struct DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); +DN_API void DN_StackTracePrint (DN_U16 limit); +DN_API void DN_StackTraceReloadSymbols (); #endif #endif // !defined(DN_BASE_OS_H) // DN: Single header generator inlined this file => #include "Base/dn_base_assert.h" @@ -3282,7 +3346,7 @@ DN_API void DN_StackTrace_ReloadSymbols (); #define DN_HardAssertF(expr, fmt, ...) \ do { \ if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -3300,7 +3364,7 @@ DN_API void DN_StackTrace_ReloadSymbols (); #define DN_AssertF(expr, fmt, ...) \ do { \ if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -3313,7 +3377,7 @@ DN_API void DN_StackTrace_ReloadSymbols (); static bool once = true; \ if (!(expr) && once) { \ once = false; \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -3339,7 +3403,7 @@ DN_API void DN_StackTrace_ReloadSymbols (); ((expr) ? true : (DN_LOG_WarningF(fmt, ##__VA_ARGS__), false)) #else #define DN_CheckF(expr, fmt, ...) \ - ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTrace_Print(128 /*limit*/), DN_DebugBreak, false)) + ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTracePrint(128 /*limit*/), DN_DebugBreak, false)) #endif #endif @@ -3913,6 +3977,51 @@ template void DN_List_Ad template DN_Slice DN_List_ToSliceCopy (DN_List const *list, DN_Arena* arena); #endif // !defined(DN_NO_LIST) #endif // !defined(DN_CONTAINER_H) +// DN: Single header generator inlined this file => #include "Base/dn_base_leak.h" +// DN: Single header generator commented out this header => #include "../dn_base_inc.h" + +enum DN_LeakAllocFlag +{ + DN_LeakAllocFlag_Freed = 1 << 0, + DN_LeakAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_LeakAlloc +{ + void *ptr; // 8 Pointer to the allocation being tracked + DN_USize size; // 16 Size of the allocation + DN_USize freed_size; // 24 Store the size of the allocation when it is freed + DN_Str8 stack_trace; // 40 Stack trace at the point of allocation + DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` +}; + +// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get +// 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_LeakTracker +{ + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_Arena alloc_table_arena; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; +}; + +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); +#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) +#endif + #endif // !defined(DN_BASE_INC_H) #if !defined(DN_OS_INC_H) @@ -5816,14 +5925,6 @@ struct DN_OSHttpResponse #endif }; -struct DN_OSInitArgs -{ - DN_U64 tls_reserve; - DN_U64 tls_commit; - DN_U64 tls_err_sink_reserve; - DN_U64 tls_err_sink_commit; -}; - struct DN_OSCore { DN_CPUReport cpu_report; @@ -5862,7 +5963,6 @@ struct DN_OSDiskSpace DN_U64 size; }; -DN_API void DN_OS_Init (DN_OSCore *os, DN_OSInitArgs *args); DN_API void DN_OS_EmitLogsWithOSPrintFunctions (DN_OSCore *os); DN_API void DN_OS_DumpThreadContextArenaStat (DN_Str8 file_path); @@ -6205,146 +6305,42 @@ DN_API DN_Slice DN_Str8BuilderBuildSliceFromTLS (DN_Str8Bui #endif // !defined(DN_OS_STRING_H) #endif // DN_OS_INC_H -#if !defined(DN_CORE_INC_H) -#define DN_CORE_INC_H +#if !defined(DN_INC_H) +#define DN_INC_H -// DN: Single header generator inlined this file => #include "Core/dn_core_debug.h" -#if !defined(DN_CORE_DEBUG_H) -#define DN_CORE_DEBUG_H - -// DN: Single header generator commented out this header => #include "../dn_base_inc.h" - -// NOTE: DN_Debug -enum DN_DebugAllocFlag -{ - DN_DebugAllocFlag_Freed = 1 << 0, - DN_DebugAllocFlag_LeakPermitted = 1 << 1, -}; - -struct DN_DebugAlloc -{ - void *ptr; // 8 Pointer to the allocation being tracked - DN_USize size; // 16 Size of the allocation - DN_USize freed_size; // 24 Store the size of the allocation when it is freed - DN_Str8 stack_trace; // 40 Stack trace at the point of allocation - DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed - DN_U16 flags; // 72 Bit flags from `DN_DebugAllocFlag` -}; - -static_assert(sizeof(DN_DebugAlloc) == 64 || sizeof(DN_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively - "We aim to keep the allocation record as light as possible as " - "memory tracking can get expensive. Enforce that there is no " - "unexpected padding."); - -// NOTE: DN_Profiler -struct DN_ProfilerAnchor -{ - // Inclusive refers to the time spent to complete the function call - // including all children functions. - // - // Exclusive refers to the time spent in the function, not including any - // time spent in children functions that we call that are also being - // profiled. If we recursively call into ourselves, the time we spent in - // our function is accumulated. - DN_U64 tsc_inclusive; - DN_U64 tsc_exclusive; - DN_U16 hit_count; - DN_Str8 name; -}; - -struct DN_ProfilerZone -{ - DN_U16 anchor_index; - DN_U64 begin_tsc; - DN_U16 parent_zone; - DN_U64 elapsed_tsc_at_zone_start; -}; - -struct DN_ProfilerAnchorArray -{ - DN_ProfilerAnchor *data; - DN_USize count; -}; - -enum DN_ProfilerTSC -{ - DN_ProfilerTSC_RDTSC, - DN_ProfilerTSC_OSPerformanceCounter, -}; - -struct DN_Profiler -{ - DN_USize frame_index; - DN_ProfilerAnchor *anchors; - DN_USize anchors_count; - DN_USize anchors_per_frame; - DN_U16 parent_zone; - bool paused; - DN_ProfilerTSC tsc; - DN_U64 tsc_frequency; - DN_ProfilerZone frame_zone; - DN_F64 frame_avg_tsc; -}; - -#define DN_Profiler_ZoneLoop(prof, name, index) \ - DN_ProfilerZone DN_UniqueName(zone_) = DN_Profiler_BeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ - DN_UniqueName(dummy_).begin_tsc == 0; \ - DN_Profiler_EndZone(prof, DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 - -#define DN_Profiler_ZoneLoopAuto(prof, name) DN_Profiler_ZoneLoop(prof, name, __COUNTER__ + 1) - -DN_API DN_Profiler DN_Profiler_Init (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSC tsc, DN_U64 tsc_frequency); -DN_API DN_ProfilerZone DN_Profiler_BeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); -#define DN_Profiler_BeginZoneAuto(prof, name) DN_Profiler_BeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) -DN_API void DN_Profiler_EndZone (DN_Profiler *profiler, DN_ProfilerZone zone); -DN_API DN_USize DN_Profiler_FrameCount (DN_Profiler const *profiler); -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchors (DN_Profiler *profiler); -DN_API void DN_Profiler_NewFrame (DN_Profiler *profiler); -DN_API void DN_Profiler_Dump (DN_Profiler *profiler); -DN_API DN_F64 DN_Profiler_SecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); -DN_API DN_F64 DN_Profiler_MsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); - - -#if defined(DN_LEAK_TRACKING) -DN_API void DN_DBGTrackAlloc (void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_DBGTrackDealloc (void *ptr); -DN_API void DN_DBGDumpLeaks (); -#else -#define DN_DBGTrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_DBGTrackDealloc(ptr) do { (void)ptr; } while (0) -#define DN_DBGDumpLeaks() do { } while (0) -#endif -#endif // DN_CORE_DEBUG_H -// DN: Single header generator inlined this file => #include "Core/dn_core.h" -#if !defined(DN_CORE_H) -#define DN_CORE_H - -// NOTE: DN_Core struct DN_Core { - // NOTE: Leak Tracing - #if defined(DN_LEAK_TRACKING) - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_Arena alloc_table_arena; + DN_USize mem_allocs_frame; + DN_LeakTracker leak; + #if defined(DN_OS_H) + DN_OSCore os; #endif - DN_U64 alloc_table_bytes_allocated_for_stack_traces; }; -enum DN_CoreOnInit +struct DN_InitArgs { - DN_CoreOnInit_Nil = 0, - DN_CoreOnInit_LogLibFeatures = 1 << 0, - DN_CoreOnInit_LogCPUFeatures = 1 << 1, - DN_CoreOnInit_LogAllFeatures = DN_CoreOnInit_LogLibFeatures | DN_CoreOnInit_LogCPUFeatures, + DN_U64 os_tls_reserve; + DN_U64 os_tls_commit; + DN_U64 os_tls_err_sink_reserve; + DN_U64 os_tls_err_sink_commit; }; -DN_API void DN_Core_Init (DN_Core *core, DN_CoreOnInit on_init); -DN_API void DN_Core_BeginFrame(); -#endif // !defined(DN_CORE_H) +typedef DN_USize DN_InitFlags; +enum DN_InitFlags_ +{ + DN_InitFlags_Nil = 0, + DN_InitFlags_OS = 1 << 0, + DN_InitFlags_LogLibFeatures = 1 << 1, + DN_InitFlags_LogCPUFeatures = 1 << 2, + DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, +}; -#endif // !defined(DN_CORE_INC_H) +extern DN_Core *g_dn_; + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args); +DN_API void DN_BeginFrame(); + +#endif // !defined(DN_INC_H) #if !defined(DN_MATH_H) #define DN_MATH_H diff --git a/Source/Base/dn_base.cpp b/Source/Base/dn_base.cpp index f2b6963..d2ce5b9 100644 --- a/Source/Base/dn_base.cpp +++ b/Source/Base/dn_base.cpp @@ -776,7 +776,6 @@ DN_API DN_F32 DN_EpsilonClampF32(DN_F32 value, DN_F32 target, DN_F32 epsilon) return result; } - static DN_ArenaBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak, DN_ArenaMemFuncs mem_funcs) { DN_ArenaBlock *result = nullptr; @@ -823,7 +822,7 @@ static DN_ArenaBlock *DN_ArenaBlockFromMemFuncs_(DN_U64 reserve, DN_U64 commit, } if (track_alloc && result) - DN_DBGTrackAlloc(result, result->reserve, alloc_can_leak); + DN_LeakTrackAlloc(dn->leak, result, result->reserve, alloc_can_leak); return result; } @@ -890,7 +889,7 @@ static void DN_ArenaBlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block) { DN_USize release_size = block->reserve; if (DN_BitIsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack)) - DN_DBGTrackDealloc(block); + DN_LeakTrackDealloc(&g_dn_->leak, block); DN_ASanUnpoisonMemoryRegion(block, block->commit); if (arena->flags & DN_ArenaFlags_MemFuncs) { if (arena->mem_funcs.type == DN_ArenaMemFuncType_Basic) @@ -2901,3 +2900,170 @@ DN_API DN_Str8x32 DN_ByteCountStr8x32FromType(DN_U64 bytes, DN_ByteCountType typ DN_Str8x32 result = DN_Str8x32FromFmt("%.2f%.*s", byte_count.bytes, DN_Str8PrintFmt(byte_count.suffix)); return result; } + +DN_API DN_Profiler DN_ProfilerInit(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency) +{ + DN_Profiler result = {}; + result.anchors = anchors; + result.anchors_count = count; + result.anchors_per_frame = anchors_per_frame; + result.tsc_now = tsc_now; + result.tsc_frequency = tsc_frequency; + + DN_AssertF(result.tsc_frequency != 0, + "You must set this to the frequency of the timestamp counter function (TSC) (e.g. how " + "many ticks occur between timestamps). We use this to determine the duration between " + "each zone's recorded TSC. For example if the 'tsc_now' was set to Window's " + "QueryPerformanceCounter then 'tsc_frequency' would be set to the value of " + "QueryPerformanceFrequency which is typically 10mhz (e.g. The duration between two " + "consecutive TSC's is 10mhz)." + "" + "Hence frequency can't be zero otherwise it's a divide by 0. If you don't have a TSC " + "function and pass in null, the profiler defaults to rdtsc() and you must measure the " + "frequency of rdtsc yourself. The reason for this is that measuring rdtsc requires " + "having some alternate timing mechanism to measure the duration between the TSCs " + "provided by rdtsc and this profiler makes no assumption about what timing primitives " + "are available other than rdtsc which is a CPU builtin available on basically all " + "platforms or have an equivalent (e.g. __builtin_readcyclecounter)" + "" + "This codebase provides DN_OS_EstimateTSCPerSecond() as an example of how to that for " + "convenience and is available if compiling with the OS layer. Some platforms like " + "Emscripten don't support rdtsc() so you should use an alternative method like " + "emscripten_get_now() or clock_gettime with CLOCK_MONOTONIC."); + return result; +} + +DN_API DN_USize DN_ProfilerFrameCount(DN_Profiler const *profiler) +{ + DN_USize result = profiler->anchors_count / profiler->anchors_per_frame; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) +{ + DN_ProfilerAnchorArray result = {}; + DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; + result.data = profiler->anchors + anchor_offset; + result.count = profiler->anchors_per_frame; + return result; +} + +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors(DN_Profiler *profiler) +{ + DN_ProfilerAnchorArray result = DN_ProfilerFrameAnchorsFromIndex(profiler, profiler->frame_index); + return result; +} + +DN_API DN_ProfilerZone DN_ProfilerBeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) +{ + DN_ProfilerZone result = {}; + if (profiler->paused) + return result; + + DN_Assert(anchor_index < profiler->anchors_per_frame); + DN_ProfilerAnchor *anchor = DN_ProfilerFrameAnchors(profiler).data + anchor_index; + anchor->name = name; + + // TODO: We need per-thread-local-storage profiler so that we can use these apis + // across threads. For now, we let them overwrite each other but this is not tenable. + #if 0 + if (anchor->name.size && anchor->name != name) + DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); + #endif + + result.begin_tsc = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + result.anchor_index = anchor_index; + result.parent_zone = profiler->parent_zone; + result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; + profiler->parent_zone = anchor_index; + return result; +} + +DN_API void DN_ProfilerEndZone(DN_Profiler *profiler, DN_ProfilerZone zone) +{ + if (profiler->paused) + return; + + DN_Assert(zone.anchor_index < profiler->anchors_per_frame); + DN_Assert(zone.parent_zone < profiler->anchors_per_frame); + + DN_ProfilerAnchorArray array = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; + DN_U64 tsc_now = profiler->tsc_now ? profiler->tsc_now() : DN_CPUGetTSC(); + DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; + + anchor->hit_count++; + anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; + anchor->tsc_exclusive += elapsed_tsc; + + if (zone.parent_zone != zone.anchor_index) { + DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; + parent_anchor->tsc_exclusive -= elapsed_tsc; + } + profiler->parent_zone = zone.parent_zone; +} + +DN_API void DN_ProfilerNewFrame(DN_Profiler *profiler) +{ + if (profiler->paused) + return; + + // NOTE: End the frame's zone + DN_ProfilerEndZone(profiler, profiler->frame_zone); + DN_ProfilerAnchorArray old_frame_anchors = DN_ProfilerFrameAnchors(profiler); + DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; + profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; + + // NOTE: Bump to the next frame + DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; + profiler->frame_index = (profiler->frame_index + 1) % frame_count; + + // NOTE: Zero out the anchors + DN_ProfilerAnchorArray next_anchors = DN_ProfilerFrameAnchors(profiler); + DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); + + // NOTE: Start the frame's zone + profiler->frame_zone = DN_ProfilerBeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); +} + +DN_API void DN_ProfilerDump(DN_Profiler *profiler) +{ + if (profiler->frame_index == 0) + return; + + DN_USize frame_index = profiler->frame_index - 1; + DN_Assert(profiler->frame_index < profiler->anchors_per_frame); + + DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); + for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { + DN_ProfilerAnchor const *anchor = anchors + index; + if (!anchor->hit_count) + continue; + + DN_U64 tsc_exclusive = anchor->tsc_exclusive; + DN_U64 tsc_inclusive = anchor->tsc_inclusive; + DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; + if (tsc_exclusive == tsc_inclusive) { + DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); + } else { + DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; + DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", + DN_Str8PrintFmt(anchor->name), + anchor->hit_count, + tsc_exclusive_milliseconds, + tsc_inclusive_milliseconds); + } + } +} + +DN_API DN_F64 DN_ProfilerSecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; + return result; +} + +DN_API DN_F64 DN_ProfilerMsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) +{ + DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; + return result; +} diff --git a/Source/Base/dn_base.h b/Source/Base/dn_base.h index 8315c54..0c23a37 100644 --- a/Source/Base/dn_base.h +++ b/Source/Base/dn_base.h @@ -765,6 +765,50 @@ struct DN_FmtAppendResult bool truncated; }; +struct DN_ProfilerAnchor +{ + // Inclusive refers to the time spent to complete the function call + // including all children functions. + // + // Exclusive refers to the time spent in the function, not including any + // time spent in children functions that we call that are also being + // profiled. If we recursively call into ourselves, the time we spent in + // our function is accumulated. + DN_U64 tsc_inclusive; + DN_U64 tsc_exclusive; + DN_U16 hit_count; + DN_Str8 name; +}; + +struct DN_ProfilerZone +{ + DN_U16 anchor_index; + DN_U64 begin_tsc; + DN_U16 parent_zone; + DN_U64 elapsed_tsc_at_zone_start; +}; + +struct DN_ProfilerAnchorArray +{ + DN_ProfilerAnchor *data; + DN_USize count; +}; + +typedef DN_U64 (DN_ProfilerTSCNowFunc)(); +struct DN_Profiler +{ + DN_USize frame_index; + DN_ProfilerAnchor *anchors; + DN_USize anchors_count; + DN_USize anchors_per_frame; + DN_U16 parent_zone; + bool paused; + DN_ProfilerTSCNowFunc *tsc_now; + DN_U64 tsc_frequency; + DN_ProfilerZone frame_zone; + DN_F64 frame_avg_tsc; +}; + #if !defined(DN_STB_SPRINTF_HEADER_ONLY) #define STB_SPRINTF_IMPLEMENTATION #define STB_SPRINTF_STATIC @@ -778,6 +822,8 @@ DN_GCC_WARNING_DISABLE(-Wunused-function) DN_GCC_WARNING_POP DN_MSVC_WARNING_POP +DN_API void DN_BeginFrame (); + #define DN_SPrintF(...) STB_SPRINTF_DECORATE(sprintf)(__VA_ARGS__) #define DN_SNPrintF(...) STB_SPRINTF_DECORATE(snprintf)(__VA_ARGS__) #define DN_VSPrintF(...) STB_SPRINTF_DECORATE(vsprintf)(__VA_ARGS__) @@ -1069,4 +1115,22 @@ DN_API DN_ByteCountResult DN_ByteCountFromType (DN_U64 bytes, DN_By DN_API DN_Str8x32 DN_ByteCountStr8x32FromType (DN_U64 bytes, DN_ByteCountType type); #define DN_ByteCountStr8x32(bytes) DN_ByteCountStr8x32FromType(bytes, DN_ByteCountType_Auto) +#define DN_ProfilerZoneLoop(prof, name, index) \ + DN_ProfilerZone DN_UniqueName(zone_) = DN_ProfilerBeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ + DN_UniqueName(dummy_).begin_tsc == 0; \ + DN_ProfilerEndZone(prof, DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 + +#define DN_ProfilerZoneLoopAuto(prof, name) DN_ProfilerZoneLoop(prof, name, __COUNTER__ + 1) +DN_API DN_Profiler DN_ProfilerInit (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSCNowFunc *tsc_now, DN_U64 tsc_frequency); +DN_API DN_ProfilerZone DN_ProfilerBeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); +#define DN_ProfilerBeginZoneAuto(prof, name) DN_ProfilerBeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) +DN_API void DN_ProfilerEndZone (DN_Profiler *profiler, DN_ProfilerZone zone); +DN_API DN_USize DN_ProfilerFrameCount (DN_Profiler const *profiler); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); +DN_API DN_ProfilerAnchorArray DN_ProfilerFrameAnchors (DN_Profiler *profiler); +DN_API void DN_ProfilerNewFrame (DN_Profiler *profiler); +DN_API void DN_ProfilerDump (DN_Profiler *profiler); +DN_API DN_F64 DN_ProfilerSecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); +DN_API DN_F64 DN_ProfilerMsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); + #endif // !defined(DN_BASE_H) diff --git a/Source/Base/dn_base_assert.h b/Source/Base/dn_base_assert.h index 9b2eaa9..6005df0 100644 --- a/Source/Base/dn_base_assert.h +++ b/Source/Base/dn_base_assert.h @@ -4,7 +4,7 @@ #define DN_HardAssertF(expr, fmt, ...) \ do { \ if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Hard assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -22,7 +22,7 @@ #define DN_AssertF(expr, fmt, ...) \ do { \ if (!(expr)) { \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -35,7 +35,7 @@ static bool once = true; \ if (!(expr) && once) { \ once = false; \ - DN_Str8 stack_trace_ = DN_StackTrace_WalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ + DN_Str8 stack_trace_ = DN_StackTraceWalkStr8FromHeap(128 /*limit*/, 2 /*skip*/); \ DN_LOG_ErrorF("Assertion [" #expr "], stack trace was:\n\n%.*s\n\n" fmt, \ DN_Str8PrintFmt(stack_trace_), \ ##__VA_ARGS__); \ @@ -61,7 +61,7 @@ ((expr) ? true : (DN_LOG_WarningF(fmt, ##__VA_ARGS__), false)) #else #define DN_CheckF(expr, fmt, ...) \ - ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTrace_Print(128 /*limit*/), DN_DebugBreak, false)) + ((expr) ? true : (DN_LOG_ErrorF(fmt, ##__VA_ARGS__), DN_StackTracePrint(128 /*limit*/), DN_DebugBreak, false)) #endif #endif diff --git a/Source/Base/dn_base_leak.cpp b/Source/Base/dn_base_leak.cpp new file mode 100644 index 0000000..324144b --- /dev/null +++ b/Source/Base/dn_base_leak.cpp @@ -0,0 +1,142 @@ +#define DN_BASE_LEAK_CPP + +#include "../dn_base_inc.h" + +DN_API void DN_LeakTrackAlloc_(DN_LeakTracker *leak, void *ptr, DN_USize size, bool leak_permitted) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&leak->alloc_table_mutex); + }; + + // NOTE: If the entry was not added, we are reusing a pointer that has been freed. + // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it + // already existed. + DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_Cast(DN_U64) ptr); + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc_entry.found) { + if ((alloc->flags & DN_LeakAllocFlag_Freed) == 0) { + DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); + DN_Str8x32 new_alloc_size = DN_ByteCountStr8x32(size); + DN_HardAssertF( + alloc->flags & DN_LeakAllocFlag_Freed, + "This pointer is already in the leak tracker, however it has not been freed yet. This " + "same pointer is being ask to be tracked twice in the allocation table, e.g. one if its " + "previous free calls has not being marked freed with an equivalent call to " + "DN_LeakTrackDealloc()\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is allocating %.*s again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(new_alloc_size), + DN_Str8PrintFmt(stack_trace)); + } + + // NOTE: Pointer was reused, clean up the prior entry + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; + leak->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; + + DN_OS_MemDealloc(alloc->stack_trace.data); + DN_OS_MemDealloc(alloc->freed_stack_trace.data); + *alloc = {}; + } + + alloc->ptr = ptr; + alloc->size = size; + alloc->stack_trace = stack_trace; + alloc->flags |= leak_permitted ? DN_LeakAllocFlag_LeakPermitted : 0; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; +} + +DN_API void DN_LeakTrackDealloc_(DN_LeakTracker *leak, void *ptr) +{ + if (!ptr) + return; + + DN_TicketMutex_Begin(&leak->alloc_table_mutex); + DN_DEFER + { + DN_TicketMutex_End(&leak->alloc_table_mutex); + }; + + DN_Str8 stack_trace = DN_StackTraceWalkStr8FromHeap(128, 3 /*skip*/); + DN_DSMap *alloc_table = &leak->alloc_table; + DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); + DN_HardAssertF(alloc_entry.found, + "Allocated pointer can not be removed as it does not exist in the " + "allocation table. When this memory was allocated, the pointer was " + "not added to the allocation table [ptr=%p]", + ptr); + + DN_LeakAlloc *alloc = alloc_entry.value; + if (alloc->flags & DN_LeakAllocFlag_Freed) { + DN_Str8x32 freed_size = DN_ByteCountStr8x32(alloc->freed_size); + DN_HardAssertF((alloc->flags & DN_LeakAllocFlag_Freed) == 0, + "Double free detected, pointer to free was already marked " + "as freed. Either the pointer was reallocated but not " + "traced, or, the pointer was freed twice.\n" + "\n" + "The pointer (0x%p) originally allocated %.*s at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer was freed at:\n" + "\n" + "%.*s\n" + "\n" + "The pointer is being freed again at:\n" + "\n" + "%.*s\n", + ptr, + DN_Str8PrintFmt(freed_size), + DN_Str8PrintFmt(alloc->stack_trace), + DN_Str8PrintFmt(alloc->freed_stack_trace), + DN_Str8PrintFmt(stack_trace)); + } + + DN_Assert(alloc->freed_stack_trace.size == 0); + alloc->flags |= DN_LeakAllocFlag_Freed; + alloc->freed_stack_trace = stack_trace; + leak->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; +} + +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); + if (alloc_leaked && !leak_permitted) { + leaked_bytes += alloc->size; + leak_count++; + DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); + DN_LOG_WarningF( + "Pointer (0x%p) leaked %.*s at:\n" + "%.*s", + alloc->ptr, + DN_Str8PrintFmt(alloc_size), + DN_Str8PrintFmt(alloc->stack_trace)); + } + } + + if (leak_count) { + DN_Str8x32 leak_size = DN_ByteCountStr8x32(leaked_bytes); + DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); + } +} diff --git a/Source/Base/dn_base_leak.h b/Source/Base/dn_base_leak.h new file mode 100644 index 0000000..a97f75c --- /dev/null +++ b/Source/Base/dn_base_leak.h @@ -0,0 +1,44 @@ +#include "../dn_base_inc.h" + +enum DN_LeakAllocFlag +{ + DN_LeakAllocFlag_Freed = 1 << 0, + DN_LeakAllocFlag_LeakPermitted = 1 << 1, +}; + +struct DN_LeakAlloc +{ + void *ptr; // 8 Pointer to the allocation being tracked + DN_USize size; // 16 Size of the allocation + DN_USize freed_size; // 24 Store the size of the allocation when it is freed + DN_Str8 stack_trace; // 40 Stack trace at the point of allocation + DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed + DN_U16 flags; // 72 Bit flags from `DN_LeakAllocFlag` +}; + +// NOTE: We aim to keep the allocation record as light as possible as memory tracking can get +// 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_LeakTracker +{ + DN_DSMap alloc_table; + DN_TicketMutex alloc_table_mutex; + DN_Arena alloc_table_arena; + DN_U64 alloc_table_bytes_allocated_for_stack_traces; +}; + +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); +#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) +#endif + diff --git a/Source/Base/dn_base_mem.cpp b/Source/Base/dn_base_mem.cpp deleted file mode 100644 index 247ec83..0000000 --- a/Source/Base/dn_base_mem.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#define DN_BASE_MEM_CPP - -#include "../dn_base_inc.h" - diff --git a/Source/Base/dn_base_mem.h b/Source/Base/dn_base_mem.h deleted file mode 100644 index 571e107..0000000 --- a/Source/Base/dn_base_mem.h +++ /dev/null @@ -1,6 +0,0 @@ -#if !defined(DN_BASE_MEM_H) -#define DN_BASE_MEM_H - -#include "../dn_base_inc.h" - -#endif // !defined(DN_BASE_MEM_H) diff --git a/Source/Base/dn_base_os.h b/Source/Base/dn_base_os.h index a9ee0a6..4263b12 100644 --- a/Source/Base/dn_base_os.h +++ b/Source/Base/dn_base_os.h @@ -34,25 +34,25 @@ struct DN_StackTraceWalkResultIterator #if defined(DN_FREESTANDING) -#define DN_StackTrace_WalkStr8FromHeap(...) DN_Str8Lit("N/A") -#define DN_StackTrace_Walk(...) -#define DN_StackTrace_WalkResultIterate(...) -#define DN_StackTrace_WalkResultToStr8(...) DN_Str8Lit("N/A") -#define DN_StackTrace_WalkStr8(...) DN_Str8Lit("N/A") -#define DN_StackTrace_WalkStr8FromHeap(...) DN_Str8Lit("N/A") -#define DN_StackTrace_GetFrames(...) -#define DN_StackTrace_RawFrameToFrame(...) -#define DN_StackTrace_Print(...) -#define DN_StackTrace_ReloadSymbols(...) +#define DN_StackTraceWalkStr8FromHeap(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalk(...) +#define DN_StackTraceWalkResultIterate(...) +#define DN_StackTraceWalkResultToStr8(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalkStr8(...) DN_Str8Lit("N/A") +#define DN_StackTraceWalkStr8FromHeap(...) DN_Str8Lit("N/A") +#define DN_StackTraceGetFrames(...) +#define DN_StackTraceRawFrameToFrame(...) +#define DN_StackTracePrint(...) +#define DN_StackTraceReloadSymbols(...) #else -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk (struct DN_Arena *arena, DN_U16 limit); -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); -DN_API DN_Str8 DN_StackTrace_WalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); -DN_API DN_Str8 DN_StackTrace_WalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); -DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap (DN_U16 limit, DN_U16 skip); -DN_API DN_Slice DN_StackTrace_GetFrames (struct DN_Arena *arena, DN_U16 limit); -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); -DN_API void DN_StackTrace_Print (DN_U16 limit); -DN_API void DN_StackTrace_ReloadSymbols (); +DN_API DN_StackTraceWalkResult DN_StackTraceWalk (struct DN_Arena *arena, DN_U16 limit); +DN_API bool DN_StackTraceWalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk); +DN_API DN_Str8 DN_StackTraceWalkResultToStr8 (struct DN_Arena *arena, DN_StackTraceWalkResult const *walk, DN_U16 skip); +DN_API DN_Str8 DN_StackTraceWalkStr8 (struct DN_Arena *arena, DN_U16 limit, DN_U16 skip); +DN_API DN_Str8 DN_StackTraceWalkStr8FromHeap (DN_U16 limit, DN_U16 skip); +DN_API DN_Slice DN_StackTraceGetFrames (struct DN_Arena *arena, DN_U16 limit); +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame (struct DN_Arena *arena, DN_StackTraceRawFrame raw_frame); +DN_API void DN_StackTracePrint (DN_U16 limit); +DN_API void DN_StackTraceReloadSymbols (); #endif #endif // !defined(DN_BASE_OS_H) diff --git a/Source/Core/dn_core.cpp b/Source/Core/dn_core.cpp deleted file mode 100644 index 3b7c260..0000000 --- a/Source/Core/dn_core.cpp +++ /dev/null @@ -1,84 +0,0 @@ -static DN_Core *g_dn_core; - -DN_API void DN_Core_Init(DN_Core *core, DN_CoreOnInit on_init) -{ - DN_Assert(g_dn_os_core_); - g_dn_core = core; - - // NOTE Initialise fields - #if defined(DN_LEAK_TRACKING) - // NOTE: Setup the allocation table with allocation tracking turned off on - // the arena we're using to initialise the table. - core->alloc_table_arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); - core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); - #endif - - // NOTE: Print out init features /////////////////////////////////////////////////////////////// - DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); - DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); - if (on_init & DN_CoreOnInit_LogLibFeatures) { - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("DN initialised:\n")); - - DN_F32 page_size_kib = g_dn_os_core_->page_size / 1024.0f; - DN_F32 alloc_granularity_kib = g_dn_os_core_->alloc_granularity / 1024.0f; - DN_Str8BuilderAppendF(&builder, - " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n" - " Logical Processor Count: %u\n", - page_size_kib, - alloc_granularity_kib, - g_dn_os_core_->logical_processor_count); - - #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - if (DN_ASAN_POISON) { - DN_Str8BuilderAppendF( - &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); - DN_Str8BuilderAppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); - } - #endif - - #if defined(DN_LEAK_TRACKING) - DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" Allocation leak tracing\n")); - #endif - - #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) - DN_POSIXCore *posix = DN_Cast(DN_POSIXCore *)g_dn_os_core_->platform_context; - DN_Str8BuilderAppendF(&builder, " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); - #endif - // TODO(doyle): Add stacktrace feature log - } - - if (on_init & DN_CoreOnInit_LogCPUFeatures) { - DN_CPUReport const *report = &g_dn_os_core_->cpu_report; - DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); - DN_MSVC_WARNING_PUSH - DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8BuilderAppendF' Actual type: 'struct DN_Str8'. - DN_Str8BuilderAppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); - DN_MSVC_WARNING_POP - - DN_USize longest_feature_name = 0; - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); - } - - for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { - DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; - bool has_feature = DN_CPUHasFeature(report, feature_decl.value); - DN_Str8BuilderAppendF(&builder, - " %.*s:%*s%s\n", - DN_Str8PrintFmt(feature_decl.label), - DN_Cast(int)(longest_feature_name - feature_decl.label.size), - "", - has_feature ? "available" : "not available"); - } - } - - DN_Str8 info_log = DN_Str8BuilderBuild(&builder, tmem.arena); - if (info_log.size) - DN_LOG_DebugF("%.*s", DN_Str8PrintFmt(info_log)); -} - -DN_API void DN_Core_BeginFrame() -{ - DN_AtomicSetValue64(&g_dn_os_core_->mem_allocs_frame, 0); -} diff --git a/Source/Core/dn_core.h b/Source/Core/dn_core.h deleted file mode 100644 index b609e8f..0000000 --- a/Source/Core/dn_core.h +++ /dev/null @@ -1,26 +0,0 @@ -#if !defined(DN_CORE_H) -#define DN_CORE_H - -// NOTE: DN_Core -struct DN_Core -{ - // NOTE: Leak Tracing - #if defined(DN_LEAK_TRACKING) - DN_DSMap alloc_table; - DN_TicketMutex alloc_table_mutex; - DN_Arena alloc_table_arena; - #endif - DN_U64 alloc_table_bytes_allocated_for_stack_traces; -}; - -enum DN_CoreOnInit -{ - DN_CoreOnInit_Nil = 0, - DN_CoreOnInit_LogLibFeatures = 1 << 0, - DN_CoreOnInit_LogCPUFeatures = 1 << 1, - DN_CoreOnInit_LogAllFeatures = DN_CoreOnInit_LogLibFeatures | DN_CoreOnInit_LogCPUFeatures, -}; - -DN_API void DN_Core_Init (DN_Core *core, DN_CoreOnInit on_init); -DN_API void DN_Core_BeginFrame(); -#endif // !defined(DN_CORE_H) diff --git a/Source/Core/dn_core_debug.cpp b/Source/Core/dn_core_debug.cpp deleted file mode 100644 index b3907d2..0000000 --- a/Source/Core/dn_core_debug.cpp +++ /dev/null @@ -1,499 +0,0 @@ -#define DN_CORE_DEBUG_CPP - -#include "../dn_base_inc.h" -#include "../dn_os_inc.h" -#include "../dn_core_inc.h" - -DN_API DN_StackTraceWalkResult DN_StackTrace_Walk(DN_Arena *arena, uint16_t limit) -{ - DN_StackTraceWalkResult result = {}; -#if defined(DN_OS_WIN32) - if (!arena) - return result; - - static DN_TicketMutex mutex = {}; - DN_TicketMutex_Begin(&mutex); - - HANDLE thread = GetCurrentThread(); - result.process = GetCurrentProcess(); - - DN_W32Core *w32 = DN_OS_GetW32Core_(); - if (!w32->sym_initialised) { - w32->sym_initialised = true; - SymSetOptions(SYMOPT_LOAD_LINES); - if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_W32Error error = DN_W32_LastError(tmem.arena); - DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg)); - } - } - - CONTEXT context; - RtlCaptureContext(&context); - - STACKFRAME64 frame = {}; - frame.AddrPC.Offset = context.Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context.Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context.Rsp; - frame.AddrStack.Mode = AddrModeFlat; - - DN_FArray raw_frames = {}; - while (raw_frames.size < limit) { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, - result.process, - thread, - &frame, - &context, - nullptr /*ReadMemoryRoutine*/, - SymFunctionTableAccess64, - SymGetModuleBase64, - nullptr /*TranslateAddress*/)) - break; - - // NOTE: It might be useful one day to use frame.AddrReturn.Offset. - // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. - DN_FArray_Add(&raw_frames, frame.AddrPC.Offset); - } - DN_TicketMutex_End(&mutex); - - result.base_addr = DN_ArenaNewArray(arena, uint64_t, raw_frames.size, DN_ZMem_No); - result.size = DN_Cast(uint16_t) raw_frames.size; - DN_Memcpy(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); -#else - (void)limit; - (void)arena; -#endif - return result; -} - -static void DN_StackTrace_AddWalkToStr8Builder(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) -{ - DN_StackTraceRawFrame raw_frame = {}; - raw_frame.process = walk->process; - for (DN_USize index = skip; index < walk->size; index++) { - raw_frame.base_addr = walk->base_addr[index]; - DN_StackTraceFrame frame = DN_StackTrace_RawFrameToFrame(builder->arena, raw_frame); - DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == walk->size - 1) ? "" : "\n"); - } -} - -DN_API bool DN_StackTrace_WalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) -{ - bool result = false; - if (!it || !walk || !walk->base_addr || !walk->process) - return result; - - if (it->index >= walk->size) - return false; - - result = true; - it->raw_frame.process = walk->process; - it->raw_frame.base_addr = walk->base_addr[it->index++]; - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) -{ - DN_Str8 result{}; - if (!walk || !arena) - return result; - - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); - DN_StackTrace_AddWalkToStr8Builder(walk, &builder, skip); - result = DN_Str8BuilderBuild(&builder, arena); - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) -{ - DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); - DN_Str8 result = DN_StackTrace_WalkResultToStr8(arena, &walk, skip); - return result; -} - -DN_API DN_Str8 DN_StackTrace_WalkStr8FromHeap(uint16_t limit, uint16_t skip) -{ - // NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which - // does not use the OS heap. - DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); - DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(&arena, limit); - DN_StackTrace_AddWalkToStr8Builder(&walk, &builder, skip); - DN_Str8 result = DN_Str8BuilderBuildFromOSHeap(&builder); - DN_ArenaDeinit(&arena); - return result; -} - -DN_API DN_Slice DN_StackTrace_GetFrames(DN_Arena *arena, uint16_t limit) -{ - DN_Slice result = {}; - if (!arena) - return result; - - DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); - DN_StackTraceWalkResult walk = DN_StackTrace_Walk(tmem.arena, limit); - if (!walk.size) - return result; - - DN_USize slice_index = 0; - result = DN_Slice_Alloc(arena, walk.size, DN_ZMem_No); - for (DN_StackTraceWalkResultIterator it = {}; DN_StackTrace_WalkResultIterate(&it, &walk); ) { - result.data[slice_index++] = DN_StackTrace_RawFrameToFrame(arena, it.raw_frame); - } - return result; -} - -DN_API DN_StackTraceFrame DN_StackTrace_RawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) -{ - #if defined(DN_OS_WIN32) - // NOTE: Get line+filename ///////////////////////////////////////////////////////////////////// - - // TODO: Why does zero-initialising this with `line = {};` cause - // SymGetLineFromAddr64 function to fail once we are at - // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The - // line and file number are still valid in the result which we use, so, - // we silently ignore this error. - IMAGEHLP_LINEW64 line; - line.SizeOfStruct = sizeof(line); - DWORD line_displacement = 0; - if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) { - line = {}; - } - - // NOTE: Get function name ///////////////////////////////////////////////////////////////////// - - alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; - SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *)buffer; - symbol->SizeOfStruct = sizeof(*symbol); - symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); - - uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address - SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); - - // NOTE: Construct result ////////////////////////////////////////////////////////////////////// - - DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16Size(line.FileName)}; - DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; - - DN_StackTraceFrame result = {}; - result.address = raw_frame.base_addr; - result.line_number = line.LineNumber; - result.file_name = DN_W32_Str16ToStr8(arena, file_name16); - result.function_name = DN_W32_Str16ToStr8(arena, function_name16); - - if (result.function_name.size == 0) - result.function_name = DN_Str8Lit(""); - if (result.file_name.size == 0) - result.file_name = DN_Str8Lit(""); - #else - DN_StackTraceFrame result = {}; - #endif - return result; -} - -DN_API void DN_StackTrace_Print(uint16_t limit) -{ - DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); - DN_Slice stack_trace = DN_StackTrace_GetFrames(tmem.arena, limit); - for (DN_StackTraceFrame &frame : stack_trace) - DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); -} - -DN_API void DN_StackTrace_ReloadSymbols() -{ - #if defined(DN_OS_WIN32) - HANDLE process = GetCurrentProcess(); - SymRefreshModuleList(process); - #endif -} - -// NOTE: DN_Debug ////////////////////////////////////////////////////////////////////////////////// -#if defined(DN_LEAK_TRACKING) -DN_API void DN_DBGTrackAlloc(void *ptr, DN_USize size, bool leak_permitted) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER - { - DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); - }; - - // NOTE: If the entry was not added, we are reusing a pointer that has been freed. - // TODO: Add API for always making the item but exposing a var to indicate if the item was newly created or it - // already existed. - DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_MakeKeyU64(alloc_table, DN_Cast(uint64_t) ptr); - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc_entry.found) { - if ((alloc->flags & DN_DebugAllocFlag_Freed) == 0) { - DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); - DN_Str8x32 new_alloc_size = DN_ByteCountStr8x32(size); - DN_HardAssertF( - alloc->flags & DN_DebugAllocFlag_Freed, - "This pointer is already in the leak tracker, however it has not been freed yet. This " - "same pointer is being ask to be tracked twice in the allocation table, e.g. one if its " - "previous free calls has not being marked freed with an equivalent call to " - "DN_DBGTrackDealloc()\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is allocating %.*s again at:\n" - "\n" - "%.*s\n", - ptr, - DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(new_alloc_size), - DN_Str8PrintFmt(stack_trace)); - } - - // NOTE: Pointer was reused, clean up the prior entry - g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->stack_trace.size; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces -= alloc->freed_stack_trace.size; - - DN_OS_MemDealloc(alloc->stack_trace.data); - DN_OS_MemDealloc(alloc->freed_stack_trace.data); - *alloc = {}; - } - - alloc->ptr = ptr; - alloc->size = size; - alloc->stack_trace = stack_trace; - alloc->flags |= leak_permitted ? DN_DebugAllocFlag_LeakPermitted : 0; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->stack_trace.size; -} - -DN_API void DN_DBGTrackDealloc(void *ptr) -{ - if (!ptr) - return; - - DN_TicketMutex_Begin(&g_dn_core->alloc_table_mutex); - DN_DEFER { DN_TicketMutex_End(&g_dn_core->alloc_table_mutex); }; - - DN_Str8 stack_trace = DN_StackTrace_WalkStr8FromHeap(128, 3 /*skip*/); - DN_DSMap *alloc_table = &g_dn_core->alloc_table; - DN_DSMapResult alloc_entry = DN_DSMap_FindKeyU64(alloc_table, DN_Cast(uintptr_t) ptr); - DN_HardAssertF(alloc_entry.found, - "Allocated pointer can not be removed as it does not exist in the " - "allocation table. When this memory was allocated, the pointer was " - "not added to the allocation table [ptr=%p]", - ptr); - - DN_DebugAlloc *alloc = alloc_entry.value; - if (alloc->flags & DN_DebugAllocFlag_Freed) { - DN_Str8x32 freed_size = DN_ByteCountStr8x32(alloc->freed_size); - DN_HardAssertF((alloc->flags & DN_DebugAllocFlag_Freed) == 0, - "Double free detected, pointer to free was already marked " - "as freed. Either the pointer was reallocated but not " - "traced, or, the pointer was freed twice.\n" - "\n" - "The pointer (0x%p) originally allocated %.*s at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer was freed at:\n" - "\n" - "%.*s\n" - "\n" - "The pointer is being freed again at:\n" - "\n" - "%.*s\n" - , - ptr, DN_Str8PrintFmt(freed_size), - DN_Str8PrintFmt(alloc->stack_trace), - DN_Str8PrintFmt(alloc->freed_stack_trace), - DN_Str8PrintFmt(stack_trace)); - } - - DN_Assert(alloc->freed_stack_trace.size == 0); - alloc->flags |= DN_DebugAllocFlag_Freed; - alloc->freed_stack_trace = stack_trace; - g_dn_core->alloc_table_bytes_allocated_for_stack_traces += alloc->freed_stack_trace.size; -} - -DN_API void DN_DBGDumpLeaks() -{ - uint64_t leak_count = 0; - uint64_t leaked_bytes = 0; - for (DN_USize index = 1; index < g_dn_core->alloc_table.occupied; index++) { - DN_DSMapSlot *slot = g_dn_core->alloc_table.slots + index; - DN_DebugAlloc *alloc = &slot->value; - bool alloc_leaked = (alloc->flags & DN_DebugAllocFlag_Freed) == 0; - bool leak_permitted = (alloc->flags & DN_DebugAllocFlag_LeakPermitted); - if (alloc_leaked && !leak_permitted) { - leaked_bytes += alloc->size; - leak_count++; - DN_Str8x32 alloc_size = DN_ByteCountStr8x32(alloc->size); - DN_LOG_WarningF("Pointer (0x%p) leaked %.*s at:\n" - "%.*s", - alloc->ptr, DN_Str8PrintFmt(alloc_size), - DN_Str8PrintFmt(alloc->stack_trace)); - } - } - - if (leak_count) { - DN_Str8x32 leak_size = DN_ByteCountStr8x32(leaked_bytes); - DN_LOG_WarningF("There were %I64u leaked allocations totalling %.*s", leak_count, DN_Str8PrintFmt(leak_size)); - } -} -#endif // DN_LEAK_TRACKING - -// NOTE: DN_Profiler -DN_API DN_Profiler DN_Profiler_Init(DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSC tsc, DN_U64 tsc_frequency) -{ - DN_Profiler result = {}; - result.anchors = anchors; - result.anchors_count = count; - result.anchors_per_frame = anchors_per_frame; - result.tsc = tsc; - result.tsc_frequency = tsc_frequency; - return result; -} - -DN_API DN_USize DN_Profiler_FrameCount(DN_Profiler const *profiler) -{ - DN_USize result = profiler->anchors_count / profiler->anchors_per_frame; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchorsFromIndex(DN_Profiler *profiler, DN_USize frame_index) -{ - DN_ProfilerAnchorArray result = {}; - DN_USize anchor_offset = frame_index * profiler->anchors_per_frame; - result.data = profiler->anchors + anchor_offset; - result.count = profiler->anchors_per_frame; - return result; -} - -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchors(DN_Profiler *profiler) -{ - DN_ProfilerAnchorArray result = DN_Profiler_FrameAnchorsFromIndex(profiler, profiler->frame_index); - return result; -} - -DN_API DN_ProfilerZone DN_Profiler_BeginZone(DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index) -{ - DN_ProfilerZone result = {}; - if (profiler->paused) - return result; - - DN_Assert(anchor_index < profiler->anchors_per_frame); - DN_ProfilerAnchor *anchor = DN_Profiler_FrameAnchors(profiler).data + anchor_index; - anchor->name = name; - - // TODO: We need per-thread-local-storage profiler so that we can use these apis - // across threads. For now, we let them overwrite each other but this is not tenable. - #if 0 - if (anchor->name.size && anchor->name != name) - DN_AssertF(name == anchor->name, "Potentially overwriting a zone by accident? Anchor is '%.*s', name is '%.*s'", DN_Str8PrintFmt(anchor->name), DN_Str8PrintFmt(name)); - #endif - - if (profiler->tsc == DN_ProfilerTSC_RDTSC) - result.begin_tsc = DN_CPUGetTSC(); - else - result.begin_tsc = DN_OS_PerfCounterNow(); - result.anchor_index = anchor_index; - result.parent_zone = profiler->parent_zone; - result.elapsed_tsc_at_zone_start = anchor->tsc_inclusive; - profiler->parent_zone = anchor_index; - return result; -} - -DN_API void DN_Profiler_EndZone(DN_Profiler *profiler, DN_ProfilerZone zone) -{ - if (profiler->paused) - return; - - DN_Assert(zone.anchor_index < profiler->anchors_per_frame); - DN_Assert(zone.parent_zone < profiler->anchors_per_frame); - - DN_ProfilerAnchorArray array = DN_Profiler_FrameAnchors(profiler); - DN_ProfilerAnchor *anchor = array.data + zone.anchor_index; - DN_U64 tsc_now = profiler->tsc == DN_ProfilerTSC_RDTSC ? DN_CPUGetTSC() : DN_OS_PerfCounterNow(); - DN_U64 elapsed_tsc = tsc_now - zone.begin_tsc; - - anchor->hit_count++; - anchor->tsc_inclusive = zone.elapsed_tsc_at_zone_start + elapsed_tsc; - anchor->tsc_exclusive += elapsed_tsc; - - if (zone.parent_zone != zone.anchor_index) { - DN_ProfilerAnchor *parent_anchor = array.data + zone.parent_zone; - parent_anchor->tsc_exclusive -= elapsed_tsc; - } - profiler->parent_zone = zone.parent_zone; -} - -DN_API void DN_Profiler_NewFrame(DN_Profiler *profiler) -{ - if (profiler->paused) - return; - - // NOTE: End the frame's zone - DN_Profiler_EndZone(profiler, profiler->frame_zone); - DN_ProfilerAnchorArray old_frame_anchors = DN_Profiler_FrameAnchors(profiler); - DN_ProfilerAnchor old_frame_anchor = old_frame_anchors.data[0]; - profiler->frame_avg_tsc = (profiler->frame_avg_tsc + old_frame_anchor.tsc_inclusive) / 2.f; - - // NOTE: Bump to the next frame - DN_USize frame_count = profiler->anchors_count / profiler->anchors_per_frame; - profiler->frame_index = (profiler->frame_index + 1) % frame_count; - - // NOTE: Zero out the anchors - DN_ProfilerAnchorArray next_anchors = DN_Profiler_FrameAnchors(profiler); - DN_Memset(next_anchors.data, 0, sizeof(*profiler->anchors) * next_anchors.count); - - // NOTE: Start the frame's zone - profiler->frame_zone = DN_Profiler_BeginZone(profiler, DN_Str8Lit("Profiler Frame"), 0); -} - -DN_API void DN_Profiler_Dump(DN_Profiler *profiler) -{ - if (profiler->frame_index == 0) - return; - - DN_USize frame_index = profiler->frame_index - 1; - DN_Assert(profiler->frame_index < profiler->anchors_per_frame); - - DN_ProfilerAnchor *anchors = profiler->anchors + (frame_index * profiler->anchors_per_frame); - for (DN_USize index = 1; index < profiler->anchors_per_frame; index++) { - DN_ProfilerAnchor const *anchor = anchors + index; - if (!anchor->hit_count) - continue; - - DN_U64 tsc_exclusive = anchor->tsc_exclusive; - DN_U64 tsc_inclusive = anchor->tsc_inclusive; - DN_F64 tsc_exclusive_milliseconds = tsc_exclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; - if (tsc_exclusive == tsc_inclusive) { - DN_OS_PrintOutLnF("%.*s[%u]: %.1fms", DN_Str8PrintFmt(anchor->name), anchor->hit_count, tsc_exclusive_milliseconds); - } else { - DN_F64 tsc_inclusive_milliseconds = tsc_inclusive * 1000 / DN_Cast(DN_F64) profiler->tsc_frequency; - DN_OS_PrintOutLnF("%.*s[%u]: %.1f/%.1fms", - DN_Str8PrintFmt(anchor->name), - anchor->hit_count, - tsc_exclusive_milliseconds, - tsc_inclusive_milliseconds); - } - } -} - -DN_API DN_F64 DN_Profiler_SecFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency; - return result; -} - -DN_API DN_F64 DN_Profiler_MsFromTSC(DN_Profiler *profiler, DN_U64 duration_tsc) -{ - DN_F64 result = DN_Cast(DN_F64)duration_tsc / profiler->tsc_frequency * 1000.0; - return result; -} diff --git a/Source/Core/dn_core_debug.h b/Source/Core/dn_core_debug.h deleted file mode 100644 index 0c582b9..0000000 --- a/Source/Core/dn_core_debug.h +++ /dev/null @@ -1,107 +0,0 @@ -#if !defined(DN_CORE_DEBUG_H) -#define DN_CORE_DEBUG_H - -#include "../dn_base_inc.h" - -// NOTE: DN_Debug -enum DN_DebugAllocFlag -{ - DN_DebugAllocFlag_Freed = 1 << 0, - DN_DebugAllocFlag_LeakPermitted = 1 << 1, -}; - -struct DN_DebugAlloc -{ - void *ptr; // 8 Pointer to the allocation being tracked - DN_USize size; // 16 Size of the allocation - DN_USize freed_size; // 24 Store the size of the allocation when it is freed - DN_Str8 stack_trace; // 40 Stack trace at the point of allocation - DN_Str8 freed_stack_trace; // 56 Stack trace of where the allocation was freed - DN_U16 flags; // 72 Bit flags from `DN_DebugAllocFlag` -}; - -static_assert(sizeof(DN_DebugAlloc) == 64 || sizeof(DN_DebugAlloc) == 32, // NOTE: 64 bit vs 32 bit pointers respectively - "We aim to keep the allocation record as light as possible as " - "memory tracking can get expensive. Enforce that there is no " - "unexpected padding."); - -// NOTE: DN_Profiler -struct DN_ProfilerAnchor -{ - // Inclusive refers to the time spent to complete the function call - // including all children functions. - // - // Exclusive refers to the time spent in the function, not including any - // time spent in children functions that we call that are also being - // profiled. If we recursively call into ourselves, the time we spent in - // our function is accumulated. - DN_U64 tsc_inclusive; - DN_U64 tsc_exclusive; - DN_U16 hit_count; - DN_Str8 name; -}; - -struct DN_ProfilerZone -{ - DN_U16 anchor_index; - DN_U64 begin_tsc; - DN_U16 parent_zone; - DN_U64 elapsed_tsc_at_zone_start; -}; - -struct DN_ProfilerAnchorArray -{ - DN_ProfilerAnchor *data; - DN_USize count; -}; - -enum DN_ProfilerTSC -{ - DN_ProfilerTSC_RDTSC, - DN_ProfilerTSC_OSPerformanceCounter, -}; - -struct DN_Profiler -{ - DN_USize frame_index; - DN_ProfilerAnchor *anchors; - DN_USize anchors_count; - DN_USize anchors_per_frame; - DN_U16 parent_zone; - bool paused; - DN_ProfilerTSC tsc; - DN_U64 tsc_frequency; - DN_ProfilerZone frame_zone; - DN_F64 frame_avg_tsc; -}; - -#define DN_Profiler_ZoneLoop(prof, name, index) \ - DN_ProfilerZone DN_UniqueName(zone_) = DN_Profiler_BeginZone(prof, DN_Str8Lit(name), index), DN_UniqueName(dummy_) = {}; \ - DN_UniqueName(dummy_).begin_tsc == 0; \ - DN_Profiler_EndZone(prof, DN_UniqueName(zone_)), DN_UniqueName(dummy_).begin_tsc = 1 - -#define DN_Profiler_ZoneLoopAuto(prof, name) DN_Profiler_ZoneLoop(prof, name, __COUNTER__ + 1) - -DN_API DN_Profiler DN_Profiler_Init (DN_ProfilerAnchor *anchors, DN_USize count, DN_USize anchors_per_frame, DN_ProfilerTSC tsc, DN_U64 tsc_frequency); -DN_API DN_ProfilerZone DN_Profiler_BeginZone (DN_Profiler *profiler, DN_Str8 name, DN_U16 anchor_index); -#define DN_Profiler_BeginZoneAuto(prof, name) DN_Profiler_BeginZone(prof, DN_Str8Lit(name), __COUNTER__ + 1) -DN_API void DN_Profiler_EndZone (DN_Profiler *profiler, DN_ProfilerZone zone); -DN_API DN_USize DN_Profiler_FrameCount (DN_Profiler const *profiler); -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchorsFromIndex (DN_Profiler *profiler, DN_USize frame_index); -DN_API DN_ProfilerAnchorArray DN_Profiler_FrameAnchors (DN_Profiler *profiler); -DN_API void DN_Profiler_NewFrame (DN_Profiler *profiler); -DN_API void DN_Profiler_Dump (DN_Profiler *profiler); -DN_API DN_F64 DN_Profiler_SecFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); -DN_API DN_F64 DN_Profiler_MsFromTSC (DN_Profiler *profiler, DN_U64 duration_tsc); - - -#if defined(DN_LEAK_TRACKING) -DN_API void DN_DBGTrackAlloc (void *ptr, DN_USize size, bool alloc_can_leak); -DN_API void DN_DBGTrackDealloc (void *ptr); -DN_API void DN_DBGDumpLeaks (); -#else -#define DN_DBGTrackAlloc(ptr, size, alloc_can_leak) do { (void)ptr; (void)size; (void)alloc_can_leak; } while (0) -#define DN_DBGTrackDealloc(ptr) do { (void)ptr; } while (0) -#define DN_DBGDumpLeaks() do { } while (0) -#endif -#endif // DN_CORE_DEBUG_H diff --git a/Source/Core/dn_core_demo.cpp b/Source/Extra/dn_demo.cpp similarity index 92% rename from Source/Core/dn_core_demo.cpp rename to Source/Extra/dn_demo.cpp index 82fbb35..afd7a85 100644 --- a/Source/Core/dn_core_demo.cpp +++ b/Source/Extra/dn_demo.cpp @@ -1,36 +1,6 @@ -/* -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ -// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ -// $$ | $$ |$$ / $$ |$$ / \__|$$ / \__| -// $$ | $$ |$$ | $$ |$$ | \$$$$$$\ -// $$ | $$ |$$ | $$ |$$ | \____$$\ -// $$ | $$ |$$ | $$ |$$ | $$\ $$\ $$ | -// $$$$$$$ | $$$$$$ |\$$$$$$ |\$$$$$$ | -// \_______/ \______/ \______/ \______/ -// -// dn_docs.cpp -- Library documentation via real code examples -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Use this file for documentation and examples of the various APIs in this -// library. Normally docs are written as inline comments in header files, -// however, these quickly go out of date as APIs change. Instead, I provide -// some example code that compiles here that serves to also document the API. -// -// The library header files then become a very minimal reference of exactly the -// function prototypes and definitions instead of massive reams of inline -// comments that visually space out the functions and hinders discoverability -// and/or conciseness of being able to learn the breadth of the APIs. -// -//////////////////////////////////////////////////////////////////////////////////////////////////// -*/ - DN_MSVC_WARNING_PUSH DN_MSVC_WARNING_DISABLE(4702) // unreachable code - -void DN_Docs_Demo() +void DN_Demo() { // NOTE: Before using anything in the library, DN_Core_Init() must be // called, for example: @@ -556,75 +526,42 @@ void DN_Docs_Demo() } #endif -#if 0 -#if !defined(DN_NO_PROFILER) // NOTE: DN_Profiler // // A profiler based off Casey Muratori's Computer Enhance course, Performance // Aware Programming. This profiler measures function elapsed time using the // CPU's time stamp counter (e.g. rdtsc) providing a rough cycle count // that can be converted into a duration. - // - // This profiler uses a double buffer scheme for storing profiling markers. - // After an application's typical update/frame cycle you can swap the - // profiler's buffer whereby the front buffer contains the previous frames - // profiling metrics and the back buffer will be populated with the new - // frame's profiling metrics. + #if defined(DN_OS_CPP) { - enum Zone + enum DemoZone { - Zone_MainLoop, - Zone_Count + DemoZone_MainLoop, + DemoZone_Count }; - DN_ProfilerZone profiler_zone_main_update = DN_Profiler_BeginZone(Zone_MainLoop); + #if defined(DN_PLATFORM_EMSCRIPTEN) + DN_ProfilerTSCNowFunc *tsc_now = DN_OS_PerfCounterNow; + DN_U64 tsc_frequency = DN_OS_PerfCounterFrequency(); + #else + DN_ProfilerTSCNowFunc *tsc_now = __rdtsc; + DN_U64 tsc_frequency = DN_OS_EstimateTSCPerSecond(100); + #endif - // NOTE: DN_Profiler_AnchorBuffer - // - // Retrieve the requested buffer from the profiler for - // writing/reading profiling metrics. Pass in the enum to specify - // which buffer to grab from the profiler. - // - // The front buffer contains the previous frame's profiling metrics - // and the back buffer is where the profiler is currently writing - // to. - // - // For end user intents and purposes, you likely only need to read - // the front buffer which contain the metrics that you can visualise - // regarding the most profiling metrics recorded. + DN_ProfilerAnchor anchors[4] = {}; + DN_USize anchors_count = DN_ArrayCountU(anchors); + DN_USize anchors_per_frame = anchors_count / 2; + DN_Profiler profiler = DN_ProfilerInit(anchors, anchors_count, anchors_per_frame, tsc_now, tsc_frequency); - // NOTE: DN_Profiler_ReadBuffer - // - // Retrieve the buffer of anchors of which there are - // `DN_PROFILER_ANCHOR_BUFFER_SIZE` anchors from the most recent run - // of the profiler after you have called `SwapAnchorBuffer` to trigger - // the double buffer - DN_ProfilerAnchor *read_anchors = DN_Profiler_ReadBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = read_anchors + index; - if (anchor->name.size) { - // ... - } + for (DN_USize it = 0; it < 1; it++) { + DN_ProfilerNewFrame(&profiler); + DN_ProfilerZone zone = DN_ProfilerBeginZone(&profiler, DN_Str8Lit("Main Loop"), DemoZone_MainLoop); + DN_OS_SleepMs(100); + DN_ProfilerEndZone(&profiler, zone); + DN_ProfilerDump(&profiler); } - - // NOTE: DN_Profiler_WriteBuffer - // - // Same as `ReadBuffer` however we return the buffer that the profiler - // is currently writing anchors into. - DN_ProfilerAnchor *write_anchors = DN_Profiler_WriteBuffer(); - for (DN_USize index = 0; index < DN_PROFILER_ANCHOR_BUFFER_SIZE; index++) { - DN_ProfilerAnchor *anchor = write_anchors + index; - if (anchor->name.size) { - // ... - } - } - - DN_Profiler_EndZone(profiler_zone_main_update); - DN_Profiler_SwapAnchorBuffer(); // Should occur after all profiling zones are ended! - DN_Memset(g_dn_core->profiler, 0, sizeof(*g_dn_core->profiler)); } -#endif // !defined(DN_NO_PROFILER) -#endif + #endif // NOTE: DN_Raycast_LineIntersectV2 // Calculate the intersection point of 2 rays returning a `t` value @@ -1199,5 +1136,4 @@ void DN_Docs_Demo() // queried using 'DN_W32_LastError' #endif } - DN_MSVC_WARNING_POP diff --git a/Source/Extra/dn_tests_main.cpp b/Source/Extra/dn_tests_main.cpp index b0bbf2a..60256cd 100644 --- a/Source/Extra/dn_tests_main.cpp +++ b/Source/Extra/dn_tests_main.cpp @@ -4,11 +4,11 @@ #include "../dn_base_inc.h" #include "../dn_os_inc.h" -#include "../dn_core_inc.h" +#include "../dn_inc.h" #include "../dn_base_inc.cpp" #include "../dn_os_inc.cpp" -#include "../dn_core_inc.cpp" +#include "../dn_inc.cpp" #include "../Extra/dn_math.h" #include "../Extra/dn_helpers.h" @@ -42,10 +42,7 @@ DN_MSVC_WARNING_DISABLE(6262) // Function uses '29804' bytes of stack. Consider int main(int, char**) { DN_Core core = {}; - DN_OSCore os = {}; - - DN_OS_Init(&os, nullptr); - DN_Core_Init(&core, DN_CoreOnInit_LogAllFeatures); + DN_Init(&core, DN_InitFlags_LogAllFeatures, nullptr); DN_Tests_RunSuite(DN_TestsPrint_Yes); return 0; } diff --git a/Source/OS/dn_os.cpp b/Source/OS/dn_os.cpp index ff2b0fa..b8e4fda 100644 --- a/Source/OS/dn_os.cpp +++ b/Source/OS/dn_os.cpp @@ -8,8 +8,6 @@ #include // getpagesize #endif -static DN_OSCore *g_dn_os_core_; - static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args) { DN_Assert(user_data); @@ -85,92 +83,6 @@ static void DN_OS_LOGEmitFromTypeTypeFV_(DN_LOGTypeParam type, void *user_data, DN_OS_PrintLnFV(dest, fmt, args); } -DN_API void DN_OS_Init(DN_OSCore *os, DN_OSInitArgs *args) -{ - g_dn_os_core_ = os; - - // NOTE: OS - { - #if defined(DN_PLATFORM_WIN32) - SYSTEM_INFO system_info = {}; - GetSystemInfo(&system_info); - - os->logical_processor_count = system_info.dwNumberOfProcessors; - os->page_size = system_info.dwPageSize; - os->alloc_granularity = system_info.dwAllocationGranularity; - #else - #if defined(DN_PLATFORM_EMSCRIPTEN) - os->logical_processor_count = 1; - #else - os->logical_processor_count = get_nprocs(); - #endif - os->page_size = getpagesize(); - os->alloc_granularity = os->page_size; - #endif - } - - // NOTE: Setup logging - DN_OS_EmitLogsWithOSPrintFunctions(os); - - { - #if defined(DN_PLATFORM_EMSCRIPTEN) - os->arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_ArenaFlags_NoAllocTrack); - #else - os->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); - #endif - - #if defined(DN_PLATFORM_WIN32) - os->platform_context = DN_ArenaNew(&os->arena, DN_W32Core, DN_ZMem_Yes); - #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) - os->platform_context = DN_ArenaNew(&os->arena, DN_POSIXCore, DN_ZMem_Yes); - #endif - - #if defined(DN_PLATFORM_WIN32) - DN_W32Core *w32 = DN_Cast(DN_W32Core *) os->platform_context; - InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); - - QueryPerformanceFrequency(&w32->qpc_frequency); - HMODULE module = LoadLibraryA("kernel32.dll"); - if (module) { - w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); - FreeLibrary(module); - } - - // NOTE: win32 bcrypt - wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; - long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); - if (w32->bcrypt_rng_handle && init_status == 0) - w32->bcrypt_init_success = true; - else - DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); - #else - DN_Posix_Init(DN_Cast(DN_POSIXCore *)os->platform_context); - #endif - } - - // NOTE: Initialise tmem arenas which allocate memory and will be - // recorded to the now initialised allocation table. The initialisation - // of tmem memory may request tmem memory itself in leak tracing mode. - // This is supported as the tmem arenas defer allocation tracking until - // initialisation is done. - DN_OSTLSInitArgs tls_init_args = {}; - if (args) { - tls_init_args.commit = args->tls_commit; - tls_init_args.reserve = args->tls_reserve; - tls_init_args.err_sink_reserve = args->tls_err_sink_reserve; - tls_init_args.err_sink_commit = args->tls_err_sink_commit; - } - - DN_OS_TLSInit(&os->tls, tls_init_args); - DN_OS_TLSSetCurrentThreadTLS(&os->tls); - os->cpu_report = DN_CPUGetReport(); - - #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; - DN_CPU_FEAT_XMACRO - #undef DN_CPU_FEAT_XENTRY - DN_Assert(g_dn_os_core_); -} - DN_API void DN_OS_EmitLogsWithOSPrintFunctions(DN_OSCore *os) { DN_Assert(os); @@ -321,7 +233,7 @@ DN_API bool DN_OS_DateIsValid(DN_OSDateTime date) return true; } -// NOTE: Other ///////////////////////////////////////////////////////////////////////////////////// +// NOTE: Other DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) { DN_Str8 result = {}; @@ -335,7 +247,7 @@ DN_API DN_Str8 DN_OS_EXEDir(DN_Arena *arena) return result; } -// NOTE: Counters ////////////////////////////////////////////////////////////////////////////////// +// NOTE: Counters DN_API DN_F64 DN_OS_PerfCounterS(uint64_t begin, uint64_t end) { uint64_t frequency = DN_OS_PerfCounterFrequency(); diff --git a/Source/OS/dn_os.h b/Source/OS/dn_os.h index 4e21738..ce32013 100644 --- a/Source/OS/dn_os.h +++ b/Source/OS/dn_os.h @@ -259,14 +259,6 @@ struct DN_OSHttpResponse #endif }; -struct DN_OSInitArgs -{ - DN_U64 tls_reserve; - DN_U64 tls_commit; - DN_U64 tls_err_sink_reserve; - DN_U64 tls_err_sink_commit; -}; - struct DN_OSCore { DN_CPUReport cpu_report; @@ -305,7 +297,6 @@ struct DN_OSDiskSpace DN_U64 size; }; -DN_API void DN_OS_Init (DN_OSCore *os, DN_OSInitArgs *args); DN_API void DN_OS_EmitLogsWithOSPrintFunctions (DN_OSCore *os); DN_API void DN_OS_DumpThreadContextArenaStat (DN_Str8 file_path); diff --git a/Source/OS/dn_os_allocator.cpp b/Source/OS/dn_os_allocator.cpp index eeda1a4..7f506b6 100644 --- a/Source/OS/dn_os_allocator.cpp +++ b/Source/OS/dn_os_allocator.cpp @@ -23,7 +23,7 @@ DN_API DN_Arena DN_ArenaFromVMem(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags fl { DN_ArenaMemFuncs mem_funcs = {}; mem_funcs.type = DN_ArenaMemFuncType_VMem; - mem_funcs.vmem_page_size = g_dn_os_core_->page_size; + mem_funcs.vmem_page_size = g_dn_->os.page_size; mem_funcs.vmem_reserve = DN_OS_MemReserve; mem_funcs.vmem_commit = DN_OS_MemCommit; mem_funcs.vmem_release = DN_OS_MemRelease; diff --git a/Source/OS/dn_os_containers.cpp b/Source/OS/dn_os_containers.cpp index 11d7919..5c1a8cc 100644 --- a/Source/OS/dn_os_containers.cpp +++ b/Source/OS/dn_os_containers.cpp @@ -193,7 +193,7 @@ bool DN_VArray_Reserve(DN_VArray *array, DN_USize count) return false; DN_USize real_commit = (array->size + count) * sizeof(T); - DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_os_core_->page_size); + DN_USize aligned_commit = DN_AlignUpPowerOfTwo(real_commit, g_dn_->os.page_size); if (array->commit >= aligned_commit) return true; bool result = DN_OS_MemCommit(array->data, aligned_commit, DN_MemPage_ReadWrite); diff --git a/Source/OS/dn_os_posix.cpp b/Source/OS/dn_os_posix.cpp index 717a0d9..956ca80 100644 --- a/Source/OS/dn_os_posix.cpp +++ b/Source/OS/dn_os_posix.cpp @@ -29,8 +29,8 @@ DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_fl os_page_flags |= (PROT_READ | PROT_WRITE); void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); if (result == MAP_FAILED) result = nullptr; return result; @@ -44,8 +44,8 @@ DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); result = mprotect(ptr, size, os_page_flags) == 0; - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); return result; } @@ -68,11 +68,11 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_Str8Lit( "Page protection requires pointers to be page aligned because we " "can only guard memory at a multiple of the page boundary."); - DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_os_core_->page_size), + DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); DN_AssertF( - DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_IsPowerOfTwoAligned(size, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); int result = mprotect(ptr, size, os_page_flags); @@ -283,8 +283,8 @@ DN_API DN_U64 DN_OS_PerfCounterFrequency() static DN_POSIXCore *DN_OS_GetPOSIXCore_() { - DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); - DN_POSIXCore *result = DN_Cast(DN_POSIXCore *)g_dn_os_core_->platform_context; + DN_Assert(g_dn_ && g_dn_->os.platform_context); + DN_POSIXCore *result = DN_Cast(DN_POSIXCore *)g_dn_->os.platform_context; return result; } @@ -1015,7 +1015,7 @@ static DN_POSIXSyncPrimitive *DN_POSIX_AllocSyncPrimitive_() posix->sync_primitive_free_list = posix->sync_primitive_free_list->next; result->next = nullptr; } else { - DN_OSCore *os = g_dn_os_core_; + DN_OSCore *os = &g_dn_->os; result = DN_ArenaNew(&os->arena, DN_POSIXSyncPrimitive, DN_ZMem_Yes); } } diff --git a/Source/OS/dn_os_stacktrace.cpp b/Source/OS/dn_os_stacktrace.cpp new file mode 100644 index 0000000..1b2a95c --- /dev/null +++ b/Source/OS/dn_os_stacktrace.cpp @@ -0,0 +1,209 @@ +#define DN_CORE_DEBUG_CPP + +#include "../dn_base_inc.h" +#include "../dn_os_inc.h" + +DN_API DN_StackTraceWalkResult DN_StackTraceWalk(DN_Arena *arena, uint16_t limit) +{ + DN_StackTraceWalkResult result = {}; +#if defined(DN_OS_WIN32) + if (!arena) + return result; + + static DN_TicketMutex mutex = {}; + DN_TicketMutex_Begin(&mutex); + + HANDLE thread = GetCurrentThread(); + result.process = GetCurrentProcess(); + + DN_W32Core *w32 = DN_OS_GetW32Core_(); + if (!w32->sym_initialised) { + w32->sym_initialised = true; + SymSetOptions(SYMOPT_LOAD_LINES); + if (!SymInitialize(result.process, nullptr /*UserSearchPath*/, true /*fInvadeProcess*/)) { + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_W32Error error = DN_W32_LastError(tmem.arena); + DN_LOG_ErrorF("SymInitialize failed, stack trace can not be generated (%lu): %.*s\n", error.code, DN_Str8PrintFmt(error.msg)); + } + } + + CONTEXT context; + RtlCaptureContext(&context); + + STACKFRAME64 frame = {}; + frame.AddrPC.Offset = context.Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context.Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context.Rsp; + frame.AddrStack.Mode = AddrModeFlat; + + DN_FArray raw_frames = {}; + while (raw_frames.size < limit) { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, + result.process, + thread, + &frame, + &context, + nullptr /*ReadMemoryRoutine*/, + SymFunctionTableAccess64, + SymGetModuleBase64, + nullptr /*TranslateAddress*/)) + break; + + // NOTE: It might be useful one day to use frame.AddrReturn.Offset. + // If AddrPC.Offset == AddrReturn.Offset then we can detect recursion. + DN_FArray_Add(&raw_frames, frame.AddrPC.Offset); + } + DN_TicketMutex_End(&mutex); + + result.base_addr = DN_ArenaNewArray(arena, uint64_t, raw_frames.size, DN_ZMem_No); + result.size = DN_Cast(uint16_t) raw_frames.size; + DN_Memcpy(result.base_addr, raw_frames.data, raw_frames.size * sizeof(raw_frames.data[0])); +#else + (void)limit; + (void)arena; +#endif + return result; +} + +static void DN_StackTraceAddWalkToStr8Builder(DN_StackTraceWalkResult const *walk, DN_Str8Builder *builder, DN_USize skip) +{ + DN_StackTraceRawFrame raw_frame = {}; + raw_frame.process = walk->process; + for (DN_USize index = skip; index < walk->size; index++) { + raw_frame.base_addr = walk->base_addr[index]; + DN_StackTraceFrame frame = DN_StackTraceRawFrameToFrame(builder->arena, raw_frame); + DN_Str8BuilderAppendF(builder, "%.*s(%zu): %.*s%s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name), (DN_Cast(int) index == walk->size - 1) ? "" : "\n"); + } +} + +DN_API bool DN_StackTraceWalkResultIterate(DN_StackTraceWalkResultIterator *it, DN_StackTraceWalkResult const *walk) +{ + bool result = false; + if (!it || !walk || !walk->base_addr || !walk->process) + return result; + + if (it->index >= walk->size) + return false; + + result = true; + it->raw_frame.process = walk->process; + it->raw_frame.base_addr = walk->base_addr[it->index++]; + return result; +} + +DN_API DN_Str8 DN_StackTraceWalkResultToStr8(DN_Arena *arena, DN_StackTraceWalkResult const *walk, uint16_t skip) +{ + DN_Str8 result{}; + if (!walk || !arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); + DN_StackTraceAddWalkToStr8Builder(walk, &builder, skip); + result = DN_Str8BuilderBuild(&builder, arena); + return result; +} + +DN_API DN_Str8 DN_StackTraceWalkStr8(DN_Arena *arena, uint16_t limit, uint16_t skip) +{ + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(tmem.arena, limit); + DN_Str8 result = DN_StackTraceWalkResultToStr8(arena, &walk, skip); + return result; +} + +DN_API DN_Str8 DN_StackTraceWalkStr8FromHeap(uint16_t limit, uint16_t skip) +{ + // NOTE: We don't use WalkResultToStr8 because that uses the TLS arenas which + // does not use the OS heap. + DN_Arena arena = DN_ArenaFromHeap(DN_Kilobytes(64), DN_ArenaFlags_NoAllocTrack); + DN_Str8Builder builder = DN_Str8BuilderFromArena(&arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(&arena, limit); + DN_StackTraceAddWalkToStr8Builder(&walk, &builder, skip); + DN_Str8 result = DN_Str8BuilderBuildFromOSHeap(&builder); + DN_ArenaDeinit(&arena); + return result; +} + +DN_API DN_Slice DN_StackTraceGetFrames(DN_Arena *arena, uint16_t limit) +{ + DN_Slice result = {}; + if (!arena) + return result; + + DN_OSTLSTMem tmem = DN_OS_TLSTMem(arena); + DN_StackTraceWalkResult walk = DN_StackTraceWalk(tmem.arena, limit); + if (!walk.size) + return result; + + DN_USize slice_index = 0; + result = DN_Slice_Alloc(arena, walk.size, DN_ZMem_No); + for (DN_StackTraceWalkResultIterator it = {}; DN_StackTraceWalkResultIterate(&it, &walk);) + result.data[slice_index++] = DN_StackTraceRawFrameToFrame(arena, it.raw_frame); + return result; +} + +DN_API DN_StackTraceFrame DN_StackTraceRawFrameToFrame(DN_Arena *arena, DN_StackTraceRawFrame raw_frame) +{ +#if defined(DN_OS_WIN32) + // NOTE: Get line+filename + + // TODO: Why does zero-initialising this with `line = {};` cause + // SymGetLineFromAddr64 function to fail once we are at + // __scrt_commain_main_seh and hit BaseThreadInitThunk frame? The + // line and file number are still valid in the result which we use, so, + // we silently ignore this error. + IMAGEHLP_LINEW64 line; + line.SizeOfStruct = sizeof(line); + DWORD line_displacement = 0; + if (!SymGetLineFromAddrW64(raw_frame.process, raw_frame.base_addr, &line_displacement, &line)) + line = {}; + + // NOTE: Get function name + + alignas(SYMBOL_INFOW) char buffer[sizeof(SYMBOL_INFOW) + (MAX_SYM_NAME * sizeof(wchar_t))] = {}; + SYMBOL_INFOW *symbol = DN_Cast(SYMBOL_INFOW *) buffer; + symbol->SizeOfStruct = sizeof(*symbol); + symbol->MaxNameLen = sizeof(buffer) - sizeof(*symbol); + + uint64_t symbol_displacement = 0; // Offset to the beginning of the symbol to the address + SymFromAddrW(raw_frame.process, raw_frame.base_addr, &symbol_displacement, symbol); + + // NOTE: Construct result + + DN_Str16 file_name16 = DN_Str16{line.FileName, DN_CStr16Size(line.FileName)}; + DN_Str16 function_name16 = DN_Str16{symbol->Name, symbol->NameLen}; + + DN_StackTraceFrame result = {}; + result.address = raw_frame.base_addr; + result.line_number = line.LineNumber; + result.file_name = DN_W32_Str16ToStr8(arena, file_name16); + result.function_name = DN_W32_Str16ToStr8(arena, function_name16); + + if (result.function_name.size == 0) + result.function_name = DN_Str8Lit(""); + if (result.file_name.size == 0) + result.file_name = DN_Str8Lit(""); +#else + DN_StackTraceFrame result = {}; +#endif + return result; +} + +DN_API void DN_StackTracePrint(uint16_t limit) +{ + DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr); + DN_Slice stack_trace = DN_StackTraceGetFrames(tmem.arena, limit); + for (DN_StackTraceFrame &frame : stack_trace) + DN_OS_PrintErrLnF("%.*s(%I64u): %.*s", DN_Str8PrintFmt(frame.file_name), frame.line_number, DN_Str8PrintFmt(frame.function_name)); +} + +DN_API void DN_StackTraceReloadSymbols() +{ +#if defined(DN_OS_WIN32) + HANDLE process = GetCurrentProcess(); + SymRefreshModuleList(process); +#endif +} diff --git a/Source/OS/dn_os_w32.cpp b/Source/OS/dn_os_w32.cpp index 12246ed..e56f707 100644 --- a/Source/OS/dn_os_w32.cpp +++ b/Source/OS/dn_os_w32.cpp @@ -51,9 +51,9 @@ DN_API void *DN_OS_MemReserve(DN_USize size, DN_MemCommit commit, DN_U32 page_fl void *result = VirtualAlloc(nullptr, size, flags, os_page_flags); if (flags & MEM_COMMIT) { - DN_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_frame, 1); } return result; } @@ -65,9 +65,9 @@ DN_API bool DN_OS_MemCommit(void *ptr, DN_USize size, DN_U32 page_flags) return false; unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); result = VirtualAlloc(ptr, size, MEM_COMMIT, os_page_flags) != nullptr; - DN_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->vmem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.vmem_allocs_frame, 1); return result; } @@ -95,8 +95,8 @@ DN_API int DN_OS_MemProtect(void *ptr, DN_USize size, DN_U32 page_flags) static DN_Str8 const ALIGNMENT_ERROR_MSG = DN_Str8Lit("Page protection requires pointers to be page aligned because we can only guard memory at a multiple of the page boundary."); - DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); - DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_os_core_->page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(DN_Cast(uintptr_t) ptr, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); + DN_AssertF(DN_IsPowerOfTwoAligned(size, g_dn_->os.page_size), "%s", ALIGNMENT_ERROR_MSG.data); unsigned long os_page_flags = DN_OS_MemConvertPageToOSFlags_(page_flags); unsigned long prev_flags = 0; @@ -113,9 +113,9 @@ DN_API void *DN_OS_MemAlloc(DN_USize size, DN_ZMem z_mem) 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_Assert(g_dn_os_core_); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_total, 1); - DN_AtomicAddU64(&g_dn_os_core_->mem_allocs_frame, 1); + DN_Assert(g_dn_); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_total, 1); + DN_AtomicAddU64(&g_dn_->os.mem_allocs_frame, 1); return result; } @@ -225,8 +225,8 @@ DN_API DN_OSDateTime DN_OS_DateUnixTimeSToDate(DN_U64 time) DN_API void DN_OS_GenBytesSecure(void *buffer, DN_U32 size) { - DN_Assert(g_dn_os_core_); - DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(g_dn_); + DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_->os.platform_context; DN_Assert(w32->bcrypt_init_success); long gen_status = BCryptGenRandom(w32->bcrypt_rng_handle, DN_Cast(unsigned char *) buffer, size, 0 /*flags*/); @@ -283,8 +283,8 @@ DN_API void DN_OS_SleepMs(DN_UInt milliseconds) DN_API DN_U64 DN_OS_PerfCounterFrequency() { - DN_Assert(g_dn_os_core_); - DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_os_core_->platform_context; + DN_Assert(g_dn_); + DN_W32Core *w32 = DN_Cast(DN_W32Core *) g_dn_->os.platform_context; DN_Assert(w32->qpc_frequency.QuadPart); DN_U64 result = w32->qpc_frequency.QuadPart; return result; @@ -1002,8 +1002,8 @@ DN_API DN_OSExecAsyncHandle DN_OS_ExecAsync(DN_Slice cmd_line, DN_OSExe static DN_W32Core *DN_OS_GetW32Core_() { - DN_Assert(g_dn_os_core_ && g_dn_os_core_->platform_context); - DN_W32Core *result = DN_Cast(DN_W32Core *)g_dn_os_core_->platform_context; + DN_Assert(g_dn_ && g_dn_->os.platform_context); + DN_W32Core *result = DN_Cast(DN_W32Core *)g_dn_->os.platform_context; return result; } @@ -1033,7 +1033,7 @@ static DN_W32SyncPrimitive *DN_W32_AllocSyncPrimitive_() w32->sync_primitive_free_list = w32->sync_primitive_free_list->next; result->next = nullptr; } else { - DN_OSCore *os = g_dn_os_core_; + DN_OSCore *os = &g_dn_->os; result = DN_ArenaNew(&os->arena, DN_W32SyncPrimitive, DN_ZMem_Yes); } } diff --git a/Source/dn_base_inc.cpp b/Source/dn_base_inc.cpp index fad8081..e0e42c3 100644 --- a/Source/dn_base_inc.cpp +++ b/Source/dn_base_inc.cpp @@ -3,3 +3,4 @@ #include "Base/dn_base.cpp" #include "Base/dn_base_containers.cpp" #include "Base/dn_base_log.cpp" +#include "Base/dn_base_leak.cpp" diff --git a/Source/dn_base_inc.h b/Source/dn_base_inc.h index d345e6b..699793c 100644 --- a/Source/dn_base_inc.h +++ b/Source/dn_base_inc.h @@ -58,5 +58,6 @@ #include "Base/dn_base_assert.h" #include "Base/dn_base_log.h" #include "Base/dn_base_containers.h" +#include "Base/dn_base_leak.h" #endif // !defined(DN_BASE_INC_H) diff --git a/Source/dn_core_inc.cpp b/Source/dn_core_inc.cpp deleted file mode 100644 index f5b5e28..0000000 --- a/Source/dn_core_inc.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#define DN_CORE_INC_CPP - -#include "Core/dn_core.cpp" -#include "Core/dn_core_debug.cpp" -#include "Core/dn_core_demo.cpp" diff --git a/Source/dn_core_inc.h b/Source/dn_core_inc.h deleted file mode 100644 index f5aeaef..0000000 --- a/Source/dn_core_inc.h +++ /dev/null @@ -1,7 +0,0 @@ -#if !defined(DN_CORE_INC_H) -#define DN_CORE_INC_H - -#include "Core/dn_core_debug.h" -#include "Core/dn_core.h" - -#endif // !defined(DN_CORE_INC_H) diff --git a/Source/dn_inc.cpp b/Source/dn_inc.cpp new file mode 100644 index 0000000..4be9803 --- /dev/null +++ b/Source/dn_inc.cpp @@ -0,0 +1,180 @@ +#define DN_INC_CPP + +#include "dn_inc.h" + +DN_Core *g_dn_; + +static void DN_InitOS_(DN_OSCore *os, DN_InitArgs *args) +{ + #if defined(DN_OS_H) && defined(DN_OS_CPP) + // NOTE: OS + { + #if defined(DN_PLATFORM_WIN32) + SYSTEM_INFO system_info = {}; + GetSystemInfo(&system_info); + + os->logical_processor_count = system_info.dwNumberOfProcessors; + os->page_size = system_info.dwPageSize; + os->alloc_granularity = system_info.dwAllocationGranularity; + #else + #if defined(DN_PLATFORM_EMSCRIPTEN) + os->logical_processor_count = 1; + #else + os->logical_processor_count = get_nprocs(); + #endif + os->page_size = getpagesize(); + os->alloc_granularity = os->page_size; + #endif + } + + // NOTE: Setup logging + DN_OS_EmitLogsWithOSPrintFunctions(os); + + { + #if defined(DN_PLATFORM_EMSCRIPTEN) + os->arena = DN_ArenaFromHeap(DN_Megabytes(1), DN_ArenaFlags_NoAllocTrack); + #else + os->arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(4), DN_ArenaFlags_NoAllocTrack); + #endif + + #if defined(DN_PLATFORM_WIN32) + os->platform_context = DN_ArenaNew(&os->arena, DN_W32Core, DN_ZMem_Yes); + #elif defined(DN_PLATFORM_POSIX) || defined(DN_PLATFORM_EMSCRIPTEN) + os->platform_context = DN_ArenaNew(&os->arena, DN_POSIXCore, DN_ZMem_Yes); + #endif + + #if defined(DN_PLATFORM_WIN32) + DN_W32Core *w32 = DN_Cast(DN_W32Core *) os->platform_context; + InitializeCriticalSection(&w32->sync_primitive_free_list_mutex); + + QueryPerformanceFrequency(&w32->qpc_frequency); + HMODULE module = LoadLibraryA("kernel32.dll"); + if (module) { + w32->set_thread_description = DN_Cast(DN_W32SetThreadDescriptionFunc *) GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } + + // NOTE: win32 bcrypt + wchar_t const BCRYPT_ALGORITHM[] = L"RNG"; + long /*NTSTATUS*/ init_status = BCryptOpenAlgorithmProvider(&w32->bcrypt_rng_handle, BCRYPT_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); + if (w32->bcrypt_rng_handle && init_status == 0) + w32->bcrypt_init_success = true; + else + DN_LOG_ErrorF("Failed to initialise Windows secure random number generator, error: %d", init_status); + #else + DN_Posix_Init(DN_Cast(DN_POSIXCore *)os->platform_context); + #endif + } + + // NOTE: Initialise tmem arenas which allocate memory and will be + // recorded to the now initialised allocation table. The initialisation + // of tmem memory may request tmem memory itself in leak tracing mode. + // This is supported as the tmem arenas defer allocation tracking until + // initialisation is done. + DN_OSTLSInitArgs tls_init_args = {}; + if (args) { + tls_init_args.commit = args->os_tls_commit; + tls_init_args.reserve = args->os_tls_reserve; + tls_init_args.err_sink_reserve = args->os_tls_err_sink_reserve; + tls_init_args.err_sink_commit = args->os_tls_err_sink_commit; + } + + DN_OS_TLSInit(&os->tls, tls_init_args); + DN_OS_TLSSetCurrentThreadTLS(&os->tls); + os->cpu_report = DN_CPUGetReport(); + + #define DN_CPU_FEAT_XENTRY(label) g_dn_cpu_feature_decl[DN_CPUFeature_##label] = {DN_CPUFeature_##label, DN_Str8Lit(#label)}; + DN_CPU_FEAT_XMACRO + #undef DN_CPU_FEAT_XENTRY + DN_Assert(g_dn_); + #endif // defined(DN_OS_H) && defined(DN_OS_CPP) +} + + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args) +{ + g_dn_ = dn; + + #if defined(DN_OS_H) && defined(DN_OS_CPP) + DN_InitOS_(&dn->os, args); + #endif + + // NOTE Initialise fields + #if defined(DN_LEAK_TRACKING) + // NOTE: Setup the allocation table with allocation tracking turned off on + // the arena we're using to initialise the table. + core->alloc_table_arena = DN_ArenaFromVMem(DN_Megabytes(1), DN_Kilobytes(512), DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak); + core->alloc_table = DN_DSMap_Init(&core->alloc_table_arena, 4096, DN_DSMapFlags_Nil); + #endif + + // NOTE: Print out init features + DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr); + DN_Str8Builder builder = DN_Str8BuilderFromArena(tmem.arena); + if (flags & DN_InitFlags_LogLibFeatures) { + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit("DN initialised:\n")); + #if defined(DN_OS_CPP) + DN_F32 page_size_kib = dn->os.page_size / 1024.0f; + DN_F32 alloc_granularity_kib = dn->os.alloc_granularity / 1024.0f; + DN_Str8BuilderAppendF(&builder, + " OS Page Size/Alloc Granularity: %.1f/%.1fKiB\n" + " Logical Processor Count: %u\n", + page_size_kib, + alloc_granularity_kib, + dn->os.logical_processor_count); + #endif + + #if DN_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + if (DN_ASAN_POISON) { + DN_Str8BuilderAppendF( + &builder, " ASAN manual poisoning%s\n", DN_ASAN_VET_POISON ? " (+vet sanity checks)" : ""); + DN_Str8BuilderAppendF(&builder, " ASAN poison guard size: %u\n", DN_ASAN_POISON_GUARD_SIZE); + } + #endif + + #if defined(DN_LEAK_TRACKING) + DN_Str8BuilderAppendRef(&builder, DN_Str8Lit(" Allocation leak tracing\n")); + #endif + + #if defined(DN_PLATFORM_EMSCRIPTEN) || defined(DN_PLATFORM_POSIX) + DN_POSIXCore *posix = DN_Cast(DN_POSIXCore *)g_dn_->os.platform_context; + DN_Str8BuilderAppendF(&builder, " Clock GetTime: %S\n", posix->clock_monotonic_raw ? DN_Str8Lit("CLOCK_MONOTONIC_RAW") : DN_Str8Lit("CLOCK_MONOTONIC")); + #endif + // TODO(doyle): Add stacktrace feature log + } + + if (flags & DN_InitFlags_LogCPUFeatures) { + DN_CPUReport const *report = &dn->os.cpu_report; + DN_Str8 brand = DN_Str8TrimWhitespaceAround(DN_Str8FromPtr(report->brand, sizeof(report->brand) - 1)); + DN_MSVC_WARNING_PUSH + DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(3) when a string is required in call to 'DN_Str8BuilderAppendF' Actual type: 'struct DN_Str8'. + DN_Str8BuilderAppendF(&builder, " CPU '%S' from '%s' detected:\n", brand, report->vendor); + DN_MSVC_WARNING_POP + + DN_USize longest_feature_name = 0; + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + longest_feature_name = DN_Max(longest_feature_name, feature_decl.label.size); + } + + for (DN_ForIndexU(feature_index, DN_CPUFeature_Count)) { + DN_CPUFeatureDecl feature_decl = g_dn_cpu_feature_decl[feature_index]; + bool has_feature = DN_CPUHasFeature(report, feature_decl.value); + DN_Str8BuilderAppendF(&builder, + " %.*s:%*s%s\n", + DN_Str8PrintFmt(feature_decl.label), + DN_Cast(int)(longest_feature_name - feature_decl.label.size), + "", + has_feature ? "available" : "not available"); + } + } + + DN_Str8 info_log = DN_Str8BuilderBuild(&builder, tmem.arena); + if (info_log.size) + DN_LOG_DebugF("%.*s", DN_Str8PrintFmt(info_log)); + +} + +DN_API void DN_BeginFrame() +{ + DN_AtomicSetValue64(&g_dn_->os.mem_allocs_frame, 0); +} diff --git a/Source/dn_inc.h b/Source/dn_inc.h new file mode 100644 index 0000000..785cad9 --- /dev/null +++ b/Source/dn_inc.h @@ -0,0 +1,36 @@ +#if !defined(DN_INC_H) +#define DN_INC_H + +struct DN_Core +{ + DN_USize mem_allocs_frame; + DN_LeakTracker leak; + #if defined(DN_OS_H) + DN_OSCore os; + #endif +}; + +struct DN_InitArgs +{ + DN_U64 os_tls_reserve; + DN_U64 os_tls_commit; + DN_U64 os_tls_err_sink_reserve; + DN_U64 os_tls_err_sink_commit; +}; + +typedef DN_USize DN_InitFlags; +enum DN_InitFlags_ +{ + DN_InitFlags_Nil = 0, + DN_InitFlags_OS = 1 << 0, + DN_InitFlags_LogLibFeatures = 1 << 1, + DN_InitFlags_LogCPUFeatures = 1 << 2, + DN_InitFlags_LogAllFeatures = DN_InitFlags_LogLibFeatures | DN_InitFlags_LogCPUFeatures, +}; + +extern DN_Core *g_dn_; + +DN_API void DN_Init(DN_Core *dn, DN_InitFlags flags, DN_InitArgs *args); +DN_API void DN_BeginFrame(); + +#endif // !defined(DN_INC_H) diff --git a/Source/dn_os_inc.cpp b/Source/dn_os_inc.cpp index a34b381..422e1da 100644 --- a/Source/dn_os_inc.cpp +++ b/Source/dn_os_inc.cpp @@ -14,3 +14,5 @@ #else #error Please define a platform e.g. 'DN_PLATFORM_WIN32' to enable the correct implementation for platform APIs #endif + +#include "OS/dn_os_stacktrace.cpp" diff --git a/single_header_generator.cpp b/single_header_generator.cpp index 3414a65..28a44b1 100644 --- a/single_header_generator.cpp +++ b/single_header_generator.cpp @@ -5,7 +5,7 @@ #else #include "Source/dn_base_inc.h" #include "Source/dn_os_inc.h" -#include "Source/dn_core_inc.h" +#include "Source/dn_inc.h" #endif #if USE_SINGLE_HEADER @@ -13,7 +13,7 @@ #else #include "Source/dn_base_inc.cpp" #include "Source/dn_os_inc.cpp" -#include "Source/dn_core_inc.cpp" +#include "Source/dn_inc.cpp" #endif enum FileType @@ -98,10 +98,10 @@ int main(int argc, char **argv) File const FILES[] = { {FileType_Header, DN_Str8Lit("dn_base_inc.h")}, {FileType_Header, DN_Str8Lit("dn_os_inc.h")}, - {FileType_Header, DN_Str8Lit("dn_core_inc.h")}, + {FileType_Header, DN_Str8Lit("dn_inc.h")}, {FileType_Impl, DN_Str8Lit("dn_base_inc.cpp")}, {FileType_Impl, DN_Str8Lit("dn_os_inc.cpp")}, - {FileType_Impl, DN_Str8Lit("dn_core_inc.cpp")}, + {FileType_Impl, DN_Str8Lit("dn_inc.cpp")}, }; for (DN_ForIndexU(type, FileType_Count)) {