unify async task kickoff/await mechanism in first pass of 'task system' layer; use in organizing various independent parsing passes of a pdb; also fix busted multithreaded fwd resolution pass

This commit is contained in:
Ryan Fleury
2024-02-20 11:16:54 -08:00
parent 10a0420264
commit 6b2e4abe81
7 changed files with 377 additions and 93 deletions
+1
View File
@@ -347,6 +347,7 @@ entry_point(int argc, char **argv)
//- rjf: initialize basic dependencies
os_init(argc, argv);
ts_init();
//- rjf: parse command line arguments
CmdLine cmdln = cmd_line_from_string_list(scratch.arena, os_get_command_line_arguments());
+2
View File
@@ -13,6 +13,7 @@
//- rjf: [h]
#include "base/base_inc.h"
#include "os/os_inc.h"
#include "task_system/task_system.h"
#include "raddbgi_make_local/raddbgi_make_local.h"
#include "mdesk/mdesk.h"
#include "hash_store/hash_store.h"
@@ -50,6 +51,7 @@
//- rjf: [c]
#include "base/base_inc.c"
#include "os/os_inc.c"
#include "task_system/task_system.c"
#include "raddbgi_make_local/raddbgi_make_local.c"
#include "mdesk/mdesk.c"
#include "hash_store/hash_store.c"
+66 -86
View File
@@ -540,30 +540,42 @@ p2r_location_over_lvar_addr_range(Arena *arena, RDIM_ScopeChunkList *scopes, RDI
}
////////////////////////////////
//~ rjf: Initial PDB Parsing Pass Threads
//~ rjf: Initial Parsing & Preparation Pass Tasks
internal void
p2r_tpi_hash_parse_thread__entry_point(void *p)
internal void *
p2r_exe_hash_task__entry_point(Arena *arena, void *p)
{
ThreadName("[p2r] tpi hash parse thread");
P2R_TPIHashParseTask *task = (P2R_TPIHashParseTask *)p;
task->out = pdb_tpi_hash_from_data(task->out_arena, task->in.strtbl, task->in.tpi, task->in.hash_data, task->in.aux_data);
P2R_EXEHashIn *in = (P2R_EXEHashIn *)p;
U64 *out = push_array(arena, U64, 1);
ProfScope("hash exe") *out = rdi_hash(in->exe_data.str, in->exe_data.size);
return out;
}
internal void
p2r_tpi_leaf_parse_thread__entry_point(void *p)
internal void *
p2r_tpi_hash_parse_task__entry_point(Arena *arena, void *p)
{
ThreadName("[p2r] tpi leaf parse thread");
P2R_TPILeafParseTask *task = (P2R_TPILeafParseTask *)p;
task->out = cv_leaf_from_data(task->out_arena, task->in.leaf_data, task->in.itype_first);
P2R_TPIHashParseIn *in = (P2R_TPIHashParseIn *)p;
void *out = 0;
ProfScope("parse tpi hash") out = pdb_tpi_hash_from_data(arena, in->strtbl, in->tpi, in->hash_data, in->aux_data);
return out;
}
internal void
p2r_exe_hash_thread__entry_point(void *p)
internal void *
p2r_tpi_leaf_parse_task__entry_point(Arena *arena, void *p)
{
ThreadName("[p2r] exe hash thread");
P2R_EXEHashTask *task = (P2R_EXEHashTask *)p;
ProfScope("hash exe") task->out = rdi_hash(task->in.exe_data.str, task->in.exe_data.size);
P2R_TPILeafParseIn *in = (P2R_TPILeafParseIn *)p;
void *out = 0;
ProfScope("parse tpi leaf") out = cv_leaf_from_data(arena, in->leaf_data, in->itype_first);
return out;
}
internal void *
p2r_symbol_stream_parse_task__entry_point(Arena *arena, void *p)
{
P2R_SymbolStreamParseIn *in = (P2R_SymbolStreamParseIn *)p;
void *out = 0;
ProfScope("parse symbol stream") out = cv_sym_from_data(arena, in->data, 4);
return out;
}
////////////////////////////////
@@ -684,7 +696,7 @@ p2r_itype_fwd_map_fill(P2R_ITypeFwdMapFillIn *in)
//- rjf: if the forwarded itype is nonzero & in TPI range -> save to map
if(itype_fwd != 0 && itype_fwd < in->tpi_leaf->itype_opl)
{
in->itype_fwd_map[itype-in->itype_first] = itype_fwd;
in->itype_fwd_map[itype] = itype_fwd;
}
}
}
@@ -1557,79 +1569,46 @@ p2r_convert(Arena *arena, P2R_ConvertIn *in)
CV_LeafParsed *ipi_leaf = 0;
ProfScope("do independent parsing & preparation passess")
{
//- rjf: kick off exe hash
OS_Handle exe_hash_thread = {0};
P2R_EXEHashTask exe_hash_task = {0};
//- rjf: form task inputs
P2R_EXEHashIn exe_hash_in = {in->input_exe_data};
P2R_TPIHashParseIn tpi_hash_in = {0};
{
exe_hash_task.in.exe_data = in->input_exe_data;
exe_hash_thread = os_launch_thread(p2r_exe_hash_thread__entry_point, &exe_hash_task, 0);
tpi_hash_in.strtbl = strtbl;
tpi_hash_in.tpi = tpi;
tpi_hash_in.hash_data = msf_data_from_stream(msf, tpi->hash_sn);
tpi_hash_in.aux_data = msf_data_from_stream(msf, tpi->hash_sn_aux);
}
P2R_TPILeafParseIn tpi_leaf_in = {0};
{
tpi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(tpi);
tpi_leaf_in.itype_first = tpi->itype_first;
}
P2R_TPIHashParseIn ipi_hash_in = {0};
{
ipi_hash_in.strtbl = strtbl;
ipi_hash_in.tpi = ipi;
ipi_hash_in.hash_data = msf_data_from_stream(msf, ipi->hash_sn);
ipi_hash_in.aux_data = msf_data_from_stream(msf, ipi->hash_sn_aux);
}
P2R_TPILeafParseIn ipi_leaf_in = {0};
{
ipi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(ipi);
ipi_leaf_in.itype_first = ipi->itype_first;
}
//- rjf: kick off tpi hash parse
OS_Handle tpi_hash_thread = {0};
P2R_TPIHashParseTask tpi_hash_task = {0};
if(tpi != 0)
{
tpi_hash_task.in.strtbl = strtbl;
tpi_hash_task.in.tpi = tpi;
tpi_hash_task.in.hash_data = msf_data_from_stream(msf, tpi->hash_sn);
tpi_hash_task.in.aux_data = msf_data_from_stream(msf, tpi->hash_sn_aux);
tpi_hash_task.out_arena = arena_alloc();
tpi_hash_thread = os_launch_thread(p2r_tpi_hash_parse_thread__entry_point, &tpi_hash_task, 0);
}
//- rjf: kick off tasks
TS_Ticket exe_hash_ticket = ts_kickoff(p2r_exe_hash_task__entry_point, &exe_hash_in);
TS_Ticket tpi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, &tpi_hash_in);
TS_Ticket tpi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, &tpi_leaf_in);
TS_Ticket ipi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, &ipi_hash_in);
TS_Ticket ipi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, &ipi_leaf_in);
//- rjf: kick off tpi leaf parse
OS_Handle tpi_leaf_thread = {0};
P2R_TPILeafParseTask tpi_leaf_task = {0};
if(tpi != 0)
{
tpi_leaf_task.in.leaf_data = pdb_leaf_data_from_tpi(tpi);
tpi_leaf_task.in.itype_first = tpi->itype_first;
tpi_leaf_task.out_arena = arena_alloc();
tpi_leaf_thread = os_launch_thread(p2r_tpi_leaf_parse_thread__entry_point, &tpi_leaf_task, 0);
}
//- rjf: kick off ipi hash parse
OS_Handle ipi_hash_thread = {0};
P2R_TPIHashParseTask ipi_hash_task = {0};
if(ipi != 0)
{
ipi_hash_task.in.strtbl = strtbl;
ipi_hash_task.in.tpi = ipi;
ipi_hash_task.in.hash_data = msf_data_from_stream(msf, ipi->hash_sn);
ipi_hash_task.in.aux_data = msf_data_from_stream(msf, ipi->hash_sn_aux);
ipi_hash_task.out_arena = arena_alloc();
ipi_hash_thread = os_launch_thread(p2r_tpi_hash_parse_thread__entry_point, &ipi_hash_task, 0);
}
//- rjf: kick off ipi leaf parse
OS_Handle ipi_leaf_thread = {0};
P2R_TPILeafParseTask ipi_leaf_task = {0};
if(ipi != 0)
{
ipi_leaf_task.in.leaf_data = pdb_leaf_data_from_tpi(ipi);
ipi_leaf_task.in.itype_first = ipi->itype_first;
ipi_leaf_task.out_arena = arena_alloc();
ipi_leaf_thread = os_launch_thread(p2r_tpi_leaf_parse_thread__entry_point, &ipi_leaf_task, 0);
}
//- rjf: join all independent task threads
os_thread_wait(exe_hash_thread, max_U64);
os_thread_wait(tpi_hash_thread, max_U64);
os_thread_wait(tpi_leaf_thread, max_U64);
os_thread_wait(ipi_hash_thread, max_U64);
os_thread_wait(ipi_leaf_thread, max_U64);
//- rjf: fill/absorb exports from completed tasks
exe_hash = exe_hash_task.out;
tpi_hash = tpi_hash_task.out;
tpi_leaf = tpi_leaf_task.out;
ipi_hash = ipi_hash_task.out;
ipi_leaf = ipi_leaf_task.out;
arena_absorb(arena, tpi_hash_task.out_arena);
arena_absorb(arena, tpi_leaf_task.out_arena);
arena_absorb(arena, ipi_hash_task.out_arena);
arena_absorb(arena, ipi_leaf_task.out_arena);
//- rjf: join tasks
exe_hash = *ts_join_struct(exe_hash_ticket, max_U64, U64);
tpi_hash = ts_join_struct(tpi_hash_ticket, max_U64, PDB_TpiHashParsed);
tpi_leaf = ts_join_struct(tpi_leaf_ticket, max_U64, CV_LeafParsed);
ipi_hash = ts_join_struct(ipi_hash_ticket, max_U64, PDB_TpiHashParsed);
ipi_leaf = ts_join_struct(ipi_leaf_ticket, max_U64, CV_LeafParsed);
}
//////////////////////////////////////////////////////////////
@@ -1915,6 +1894,7 @@ p2r_convert(Arena *arena, P2R_ConvertIn *in)
task->fill_in.tpi_leaf = tpi_leaf;
task->fill_in.itype_first = task_idx*task_size_itypes;
task->fill_in.itype_opl = task->fill_in.itype_first + task_size_itypes;
task->fill_in.itype_opl = ClampTop(task->fill_in.itype_opl, itype_opl);
task->fill_in.itype_fwd_map = itype_fwd_map;
}
}
+24 -7
View File
@@ -1,8 +1,8 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef RDI_FROM_PDB_H
#define RDI_FROM_PDB_H
#ifndef RADDBGI_FROM_PDB_H
#define RADDBGI_FROM_PDB_H
////////////////////////////////
//~ rjf: Conversion Inputs/Outputs
@@ -107,6 +107,22 @@ struct P2R_EXEHashTask
U64 out;
};
//- rjf: symbol stream parsing
typedef struct P2R_SymbolStreamParseIn P2R_SymbolStreamParseIn;
struct P2R_SymbolStreamParseIn
{
String8 data;
};
typedef struct P2R_SymbolStreamParseTask P2R_SymbolStreamParseTask;
struct P2R_SymbolStreamParseTask
{
P2R_SymbolStreamParseIn in;
Arena *out_arena;
CV_SymParsed *sym;
};
////////////////////////////////
//~ rjf: Conversion Data Structure Types
@@ -233,11 +249,12 @@ internal RDI_RegisterCode p2r_reg_code_from_arch_encoded_fp_reg(RDI_Arch arch, C
internal void p2r_location_over_lvar_addr_range(Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_LocationSet *locset, RDIM_Location *location, CV_LvarAddrRange *range, COFF_SectionHeader *section, CV_LvarAddrGap *gaps, U64 gap_count);
////////////////////////////////
//~ rjf: Initial Parsing & Preparation Pass Threads
//~ rjf: Initial Parsing & Preparation Pass Tasks
internal void p2r_tpi_hash_parse_thread__entry_point(void *p);
internal void p2r_tpi_leaf_parse_thread__entry_point(void *p);
internal void p2r_exe_hash_thread__entry_point(void *p);
internal void *p2r_exe_hash_task__entry_point(Arena *arena, void *p);
internal void *p2r_tpi_hash_parse_task__entry_point(Arena *arena, void *p);
internal void *p2r_tpi_leaf_parse_task__entry_point(Arena *arena, void *p);
internal void *p2r_symbol_stream_parse_task__entry_point(Arena *arena, void *p);
////////////////////////////////
//~ rjf: Type Forward Resolution Map Build Path & Thread
@@ -256,4 +273,4 @@ internal void p2r_symbol_stream_convert_task_thread__entry_point(void *p);
internal P2R_ConvertOut *p2r_convert(Arena *arena, P2R_ConvertIn *in);
#endif // RDI_FROM_PDB_H
#endif // RADDBGI_FROM_PDB_H
@@ -8,6 +8,7 @@
//- rjf: [h]
#include "base/base_inc.h"
#include "os/os_inc.h"
#include "task_system/task_system.h"
#include "raddbgi_make_local/raddbgi_make_local.h"
#include "coff/coff.h"
#include "codeview/codeview.h"
@@ -20,6 +21,7 @@
//- rjf: [c]
#include "base/base_inc.c"
#include "os/os_inc.c"
#include "task_system/task_system.c"
#include "raddbgi_make_local/raddbgi_make_local.c"
#include "coff/coff.c"
#include "codeview/codeview.c"
@@ -48,6 +50,7 @@ main(int argc, char **argv)
//- rjf: initialize dependencies
os_init(argc, argv);
ts_init();
//- rjf: initialize state, parse command line
Arena *arena = arena_alloc();
+168
View File
@@ -0,0 +1,168 @@
////////////////////////////////
//~ rjf: Top-Level Layer Initialization
internal void
ts_init(void)
{
Arena *arena = arena_alloc();
ts_shared = push_array(arena, TS_Shared, 1);
ts_shared->arena = arena;
ts_shared->artifact_slots_count = 1024;
ts_shared->artifact_stripes_count = 64;
ts_shared->artifact_slots = push_array(arena, TS_TaskArtifactSlot, ts_shared->artifact_slots_count);
ts_shared->artifact_stripes = push_array(arena, TS_TaskArtifactStripe, ts_shared->artifact_stripes_count);
for(U64 idx = 0; idx < ts_shared->artifact_stripes_count; idx += 1)
{
ts_shared->artifact_stripes[idx].arena = arena_alloc();
ts_shared->artifact_stripes[idx].cv = os_condition_variable_alloc();
ts_shared->artifact_stripes[idx].rw_mutex = os_rw_mutex_alloc();
}
ts_shared->u2t_ring_size = KB(256);
ts_shared->u2t_ring_base = push_array_no_zero(arena, U8, ts_shared->u2t_ring_size);
ts_shared->u2t_ring_mutex = os_mutex_alloc();
ts_shared->u2t_ring_cv = os_condition_variable_alloc();
ts_shared->task_threads_count = os_logical_core_count()-1;
ts_shared->task_threads = push_array(arena, TS_TaskThread, ts_shared->task_threads_count);
for(U64 idx = 0; idx < ts_shared->task_threads_count; idx += 1)
{
ts_shared->task_threads[idx].arena = arena_alloc();
ts_shared->task_threads[idx].thread = os_launch_thread(ts_task_thread__entry_point, (void *)idx, 0);
}
}
////////////////////////////////
//~ rjf: High-Level Task Kickoff / Joining
internal TS_Ticket
ts_kickoff(TS_TaskFunctionType *entry_point, void *p)
{
// rjf: obtain number & slot/stripefor next artifact
U64 artifact_num = ins_atomic_u64_inc_eval(&ts_shared->artifact_num_gen);
U64 slot_idx = artifact_num%ts_shared->artifact_slots_count;
U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count;
TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx];
TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx];
// rjf: allocate artifact
TS_TaskArtifact *artifact = 0;
OS_MutexScopeW(stripe->rw_mutex)
{
artifact = stripe->free_artifact;
if(artifact != 0)
{
SLLStackPop(stripe->free_artifact);
}
else
{
artifact = push_array_no_zero(stripe->arena, TS_TaskArtifact, 1);
}
artifact->num = artifact_num;
artifact->task_is_done = 0;
artifact->result = 0;
}
// rjf: form ticket out of artifact info
TS_Ticket ticket = {artifact_num, (U64)artifact};
// rjf: push task info to task ring buffer
OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;)
{
U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos;
U64 available_size = ts_shared->u2t_ring_size-unconsumed_size;
if(available_size >= sizeof(entry_point) + sizeof(p) + sizeof(ticket))
{
ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &entry_point);
ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &p);
ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &ticket);
break;
}
os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64);
}
os_condition_variable_broadcast(ts_shared->u2t_ring_cv);
return ticket;
}
internal void *
ts_join(TS_Ticket ticket, U64 endt_us)
{
void *result = 0;
U64 artifact_num = ticket.u64[0];
U64 slot_idx = artifact_num%ts_shared->artifact_slots_count;
U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count;
TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx];
TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx];
TS_TaskArtifact *artifact = (TS_TaskArtifact *)ticket.u64[1];
OS_MutexScopeR(stripe->rw_mutex) for(;;)
{
B64 task_is_done = artifact->task_is_done;
if(task_is_done)
{
OS_MutexScopeRWPromote(stripe->rw_mutex)
{
result = artifact->result;
SLLStackPush(stripe->free_artifact, artifact);
}
break;
}
else
{
os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us);
}
}
return result;
}
////////////////////////////////
//~ rjf: Task Threads
internal void
ts_u2t_dequeue_task(TS_TaskFunctionType **entry_point_out, void **p_out, TS_Ticket *ticket_out)
{
OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;)
{
U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos;
if(unconsumed_size >= sizeof(*entry_point_out) + sizeof(*p_out) + sizeof(*ticket_out))
{
ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, entry_point_out);
ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, p_out);
ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, ticket_out);
break;
}
os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64);
}
os_condition_variable_broadcast(ts_shared->u2t_ring_cv);
}
internal void
ts_task_thread__entry_point(void *p)
{
U64 thread_idx = (U64)p;
ThreadName("[ts] task thread #%I64u", thread_idx+1);
TS_TaskThread *thread = &ts_shared->task_threads[thread_idx];
for(;;)
{
//- rjf: grab next task
TS_TaskFunctionType *task_function = 0;
void *task_params = 0;
TS_Ticket task_ticket = {0};
ts_u2t_dequeue_task(&task_function, &task_params, &task_ticket);
//- rjf: run task
void *task_result = task_function(thread->arena, task_params);
//- rjf: store into artifact
U64 artifact_num = task_ticket.u64[0];
U64 slot_idx = artifact_num%ts_shared->artifact_slots_count;
U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count;
TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx];
TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx];
TS_TaskArtifact *artifact = (TS_TaskArtifact *)task_ticket.u64[1];
OS_MutexScopeW(stripe->rw_mutex)
{
artifact->task_is_done = 1;
artifact->result = task_result;
}
os_condition_variable_broadcast(stripe->cv);
}
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#ifndef TASK_SYSTEM_H
#define TASK_SYSTEM_H
////////////////////////////////
//~ rjf: Task "Ticket" Type
//
// "Tickets" are opaque handles, used to refer to submitted tasks.
//
typedef struct TS_Ticket TS_Ticket;
struct TS_Ticket
{
U64 u64[2];
};
////////////////////////////////
//~ rjf: Task Request Types
typedef void *TS_TaskFunctionType(Arena *arena, void *user_data);
////////////////////////////////
//~ rjf: Task Artifact Cache Types
typedef struct TS_TaskArtifact TS_TaskArtifact;
struct TS_TaskArtifact
{
TS_TaskArtifact *next;
U64 num;
B64 task_is_done;
void *result;
};
typedef struct TS_TaskArtifactSlot TS_TaskArtifactSlot;
struct TS_TaskArtifactSlot
{
TS_TaskArtifact *first;
TS_TaskArtifact *last;
};
typedef struct TS_TaskArtifactStripe TS_TaskArtifactStripe;
struct TS_TaskArtifactStripe
{
Arena *arena;
OS_Handle cv;
OS_Handle rw_mutex;
TS_TaskArtifact *free_artifact;
};
////////////////////////////////
//~ rjf: Per-Thread State
typedef struct TS_TaskThread TS_TaskThread;
struct TS_TaskThread
{
Arena *arena;
OS_Handle thread;
};
////////////////////////////////
//~ rjf: Main Shared State
typedef struct TS_Shared TS_Shared;
struct TS_Shared
{
Arena *arena;
// rjf: task artifact cache
U64 artifact_num_gen;
U64 artifact_slots_count;
U64 artifact_stripes_count;
TS_TaskArtifactSlot *artifact_slots;
TS_TaskArtifactStripe *artifact_stripes;
// rjf: task ring buffer
U64 u2t_ring_size;
U8 *u2t_ring_base;
U64 u2t_ring_write_pos;
U64 u2t_ring_read_pos;
OS_Handle u2t_ring_mutex;
OS_Handle u2t_ring_cv;
// rjf: task threads
TS_TaskThread *task_threads;
U64 task_threads_count;
};
////////////////////////////////
//~ rjf: Globals
global TS_Shared *ts_shared = 0;
////////////////////////////////
//~ rjf: Top-Level Layer Initialization
internal void ts_init(void);
////////////////////////////////
//~ rjf: High-Level Task Kickoff / Joining
internal TS_Ticket ts_kickoff(TS_TaskFunctionType *entry_point, void *p);
internal void *ts_join(TS_Ticket ticket, U64 endt_us);
#define ts_join_struct(ticket, endt_us, type) (type *)ts_join((ticket), (endt_us))
////////////////////////////////
//~ rjf: Task Threads
internal void ts_u2t_dequeue_task(TS_TaskFunctionType **entry_point_out, void **p_out, TS_Ticket *ticket_out);
internal void ts_task_thread__entry_point(void *p);
#endif // TASK_SYSTEM_H