400 lines
14 KiB
C++
400 lines
14 KiB
C++
#define DN_OS_TLSCPP
|
|
|
|
// NOTE: DN_OSTLS ////////////////////////////////////////////////////////////////////////////////////
|
|
DN_OSTLSTMem::DN_OSTLSTMem(DN_OSTLS *tls, DN_U8 arena_index, DN_OSTLSPushTMem push_tmem)
|
|
{
|
|
DN_Assert(arena_index == DN_OSTLSArena_TMem0 || arena_index == DN_OSTLSArena_TMem1);
|
|
arena = tls->arenas + arena_index;
|
|
temp_mem = DN_Arena_TempMemBegin(arena);
|
|
destructed = false;
|
|
push_arena = push_tmem;
|
|
if (push_arena)
|
|
DN_OS_TLSPushArena(arena);
|
|
}
|
|
|
|
DN_OSTLSTMem::~DN_OSTLSTMem()
|
|
{
|
|
DN_Assert(destructed == false);
|
|
DN_Arena_TempMemEnd(temp_mem);
|
|
destructed = true;
|
|
if (push_arena)
|
|
DN_OS_TLSPopArena();
|
|
}
|
|
|
|
DN_API void DN_OS_TLSInit(DN_OSTLS *tls, DN_OSTLSInitArgs args)
|
|
{
|
|
DN_Check(tls);
|
|
if (tls->init)
|
|
return;
|
|
|
|
DN_U64 reserve = args.reserve ? args.reserve : DN_Kilobytes(64);
|
|
DN_U64 commit = args.commit ? args.commit : DN_Kilobytes(4);
|
|
DN_U64 err_sink_reserve = args.err_sink_reserve ? args.err_sink_reserve : DN_Kilobytes(64);
|
|
DN_U64 err_sink_commit = args.err_sink_commit ? args.err_sink_commit : DN_Kilobytes(4);
|
|
|
|
// TODO: We shouldn't have the no alloc track flag here but the initial TLS
|
|
// init on OS init happens before CORE init. CORE init is the one responsible
|
|
// for setting up the alloc tracking data structures.
|
|
DN_ForIndexU(index, DN_OSTLSArena_Count) {
|
|
DN_Arena *arena = tls->arenas + index;
|
|
switch (DN_CAST(DN_OSTLSArena) index) {
|
|
default: *arena = DN_Arena_InitFromOSVMem(reserve, commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break;
|
|
case DN_OSTLSArena_ErrorSink: *arena = DN_Arena_InitFromOSVMem(err_sink_reserve, err_sink_commit, DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_NoAllocTrack); break;
|
|
case DN_OSTLSArena_Count: DN_InvalidCodePath; break;
|
|
}
|
|
}
|
|
|
|
tls->thread_id = DN_OS_ThreadID();
|
|
tls->err_sink.arena = tls->arenas + DN_OSTLSArena_ErrorSink;
|
|
tls->init = true;
|
|
}
|
|
|
|
DN_API void DN_OS_TLSDeinit(DN_OSTLS *tls)
|
|
{
|
|
tls->init = false;
|
|
tls->err_sink = {};
|
|
tls->arena_stack_index = {};
|
|
DN_ForIndexU(index, DN_OSTLSArena_Count) {
|
|
DN_Arena *arena = tls->arenas + index;
|
|
DN_Arena_Deinit(arena);
|
|
}
|
|
}
|
|
|
|
DN_THREAD_LOCAL DN_OSTLS *g_dn_curr_thread_tls;
|
|
DN_API void DN_OS_TLSSetCurrentThreadTLS(DN_OSTLS *tls)
|
|
{
|
|
g_dn_curr_thread_tls = tls;
|
|
}
|
|
|
|
DN_API DN_OSTLS *DN_OS_TLSGet()
|
|
{
|
|
DN_Assert(g_dn_curr_thread_tls &&
|
|
"DN must be initialised (via DN_Core_Init) before calling any functions depending on "
|
|
"TLS if this is the main thread, OR, the created thread has not called "
|
|
"SetCurrentThreadTLS yet so the TLS data structure hasn't been assigned yet");
|
|
return g_dn_curr_thread_tls;
|
|
}
|
|
|
|
DN_API DN_Arena *DN_OS_TLSArena()
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_Arena *result = tls->arenas + DN_OSTLSArena_Main;
|
|
return result;
|
|
}
|
|
|
|
// TODO: Is there a way to handle conflict arenas without the user needing to
|
|
// manually pass it in?
|
|
DN_API DN_OSTLSTMem DN_OS_TLSGetTMem(void const *conflict_arena, DN_OSTLSPushTMem push_tmem)
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_U8 tls_index = (DN_U8)-1;
|
|
for (DN_U8 index = DN_OSTLSArena_TMem0; index <= DN_OSTLSArena_TMem1; index++) {
|
|
DN_Arena *arena = tls->arenas + index;
|
|
if (!conflict_arena || arena != conflict_arena) {
|
|
tls_index = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DN_Assert(tls_index != (DN_U8)-1);
|
|
return DN_OSTLSTMem(tls, tls_index, push_tmem);
|
|
}
|
|
|
|
DN_API void DN_OS_TLSPushArena(DN_Arena *arena)
|
|
{
|
|
DN_Assert(arena);
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_Assert(tls->arena_stack_index < DN_ArrayCountU(tls->arena_stack));
|
|
tls->arena_stack[tls->arena_stack_index++] = arena;
|
|
}
|
|
|
|
DN_API void DN_OS_TLSPopArena()
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_Assert(tls->arena_stack_index > 0);
|
|
tls->arena_stack_index--;
|
|
}
|
|
|
|
DN_API DN_Arena *DN_OS_TLSTopArena()
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_Arena *result = nullptr;
|
|
if (tls->arena_stack_index)
|
|
result = tls->arena_stack[tls->arena_stack_index - 1];
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_TLSBeginFrame(DN_Arena *frame_arena)
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
tls->frame_arena = frame_arena;
|
|
}
|
|
|
|
DN_API DN_Arena *DN_OS_TLSFrameArena()
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_Arena *result = tls->frame_arena;
|
|
DN_AssertF(result, "Frame arena must be set by calling DN_OS_TLSBeginFrame at the beginning of the frame");
|
|
return result;
|
|
}
|
|
|
|
// NOTE: DN_OSErrSink ////////////////////////////////////////////////////////////////////////////////
|
|
static void DN_OS_ErrSinkCheck_(DN_OSErrSink const *err)
|
|
{
|
|
DN_AssertF(err->arena, "Arena should be assigned in TLS init");
|
|
if (err->stack_size == 0)
|
|
return;
|
|
|
|
DN_OSErrSinkNode const *node = err->stack + (err->stack_size - 1);
|
|
DN_Assert(node->mode >= DN_OSErrSinkMode_Nil && node->mode <= DN_OSErrSinkMode_ExitOnError);
|
|
DN_Assert(node->msg_sentinel);
|
|
|
|
// NOTE: Walk the list ensuring we eventually terminate at the sentinel (e.g. we have a
|
|
// well formed doubly-linked-list terminated by a sentinel, or otherwise we will hit the
|
|
// walk limit or dereference a null pointer and assert)
|
|
size_t WALK_LIMIT = 99'999;
|
|
size_t walk = 0;
|
|
for (DN_OSErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next, walk++) {
|
|
DN_AssertF(it, "Encountered null pointer which should not happen in a sentinel DLL");
|
|
DN_Assert(walk < WALK_LIMIT);
|
|
}
|
|
}
|
|
|
|
DN_API DN_OSErrSink *DN_OS_ErrSinkBegin_(DN_OSErrSinkMode mode, DN_CallSite call_site)
|
|
{
|
|
DN_OSTLS *tls = DN_OS_TLSGet();
|
|
DN_OSErrSink *err = &tls->err_sink;
|
|
DN_OSErrSink *result = err;
|
|
DN_USize arena_pos = DN_Arena_Pos(result->arena);
|
|
|
|
if (tls->err_sink.stack_size == DN_ArrayCountU(err->stack)) {
|
|
DN_Str8Builder builder = DN_Str8Builder_InitFromTLS();
|
|
DN_USize counter = 0;
|
|
DN_ForItSize(it, DN_OSErrSinkNode, err->stack, err->stack_size) {
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(4) when a string is required in call to 'DN_Str8Builder_AppendF' Actual type: 'struct DN_Str8'.
|
|
DN_Str8Builder_AppendF(&builder, " [%04zu] %S:%u %S\n", counter++, it.data->call_site.file, it.data->call_site.line, it.data->call_site.function);
|
|
DN_MSVC_WARNING_POP
|
|
}
|
|
|
|
DN_MSVC_WARNING_PUSH
|
|
DN_MSVC_WARNING_DISABLE(6284) // Object passed as _Param_(6) when a string is required in call to 'DN_LOG_EmitFromType' Actual type: 'struct DN_Str8'.
|
|
DN_AssertF(tls->err_sink.stack_size < DN_ArrayCountU(err->stack),
|
|
"Error sink has run out of error scopes, potential leak. Scopes were\n%S", DN_Str8Builder_BuildFromTLS(&builder));
|
|
DN_MSVC_WARNING_POP
|
|
}
|
|
|
|
DN_OSErrSinkNode *node = tls->err_sink.stack + tls->err_sink.stack_size++;
|
|
node->arena_pos = arena_pos;
|
|
node->mode = mode;
|
|
node->call_site = call_site;
|
|
DN_DLList_InitArena(node->msg_sentinel, DN_OSErrSinkMsg, result->arena);
|
|
|
|
// NOTE: Handle allocation error
|
|
if (!DN_Check(node && node->msg_sentinel)) {
|
|
DN_Arena_PopTo(result->arena, arena_pos);
|
|
node->msg_sentinel = nullptr;
|
|
tls->err_sink.stack_size--;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_ErrSinkHasError(DN_OSErrSink *err)
|
|
{
|
|
bool result = false;
|
|
if (err && err->stack_size) {
|
|
DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
result = DN_DLList_HasItems(node->msg_sentinel);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
DN_API DN_OSErrSinkMsg *DN_OS_ErrSinkEnd(DN_Arena *arena, DN_OSErrSink *err)
|
|
{
|
|
DN_OSErrSinkMsg *result = nullptr;
|
|
DN_OS_ErrSinkCheck_(err);
|
|
if (!err || err->stack_size == 0)
|
|
return result;
|
|
|
|
DN_AssertF(arena != err->arena,
|
|
"You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost");
|
|
// NOTE: Walk the list and allocate it onto the user's arena
|
|
DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_OSErrSinkMsg *prev = nullptr;
|
|
for (DN_OSErrSinkMsg *it = node->msg_sentinel->next; it != node->msg_sentinel; it = it->next) {
|
|
DN_OSErrSinkMsg *entry = DN_Arena_New(arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes);
|
|
entry->msg = DN_Str8_Copy(arena, it->msg);
|
|
entry->call_site = it->call_site;
|
|
entry->error_code = it->error_code;
|
|
if (!result)
|
|
result = entry; // Assign first entry if we haven't yet
|
|
if (prev)
|
|
prev->next = entry; // Link the prev message to the current one
|
|
prev = entry; // Update prev to latest
|
|
}
|
|
|
|
// NOTE: Deallocate all the memory for this scope
|
|
err->stack_size--;
|
|
DN_Arena_PopTo(err->arena, node->arena_pos);
|
|
return result;
|
|
}
|
|
|
|
static void DN_OS_ErrSinkAddMsgToStr8Builder_(DN_Str8Builder *builder, DN_OSErrSinkMsg *msg, DN_OSErrSinkMsg *end)
|
|
{
|
|
if (msg == end) // NOTE: No error messages to add
|
|
return;
|
|
|
|
if (msg->next == end) {
|
|
DN_OSErrSinkMsg *it = msg;
|
|
DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file);
|
|
DN_Str8Builder_AppendF(builder,
|
|
"%.*s:%05I32u:%.*s %.*s",
|
|
DN_STR_FMT(file_name),
|
|
it->call_site.line,
|
|
DN_STR_FMT(it->call_site.function),
|
|
DN_STR_FMT(it->msg));
|
|
} else {
|
|
// NOTE: More than one message
|
|
for (DN_OSErrSinkMsg *it = msg; it != end; it = it->next) {
|
|
DN_Str8 file_name = DN_Str8_FileNameFromPath(it->call_site.file);
|
|
DN_Str8Builder_AppendF(builder,
|
|
"%s - %.*s:%05I32u:%.*s%s%.*s",
|
|
it == msg ? "" : "\n",
|
|
DN_STR_FMT(file_name),
|
|
it->call_site.line,
|
|
DN_STR_FMT(it->call_site.function),
|
|
DN_Str8_HasData(it->msg) ? " " : "",
|
|
DN_STR_FMT(it->msg));
|
|
}
|
|
}
|
|
}
|
|
|
|
DN_API DN_Str8 DN_OS_ErrSinkEndStr8(DN_Arena *arena, DN_OSErrSink *err)
|
|
{
|
|
DN_Str8 result = {};
|
|
DN_OS_ErrSinkCheck_(err);
|
|
if (!err || err->stack_size == 0)
|
|
return result;
|
|
|
|
DN_AssertF(arena != err->arena,
|
|
"You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost");
|
|
|
|
// NOTE: Walk the list and allocate it onto the user's arena
|
|
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(arena);
|
|
DN_Str8Builder builder = DN_Str8Builder_InitFromTLS();
|
|
DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, node->msg_sentinel->next, node->msg_sentinel);
|
|
|
|
// NOTE: Deallocate all the memory for this scope
|
|
err->stack_size--;
|
|
DN_U64 arena_pos = node->arena_pos;
|
|
DN_Arena_PopTo(err->arena, arena_pos);
|
|
|
|
result = DN_Str8Builder_Build(&builder, arena);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_ErrSinkEndAndIgnore(DN_OSErrSink *err)
|
|
{
|
|
DN_OS_ErrSinkEnd(nullptr, err);
|
|
}
|
|
|
|
DN_API bool DN_OS_ErrSinkEndAndLogError_(DN_OSErrSink *err, DN_CallSite call_site, DN_Str8 err_msg)
|
|
{
|
|
DN_AssertF(err->stack_size, "Begin must be called before calling end");
|
|
DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_AssertF(node->msg_sentinel, "Begin must be called before calling end");
|
|
|
|
DN_OSTLSTMem tmem = DN_OS_TLSPushTMem(nullptr);
|
|
DN_OSErrSinkMode mode = node->mode;
|
|
DN_OSErrSinkMsg *msg = DN_OS_ErrSinkEnd(tmem.arena, err);
|
|
if (!msg)
|
|
return false;
|
|
|
|
DN_Str8Builder builder = DN_Str8Builder_InitFromTLS();
|
|
if (DN_Str8_HasData(err_msg)) {
|
|
DN_Str8Builder_AppendRef(&builder, err_msg);
|
|
DN_Str8Builder_AppendRef(&builder, DN_STR8(":"));
|
|
} else {
|
|
DN_Str8Builder_AppendRef(&builder, DN_STR8("Error(s) encountered:"));
|
|
}
|
|
|
|
if (msg->next) // NOTE: More than 1 message
|
|
DN_Str8Builder_AppendRef(&builder, DN_STR8("\n"));
|
|
DN_OS_ErrSinkAddMsgToStr8Builder_(&builder, msg, nullptr);
|
|
|
|
DN_Str8 log = DN_Str8Builder_BuildFromTLS(&builder);
|
|
DN_LOG_EmitFromType(DN_LOG_MakeU32LogTypeParam(DN_LOGType_Error), call_site, "%.*s", DN_STR_FMT(log));
|
|
|
|
if (mode == DN_OSErrSinkMode_DebugBreakOnEndAndLog)
|
|
DN_DebugBreak;
|
|
return true;
|
|
}
|
|
|
|
DN_API bool DN_OS_ErrSinkEndAndLogErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr);
|
|
DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args);
|
|
bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log);
|
|
return result;
|
|
}
|
|
|
|
DN_API bool DN_OS_ErrSinkEndAndLogErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OSTLSTMem tmem = DN_OS_TLSTMem(nullptr);
|
|
DN_Str8 log = DN_Str8_InitFV(tmem.arena, fmt, args);
|
|
bool result = DN_OS_ErrSinkEndAndLogError_(err, call_site, log);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
DN_API void DN_OS_ErrSinkEndAndExitIfErrorFV_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
if (DN_OS_ErrSinkEndAndLogErrorFV_(err, call_site, fmt, args)) {
|
|
DN_DebugBreak;
|
|
DN_OS_Exit(exit_val);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_ErrSinkEndAndExitIfErrorF_(DN_OSErrSink *err, DN_CallSite call_site, DN_U32 exit_val, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_ErrSinkEndAndExitIfErrorFV_(err, call_site, exit_val, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
DN_API void DN_OS_ErrSinkAppendFV_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, va_list args)
|
|
{
|
|
if (!err)
|
|
return;
|
|
DN_Assert(err && err->stack_size);
|
|
DN_OSErrSinkNode *node = err->stack + (err->stack_size - 1);
|
|
DN_AssertF(node, "Error sink must be begun by calling 'Begin' before using this function.");
|
|
|
|
DN_OSErrSinkMsg *msg = DN_Arena_New(err->arena, DN_OSErrSinkMsg, DN_ZeroMem_Yes);
|
|
if (DN_Check(msg)) {
|
|
msg->msg = DN_Str8_InitFV(err->arena, fmt, args);
|
|
msg->error_code = error_code;
|
|
msg->call_site = DN_OS_TLSGet()->call_site;
|
|
DN_DLList_Prepend(node->msg_sentinel, msg);
|
|
|
|
if (node->mode == DN_OSErrSinkMode_ExitOnError)
|
|
DN_OS_ErrSinkEndAndExitIfErrorF_(err, msg->call_site, error_code, "Fatal error %u", error_code);
|
|
}
|
|
}
|
|
|
|
DN_API void DN_OS_ErrSinkAppendF_(DN_OSErrSink *err, DN_U32 error_code, DN_FMT_ATTRIB char const *fmt, ...)
|
|
{
|
|
if (!err)
|
|
return;
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
DN_OS_ErrSinkAppendFV_(err, error_code, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|