Remove core dn layer
This commit is contained in:
+169
-3
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<DN_LeakAlloc> *alloc_table = &leak->alloc_table;
|
||||
DN_DSMapResult<DN_LeakAlloc> 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<DN_LeakAlloc> *alloc_table = &leak->alloc_table;
|
||||
DN_DSMapResult<DN_LeakAlloc> 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<DN_LeakAlloc> *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));
|
||||
}
|
||||
}
|
||||
@@ -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<DN_LeakAlloc> 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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
#define DN_BASE_MEM_CPP
|
||||
|
||||
#include "../dn_base_inc.h"
|
||||
|
||||
@@ -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)
|
||||
+19
-19
@@ -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_StackTraceFrame> 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_StackTraceFrame> 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)
|
||||
|
||||
Reference in New Issue
Block a user