Dqn/OS/dn_os_tls.cpp
2025-05-12 17:09:03 +10:00

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);
}