From 4a787ee618e9a273c930e1ef6062670c4c8d6448 Mon Sep 17 00:00:00 2001 From: jacob Date: Fri, 5 Dec 2025 23:40:26 -0600 Subject: [PATCH] threading refactor progress --- src/base/base.h | 44 +- src/base/base_arena.c | 28 +- src/base/base_arena.h | 15 +- src/base/base_futex.c | 187 ------ src/base/base_futex.h | 51 +- src/base/base_inc.h | 8 +- src/base/base_job.h | 121 ---- src/base/base_log.h | 1 - src/base/base_memory.c | 46 -- src/base/base_memory.h | 2 +- src/base/{base_snc.c => base_sync.c} | 4 +- src/base/{base_snc.h => base_sync.h} | 2 +- src/base/base_wave.c | 7 + src/base/base_wave.h | 31 + src/base/base_win32/base_win32.c | 52 +- src/base/base_win32/base_win32.h | 42 +- src/base/base_win32/base_win32_futex.c | 27 + src/base/base_win32/base_win32_inc.h | 6 +- src/base/base_win32/base_win32_job.c | 721 ------------------------ src/base/base_win32/base_win32_job.h | 205 ------- src/base/base_win32/base_win32_log.c | 7 +- src/base/base_win32/base_win32_memory.c | 42 ++ src/base/base_win32/base_win32_wave.c | 79 +++ src/base/base_win32/base_win32_wave.h | 17 + src/meta/meta.c | 670 ++++++++++++++++++++-- src/meta/meta.h | 1 - 26 files changed, 900 insertions(+), 1516 deletions(-) delete mode 100644 src/base/base_futex.c delete mode 100644 src/base/base_job.h rename src/base/{base_snc.c => base_sync.c} (97%) rename src/base/{base_snc.h => base_sync.h} (98%) create mode 100644 src/base/base_wave.c create mode 100644 src/base/base_wave.h create mode 100644 src/base/base_win32/base_win32_futex.c delete mode 100644 src/base/base_win32/base_win32_job.c delete mode 100644 src/base/base_win32/base_win32_job.h create mode 100644 src/base/base_win32/base_win32_memory.c create mode 100644 src/base/base_win32/base_win32_wave.c create mode 100644 src/base/base_win32/base_win32_wave.h diff --git a/src/base/base.h b/src/base/base.h index 82430b15..f5611598 100644 --- a/src/base/base.h +++ b/src/base/base.h @@ -112,23 +112,11 @@ #endif //////////////////////////////////////////////////////////// -//~ Platform headers +//~ C headers -//- Windows headers -#if IsPlatformWindows - #define COBJMACROS - #define WIN32_LEAN_AND_MEAN - #define UNICODE - #pragma warning(push, 0) - #include - #include - #include - #include - #include - #include - #include - #include - #pragma warning(pop) +#if IsLanguageC + #include + #include #endif //////////////////////////////////////////////////////////// @@ -207,7 +195,14 @@ #define Readonly __attribute((section(".rodata"))) #endif -//- Barriers +//- Thread-local + +#if IsCompilerMsvc + #define ThreadLocal __declspec(thread) +#endif + + +//- Memory barriers #if IsCompilerMsvc #define WriteBarrier() _WriteBarrier() #define ReadBarrier() _ReadBarrier() @@ -482,7 +477,6 @@ //~ Scalar types #if IsLanguageC - #include typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; @@ -747,20 +741,6 @@ Struct(SamplerStateHandle) { u32 v; }; #define ShaderConstant(type, name, slot) cbuffer name : register(b##slot) { type name; } #endif -//////////////////////////////////////////////////////////// -//~ Fibers - -#define MaxFibers 4096 -StaticAssert(MaxFibers < I16Max); /* MaxFibers should fit in FiberId */ - -#if IsLanguageC - #if IsPlatformWindows - #define FiberId() (*(volatile i16 *)__readgsqword(0x20)) - #else - #error FiberId not implemented - #endif -#endif - //////////////////////////////////////////////////////////// //~ Exit callback types diff --git a/src/base/base_arena.c b/src/base/base_arena.c index 83801983..479dc88e 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -1,4 +1,4 @@ -SharedArenaCtx shared_arena_ctx = ZI; +ThreadLocal ThreadArenasCtx t_arena_ctx = ZI; //////////////////////////////////////////////////////////// //~ Arena management @@ -228,24 +228,6 @@ void EndTempArena(TempArena temp) PopTo(temp.arena, temp.start_pos); } -//////////////////////////////////////////////////////////// -//~ Fiber arena helpers - -FiberArenaCtx *FiberArenaCtxFromId(i16 fiber_id) -{ - SharedArenaCtx *g = &shared_arena_ctx; - FiberArenaCtx *ctx = &g->arena_contexts[fiber_id]; - if (!ctx->perm_arena) - { - ctx->perm_arena = AcquireArena(Gibi(64)); - for (i32 i = 0; i < (i32)countof(ctx->scratch_arenas); ++i) - { - ctx->scratch_arenas[i] = AcquireArena(Gibi(64)); - } - } - return ctx; -} - //////////////////////////////////////////////////////////// //~ Scratch arena helpers @@ -257,11 +239,10 @@ TempArena BeginScratch(Arena *potential_conflict) /* Use `BeginScratchNoConflict` if no conflicts are present */ Assert(potential_conflict != 0); - FiberArenaCtx *ctx = FiberArenaCtxFromId(FiberId()); - Arena *scratch_arena = ctx->scratch_arenas[0]; + Arena *scratch_arena = t_arena_ctx.scratch_arenas[0]; if (scratch_arena == potential_conflict) { - scratch_arena = ctx->scratch_arenas[1]; + scratch_arena = t_arena_ctx.scratch_arenas[1]; } TempArena temp = BeginTempArena(scratch_arena); return temp; @@ -269,8 +250,7 @@ TempArena BeginScratch(Arena *potential_conflict) TempArena BeginScratchNoConflict_(void) { - FiberArenaCtx *ctx = FiberArenaCtxFromId(FiberId()); - Arena *scratch_arena = ctx->scratch_arenas[0]; + Arena *scratch_arena = t_arena_ctx.scratch_arenas[0]; TempArena temp = BeginTempArena(scratch_arena); return temp; } diff --git a/src/base/base_arena.h b/src/base/base_arena.h index d628b259..6a0a501f 100644 --- a/src/base/base_arena.h +++ b/src/base/base_arena.h @@ -22,16 +22,13 @@ Struct(TempArena) #define ScratchArenasPerCtx 2 -Struct(FiberArenaCtx) +Struct(ThreadArenasCtx) { Arena *perm_arena; Arena *scratch_arenas[ScratchArenasPerCtx]; }; -Struct(SharedArenaCtx) -{ - FiberArenaCtx arena_contexts[MaxFibers]; -} extern shared_arena_ctx; +extern ThreadLocal ThreadArenasCtx t_arena_ctx; //////////////////////////////////////////////////////////// //~ Arena management @@ -76,16 +73,12 @@ void *ArenaNext_(Arena *arena, u64 align); void *ArenaFirst_(Arena *arena, u64 align); //////////////////////////////////////////////////////////// -//~ Temp arena helpers +//~ Thread arena helpers TempArena BeginTempArena(Arena *arena); void EndTempArena(TempArena temp); -//////////////////////////////////////////////////////////// -//~ Fiber arena helpers - -FiberArenaCtx *FiberArenaCtxFromId(i16 fiber_id); -#define PermArena() (FiberArenaCtxFromId(FiberId())->perm_arena) +#define PermArena() (t_arena_ctx.perm_arena) //////////////////////////////////////////////////////////// //~ Scratch arena helpers diff --git a/src/base/base_futex.c b/src/base/base_futex.c deleted file mode 100644 index 656bfd51..00000000 --- a/src/base/base_futex.c +++ /dev/null @@ -1,187 +0,0 @@ -SharedFutexState shared_futex_state = ZI; - -//////////////////////////////////////////////////////////// -//~ Startup - -void InitFutexSystem(void) -{ - SharedFutexState *g = &shared_futex_state; -} - -//////////////////////////////////////////////////////////// -//~ State helpers - -FiberNeqFutexState *FiberNeqFutexStateFromId(i16 fiber_id) -{ - return &shared_futex_state.fiber_neq_states[fiber_id]; -} - -//////////////////////////////////////////////////////////// -//~ Not-equal futex operations - -void FutexYieldNeq(volatile void *addr, void *cmp, u8 cmp_size) -{ - SharedFutexState *g = &shared_futex_state; - u64 bin_index = RandU64FromSeed((u64)addr) % countof(g->neq_bins); - FutexNeqListBin *bin = &g->neq_bins[bin_index]; - - b32 cancel = 0; - - LockTicketMutex(&bin->tm); - { - /* Now that bin is locked, check if we should cancel insertion based on current value at address */ - { - union - { - volatile void *v; - Atomic8 *v8; - Atomic16 *v16; - Atomic32 *v32; - Atomic64 *v64; - } addr_atomic; - union - { - void *v; - i8 *v8; - i16 *v16; - i32 *v32; - i64 *v64; - } cmp_int; - addr_atomic.v = addr; - cmp_int.v = cmp; - switch(cmp_size) - { - default: Assert(0); cancel = 1; break; /* Invalid futex size */ - case 1: cancel = Atomic8Fetch(addr_atomic.v8) != *cmp_int.v8; break; - case 2: cancel = Atomic16Fetch(addr_atomic.v16) != *cmp_int.v16; break; - case 4: cancel = Atomic32Fetch(addr_atomic.v32) != *cmp_int.v32; break; - case 8: cancel = Atomic64Fetch(addr_atomic.v64) != *cmp_int.v64; break; - } - } - if (!cancel) - { - /* Grab futex list from address */ - FutexNeqList *list = 0; - { - list = bin->first; - for (; list; list = list->next) - { - if (list->addr == addr) break; - } - if (!list) - { - if (bin->first_free) - { - list = bin->first_free; - bin->first_free = list->next; - ZeroStruct(list); - } - else - { - Arena *perm = PermArena(); - PushAlign(perm, CachelineSize); - list = PushStruct(perm, FutexNeqList); - PushAlign(perm, CachelineSize); - } - list->addr = addr; - list->next = bin->first; - bin->first = list; - } - } - - /* Insert */ - { - i16 fiber_id = FiberId(); - FiberNeqFutexState *f = FiberNeqFutexStateFromId(fiber_id); - f->next = list->first; - list->first = fiber_id; - } - } - } - UnlockTicketMutex(&bin->tm); - - /* Suspend */ - if (!cancel) - { - SuspendFiber(); - } -} - -void FutexWakeNeq(void *addr) -{ - SharedFutexState *g = &shared_futex_state; - u64 bin_index = RandU64FromSeed((u64)addr) % countof(g->neq_bins); - FutexNeqListBin *bin = &g->neq_bins[RandU64FromSeed((u64)addr) % countof(g->neq_bins)]; - - /* Pull waiting ids */ - i16 first_id = 0; - { - LockTicketMutex(&bin->tm); - { - FutexNeqList *list = bin->first; - for (; list; list = list->next) - { - if (list->addr == addr) break; - } - if (list) - { - first_id = list->first; - /* Free futex list */ - { - FutexNeqList *prev = list->prev; - FutexNeqList *next = list->next; - if (prev) - { - prev->next = next; - } - else - { - bin->first = next; - } - if (next) - { - next->prev = prev; - } - list->next = bin->first_free; - bin->first_free = list; - } - } - } - UnlockTicketMutex(&bin->tm); - } - - /* Resume fibers */ - if (first_id != 0) - { - TempArena scratch = BeginScratchNoConflict(); - i16 *ids = ArenaNext(scratch.arena, i16); - i16 ids_count = 0; - { - i16 id = first_id; - while (id != 0) - { - FiberNeqFutexState *f = FiberNeqFutexStateFromId(id); - *PushStructNoZero(scratch.arena, i16) = id; - ++ids_count; - id = FiberNeqFutexStateFromId(id)->next; - } - } - ResumeFibers(ids_count, ids); - EndScratch(scratch); - } -} - -//////////////////////////////////////////////////////////// -//~ Greater-than-or-equal futex operations - -void FutexYieldGte(volatile void *addr, void *cmp, u8 cmp_size) -{ - /* TODO: Actually implement this. Just emulating via neq for now. */ - FutexYieldNeq(addr, cmp, cmp_size); -} - -void FutexWakeGte(void *addr) -{ - /* TODO: Actually implement this. Just emulating via neq for now. */ - FutexWakeNeq(addr); -} diff --git a/src/base/base_futex.h b/src/base/base_futex.h index 89f5f68a..121b8b47 100644 --- a/src/base/base_futex.h +++ b/src/base/base_futex.h @@ -1,50 +1,5 @@ //////////////////////////////////////////////////////////// -//~ Neq futex types - -Struct(FutexNeqList) -{ - FutexNeqList *prev; - FutexNeqList *next; - volatile void *addr; - i16 first; -}; - -Struct(FutexNeqListBin) -{ - TicketMutex tm; - FutexNeqList *first; - FutexNeqList *first_free; -}; - -//////////////////////////////////////////////////////////// -//~ State types - -#define FutexNeqBinsCount 16384 -#define FutexGteBinsCount 16384 - -AlignedStruct(FiberNeqFutexState, CachelineSize) -{ - i16 next; -}; - -Struct(SharedFutexState) -{ - FiberNeqFutexState fiber_neq_states[MaxFibers]; - FutexNeqListBin neq_bins[FutexNeqBinsCount]; -} extern shared_futex_state; - -//////////////////////////////////////////////////////////// -//~ Startup - -void InitFutexSystem(void); - -//////////////////////////////////////////////////////////// -//~ State helpers - -FiberNeqFutexState *FiberNeqFutexStateFromId(i16 fiber_id); - -//////////////////////////////////////////////////////////// -//~ Not-equal futex operations +//~ @hookdecl Not-equal futex operations /* Similar to Win32 WaitOnAddress & WakeByAddressAll * i.e. - Suprious wait until value at address != cmp */ @@ -53,7 +8,7 @@ void FutexYieldNeq(volatile void *addr, void *cmp, u8 cmp_size); void FutexWakeNeq(void *addr); //////////////////////////////////////////////////////////// -//~ Greater-than-or-equal futex operations +//~ @hookdecl Greater-than-or-equal futex operations /* Similar to Win32 WaitOnAddress & WakeByAddressAll * i.e. - Spurious wait until monotonically increasing value at address >= cmp (used for fences) @@ -61,7 +16,7 @@ void FutexWakeNeq(void *addr); * NOTE: This API is offered for fence-like semantics, where waiters only want to * wake when the futex progresses past the specified target value, rather than * wake every time the futex is modified. - **/ + */ void FutexYieldGte(volatile void *addr, void *cmp, u8 cmp_size); void FutexWakeGte(void *addr); diff --git a/src/base/base_inc.h b/src/base/base_inc.h index ce76e8be..eaab4b5f 100644 --- a/src/base/base_inc.h +++ b/src/base/base_inc.h @@ -9,8 +9,8 @@ # include "base_memory.h" # include "base_arena.h" # include "base_futex.h" -# include "base_snc.h" -# include "base_job.h" +# include "base_sync.h" +# include "base_wave.h" # include "base_time.h" # include "base_uid.h" # include "base_string.h" @@ -34,8 +34,8 @@ #if IsLanguageC # include "base_memory.c" # include "base_arena.c" -# include "base_futex.c" -# include "base_snc.c" +# include "base_sync.c" +# include "base_wave.c" # include "base_uid.c" # include "base_string.c" # include "base_cmdline.c" diff --git a/src/base/base_job.h b/src/base/base_job.h deleted file mode 100644 index 6f5fa219..00000000 --- a/src/base/base_job.h +++ /dev/null @@ -1,121 +0,0 @@ -//////////////////////////////////////////////////////////// -//~ Job pool types - -typedef i32 JobPoolId; - -Enum(JobPoolPriority) -{ - JobPoolPriority_Background, - JobPoolPriority_Async, - JobPoolPriority_Graphics, - JobPoolPriority_Simulation, - JobPoolPriority_Critical, - JobPoolPriority_Audio, -}; - -//////////////////////////////////////////////////////////// -//~ Job types - -Struct(Job); - -typedef void JobFunc(void *, i32); - -Enum(JobFlag) -{ - JobFlag_None = (0), -}; - -Struct(Job) -{ - /* Internal */ - Job *next; - Atomic32Padded num_tasks_completed; - - /* Initialized & constant after OpenJob */ - Arena *arena; - JobFunc *func; - JobPoolId pool; - - /* Configurable between OpenJob & CloseJob */ - i32 count; - Fence *fence; - JobFlag flags; - void *sig; -}; - -//////////////////////////////////////////////////////////// -//~ @hookdecl Init - -void InitJobSystem(void); - -//////////////////////////////////////////////////////////// -//~ @hookdecl Fiber suspend/resume operations - -void SuspendFiber(void); -void ResumeFibers(i16 fiber_ids_count, i16 *fiber_ids); /* NOTE: Must only be called on fibers suspended via SuspendFiber */ - -//////////////////////////////////////////////////////////// -//~ @hookdecl Job pool operations - -JobPoolId InitJobPool(u32 thread_count, String name, JobPoolPriority priority); - -//- Default job pools - -/* The current job pool */ -JobPoolId CurrentPool(void); - -/* Contains high-priority workers that span the entire CPU. - * Meant to take on temporary high-throughput work that is allowed to - * steal performance from all other pools (e.g. program startup, loading a level, - * etc). */ -JobPoolId HyperPool(void); - -/* Contains lower-priority workers affinitized to not interfere with workers in other pools. - * Meant to consume asynchronous work from higher priority pools. */ -JobPoolId AsyncPool(void); - -//////////////////////////////////////////////////////////// -//~ @hookdecl Job declaration operations - -#define EmptySig { i32 _; } - -#define JobDecl(job, sigdef) \ - typedef struct job##_Sig sigdef job##_Sig; \ - Struct(job##_Desc) { JobFunc *func; JobPoolId pool; u32 count; Fence *fence; JobFlag flags; job##_Sig sig; }; \ - void job(job##_Sig *, i32); \ - StaticAssert(1) - -#define JobImpl(job, sig_arg, id_arg) void job(job##_Sig *sig_arg, i32 id_arg) - -//////////////////////////////////////////////////////////// -//~ @hookdecl Job dispatch operations - -/* RunJob example usage: - * - * This example pushes a single 'LoadTextureJob' onto the background job - * pool, copying 'sprite' into the job signature. 'fence' is also passed - * and then immediately yielded on in this example, effectively making - * the operation synchronous as the caller will block until the job completes: - * { - * Fence job_fence = {0}; - * u32 job_count = 0; - * job_count += RunJob(LoadTextureJob, .pool = JobPool_Background, .fence = &job_fence, .sig = { .resource = sprite }); - * YieldOnFence(&job_fence, job_count); - * } - * - */ - -#define RunJob(job_func, ...) (1); \ -do { \ - job_func##_Desc __desc = { .count = 1, .pool = CurrentPool(), .func = job_func, __VA_ARGS__ }; \ - Job *__job = OpenJob(__desc.func, __desc.pool); \ - __job->count = __desc.count; \ - __job->fence = __desc.fence; \ - __job->flags = __desc.flags; \ - __job->sig = PushStructNoZero(__job->arena, job_func##_Sig); \ - CopyBytes(__job->sig, &__desc.sig, sizeof(__desc.sig)); \ - CloseJob(__job); \ -} while (0) - -Job *OpenJob(JobFunc *func, JobPoolId pool_kind); -u32 CloseJob(Job *job); diff --git a/src/base/base_log.h b/src/base/base_log.h index 4cd8ec0b..2d5dfae6 100644 --- a/src/base/base_log.h +++ b/src/base/base_log.h @@ -13,7 +13,6 @@ Struct(LogEvent) i32 level; i32 thread_id; - i32 fiber_id; }; Struct(LogEventsArray) diff --git a/src/base/base_memory.c b/src/base/base_memory.c index c58e725a..b9f48aa5 100644 --- a/src/base/base_memory.c +++ b/src/base/base_memory.c @@ -1,49 +1,3 @@ -//////////////////////////////////////////////////////////// -//~ Win32 memory allocation - -#if IsPlatformWindows - -//- Reserve -void *ReserveMemory(u64 size) -{ - void *ptr = VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS); - return ptr; -} - -void ReleaseMemory(void *address) -{ - VirtualFree(address, 0, MEM_RELEASE); -} - -//- Commit -void *CommitMemory(void *address, u64 size) -{ - void *ptr = VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE); - return ptr; -} - -void DecommitMemory(void *address, u64 size) -{ - VirtualFree(address, size, MEM_DECOMMIT); -} - -//- Protect -void SetMemoryReadonly(void *address, u64 size) -{ - DWORD old; - VirtualProtect(address, size, PAGE_READONLY, &old); -} - -void SetMemoryReadWrite(void *address, u64 size) -{ - DWORD old; - VirtualProtect(address, size, PAGE_READWRITE, &old); -} - -#else -# error Memory allocation not implemented for this platform -#endif /* IsPlatformWindows */ - //////////////////////////////////////////////////////////// //~ Crtlib mem op stubs diff --git a/src/base/base_memory.h b/src/base/base_memory.h index aa244d41..27fc59d7 100644 --- a/src/base/base_memory.h +++ b/src/base/base_memory.h @@ -1,5 +1,5 @@ //////////////////////////////////////////////////////////// -//~ Memory allocation +//~ @hookdecl Memory allocation //- Reserve void *ReserveMemory(u64 size); diff --git a/src/base/base_snc.c b/src/base/base_sync.c similarity index 97% rename from src/base/base_snc.c rename to src/base/base_sync.c index 9b3748c3..892d2be9 100644 --- a/src/base/base_snc.c +++ b/src/base/base_sync.c @@ -56,7 +56,7 @@ Lock LockSpinE(Mutex *m, i32 spin) } #if IsRtcEnabled - Atomic32Set(&m->exclusive_fiber_id, FiberId()); + Atomic32Set(&m->exclusive_thread_id, ThreadId()); #endif Lock lock = ZI; @@ -123,7 +123,7 @@ void Unlock(Lock *l) if (l->exclusive) { #if IsRtcEnabled - Atomic32Set(&m->exclusive_fiber_id, 0); + Atomic32Set(&m->exclusive_thread_id, 0); #endif Atomic32Set(&m->v, 0); } diff --git a/src/base/base_snc.h b/src/base/base_sync.h similarity index 98% rename from src/base/base_snc.h rename to src/base/base_sync.h index 49cb60de..c6e350ab 100644 --- a/src/base/base_snc.h +++ b/src/base/base_sync.h @@ -12,7 +12,7 @@ AlignedStruct(Mutex, CachelineSize) Atomic32 v; #if IsRtcEnabled - Atomic32 exclusive_fiber_id; + Atomic32 exclusive_thread_id; #endif }; StaticAssert(alignof(Mutex) == CachelineSize && sizeof(Mutex) % CachelineSize == 0); diff --git a/src/base/base_wave.c b/src/base/base_wave.c new file mode 100644 index 00000000..672529b6 --- /dev/null +++ b/src/base/base_wave.c @@ -0,0 +1,7 @@ +//////////////////////////////////////////////////////////// +//~ Wave sync ops + +void WaveSyncBroadcast_(WaveLaneCtx *lane_ctx, i32 broadcast_lane_idx, void *broadcast_ptr, u64 broadcast_size) +{ + /* FIXME: Impl */ +} diff --git a/src/base/base_wave.h b/src/base/base_wave.h new file mode 100644 index 00000000..916923b4 --- /dev/null +++ b/src/base/base_wave.h @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////// +//~ Wave types + +Struct(WaveCtx) +{ + u32 lanes_count; +}; + +Struct(WaveLaneCtx) +{ + u32 idx; + WaveCtx *wave; +}; + +typedef void WaveLaneEntryFunc(WaveLaneCtx *lane, void *udata); + +//////////////////////////////////////////////////////////// +//~ Wave sync ops + +#define WaveSyncBroadcast(lane_ctx, broadcast_lane_idx, broadcast_ptr) WaveSyncBroadcast_((lane_ctx), (broadcast_lane_idx), (broadcast_ptr), sizeof(*(broadcast_ptr))) +void WaveSyncBroadcast_(WaveLaneCtx *lane_ctx, i32 broadcast_lane_idx, void *broadcast_ptr, u64 broadcast_size); + +//////////////////////////////////////////////////////////// +//~ @hookdecl Dispatch + +void DispatchWave(u32 num_lanes, WaveLaneEntryFunc *entry, void *udata); + +//////////////////////////////////////////////////////////// +//~ @hookdecl Thread + +i32 ThreadId(void); diff --git a/src/base/base_win32/base_win32.c b/src/base/base_win32/base_win32.c index 132f6c57..d121c3ca 100644 --- a/src/base/base_win32/base_win32.c +++ b/src/base/base_win32/base_win32.c @@ -196,32 +196,6 @@ void ExitNow(i32 code) ExitProcess(code); } -//////////////////////////////////////////////////////////// -//~ Startup / shutdown jobs - -JobImpl(W32_StartupLayers, sig, id) -{ - W32_SharedState *g = &W32_shared_state; - TempArena scratch = BeginScratchNoConflict(); - { - StartupLayers(); - SetEvent(g->startup_end_event); - } - EndScratch(scratch); -} - -JobImpl(W32_ShutdownLayers, sig, id) -{ - W32_SharedState *g = &W32_shared_state; - i32 num_funcs = Atomic32Fetch(&g->num_exit_funcs); - for (i32 i = num_funcs - 1; i >= 0; --i) - { - ExitFunc *func = g->exit_funcs[i]; - func(); - } - SetEvent(g->exit_end_event); -} - //////////////////////////////////////////////////////////// //~ Main @@ -241,11 +215,9 @@ i32 W32_Main(void) g->timer_start_qpc = qpc.QuadPart; } - /* Set up exit events */ + /* Setup events */ g->panic_event = CreateEventW(0, 1, 0, 0); - g->startup_end_event = CreateEventW(0, 1, 0, 0); g->exit_begin_event = CreateEventW(0, 1, 0, 0); - g->exit_end_event = CreateEventW(0, 1, 0, 0); g->main_thread_id = GetCurrentThreadId(); SetThreadDescription(GetCurrentThread(), L"Main thread"); @@ -253,11 +225,8 @@ i32 W32_Main(void) /* Query system info */ GetSystemInfo(&g->info); - /* Init job system */ - InitJobSystem(); - - /* Init futex system */ - InitFutexSystem(); + /* Init main thread */ + W32_InitCurrentThread(Lit("Main thread")); /* Get raw args from command line */ { @@ -296,14 +265,13 @@ i32 W32_Main(void) /* Startup layers */ if (!Atomic32Fetch(&g->panicking)) { - RunJob(W32_StartupLayers); + StartupLayers(); } - /* Wait for startup end or panic */ + /* Wait for panic */ if (!Atomic32Fetch(&g->panicking)) { HANDLE handles[] = { - g->startup_end_event, g->panic_event, }; WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); @@ -321,17 +289,21 @@ i32 W32_Main(void) //- App shutdown - /* Run exit callbacks job */ + /* Run exit callbacks */ if (!Atomic32Fetch(&g->panicking)) { - RunJob(W32_ShutdownLayers); + i32 num_funcs = Atomic32Fetch(&g->num_exit_funcs); + for (i32 idx = num_funcs - 1; idx >= 0; --idx) + { + ExitFunc *func = g->exit_funcs[idx]; + func(); + } } /* Wait for exit end or panic */ if (!Atomic32Fetch(&g->panicking)) { HANDLE handles[] = { - g->exit_end_event, g->panic_event }; WaitForMultipleObjects(countof(handles), handles, 0, INFINITE); diff --git a/src/base/base_win32/base_win32.h b/src/base/base_win32/base_win32.h index be697980..6d2d6903 100644 --- a/src/base/base_win32/base_win32.h +++ b/src/base/base_win32/base_win32.h @@ -1,14 +1,48 @@ //////////////////////////////////////////////////////////// //~ Win32 libs +//- Windows headers + +#define COBJMACROS +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#pragma warning(push, 0) + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#pragma warning(pop) + #ifndef BCRYPT_RNG_ALG_HANDLE #define BCRYPT_RNG_ALG_HANDLE ((void *)0x00000081) u32 BCryptGenRandom(void *algorithm, u8 *buffer, u32 buffer_size, u32 flags); #endif +//- Windows libs + #pragma comment(lib, "kernel32") #pragma comment(lib, "user32") #pragma comment(lib, "bcrypt") +#pragma comment(lib, "shell32") +#pragma comment(lib, "ole32") +#pragma comment(lib, "winmm") +#pragma comment(lib, "dwmapi") +#pragma comment(lib, "synchronization") +#pragma comment(lib, "avrt") +#pragma comment(lib, "ws2_32.lib") //////////////////////////////////////////////////////////// //~ Embedded data iter types @@ -40,9 +74,7 @@ Struct(W32_SharedState) Atomic32 panicking; wchar_t panic_wstr[4096]; HANDLE panic_event; - HANDLE startup_end_event; HANDLE exit_begin_event; - HANDLE exit_end_event; //- Exit funcs Atomic32 num_exit_funcs; @@ -55,12 +87,6 @@ Struct(W32_SharedState) #define W32_EmbeddedDataPrefix EMBEDDED_RESOURCE_DATA__ BOOL W32_FindEmbeddedRcData(HMODULE module, LPCWSTR type, LPWSTR wstr_entry_name, LONG_PTR udata); -//////////////////////////////////////////////////////////// -//~ Startup / shutdown jobs - -JobDecl(W32_StartupLayers, EmptySig); -JobDecl(W32_ShutdownLayers, EmptySig); - //////////////////////////////////////////////////////////// //~ Main diff --git a/src/base/base_win32/base_win32_futex.c b/src/base/base_win32/base_win32_futex.c new file mode 100644 index 00000000..acf81fd1 --- /dev/null +++ b/src/base/base_win32/base_win32_futex.c @@ -0,0 +1,27 @@ +//////////////////////////////////////////////////////////// +//~ @hookimpl Not-equal futex operations + +void FutexYieldNeq(volatile void *addr, void *cmp, u8 cmp_size) +{ + WaitOnAddress(addr, cmp, cmp_size, INFINITE); +} + +void FutexWakeNeq(void *addr) +{ + WakeByAddressSingle(addr); +} + +//////////////////////////////////////////////////////////// +//~ @hookimpl Greater-than-or-equal futex operations + +void FutexYieldGte(volatile void *addr, void *cmp, u8 cmp_size) +{ + /* TODO: Actually implement this. Just emulating via neq for now. */ + FutexYieldNeq(addr, cmp, cmp_size); +} + +void FutexWakeGte(void *addr) +{ + /* TODO: Actually implement this. Just emulating via neq for now. */ + FutexWakeNeq(addr); +} diff --git a/src/base/base_win32/base_win32_inc.h b/src/base/base_win32/base_win32_inc.h index 2b2d181a..587d8e9a 100644 --- a/src/base/base_win32/base_win32_inc.h +++ b/src/base/base_win32/base_win32_inc.h @@ -1,10 +1,12 @@ //- Api #include "base_win32.h" -#include "base_win32_job.h" +#include "base_win32_wave.h" #include "base_win32_log.h" //- Impl #include "base_win32.c" -#include "base_win32_job.c" +#include "base_win32_futex.c" +#include "base_win32_memory.c" +#include "base_win32_wave.c" #include "base_win32_time.c" #include "base_win32_log.c" diff --git a/src/base/base_win32/base_win32_job.c b/src/base/base_win32/base_win32_job.c deleted file mode 100644 index d1a960c7..00000000 --- a/src/base/base_win32/base_win32_job.c +++ /dev/null @@ -1,721 +0,0 @@ -W32_SharedJobState W32_shared_job_state = ZI; - -//////////////////////////////////////////////////////////// -//~ @hookimpl Startup - -void InitJobSystem(void) -{ - /* Init fibers */ - W32_SharedJobState *g = &W32_shared_job_state; - g->num_fibers = 1; /* Fiber at index 0 always nil */ - W32_AcquireFiber(0); /* Convert main thread to fiber */ - Arena *perm = PermArena(); - - /* TODO: Dynamic thread counts */ - g->hyper_pool = InitJobPool(8, Lit("Hyper"), JobPoolPriority_Critical); - g->async_pool = InitJobPool(8, Lit("Async"), JobPoolPriority_Async); - - /* Ensure hyper pool has id 0. - * Pool ID 0 will be used when pushing a job from outside a pool (e.g. during startup) */ - Assert(g->hyper_pool == 0); -} - -//////////////////////////////////////////////////////////// -//~ Win32 thread - -JobImpl(W32_DummyJob, sig, id) -{ -} - -DWORD WINAPI W32_Win32ThreadProc(LPVOID vt) -{ - /* Convert thread to fiber */ - W32_AcquireFiber(0); - Arena *perm = PermArena(); - - - W32_Thread *t = (W32_Thread *)vt; - String thread_name_desc = StringF(perm, "[%F] %F", FmtSint(FiberId()), FmtString(t->thread_name)); - char *thread_name_desc_cstr = CstrFromString(perm, thread_name_desc); - wchar_t *thread_name_desc_wstr = WstrFromString(perm, thread_name_desc); - - /* Initialize COM */ - CoInitializeEx(0, COINIT_MULTITHREADED); - - /* Set thread name */ - SetThreadDescription(GetCurrentThread(), thread_name_desc_wstr); - - //LogInfoF("New thread \"%F\" created with ID %F", FmtString(StringFromCstrNoLimit(t->thread_name_cstr)), FmtUint(ThreadId())); - - /* Enter thread entry point */ - t->entry_point(t->thread_udata); - - /* Uninitialize COM */ - CoUninitialize(); - - return 0; -} - -W32_Thread *W32_StartThread(W32_ThreadFunc *entry_point, void *thread_udata, String thread_name) -{ - W32_SharedJobState *g = &W32_shared_job_state; - TempArena scratch = BeginScratchNoConflict(); - Arena *perm = PermArena(); - Assert(entry_point != 0); - //LogInfoF("Creating thread \"%F\"", FmtString(thread_name)); - - W32_Thread *t = PushStruct(perm, W32_Thread); - t->thread_name = PushString(perm, thread_name); - t->entry_point = entry_point; - t->thread_udata = thread_udata; - - t->handle = CreateThread(0, W32_FiberStackSize, W32_Win32ThreadProc, t, 0, 0); - if (!t->handle) - { - Panic(Lit("Failed to create thread")); - } - - EndScratch(scratch); - return (W32_Thread *)t; -} - -/* Returns 0 if the thread could not release in specified timeout (e.g. because it is still running) */ -b32 W32_TryEndThread(W32_Thread *thread, f32 timeout_seconds) -{ - W32_SharedJobState *g = &W32_shared_job_state; - b32 ok = 0; - W32_Thread *t = (W32_Thread *)thread; - HANDLE handle = t->handle; - if (handle) - { - /* Wait for thread to stop */ - DWORD timeout_ms = (timeout_seconds > 10000000) ? INFINITE : RoundF32ToI32(timeout_seconds * 1000); - DWORD wait_result = WaitForSingleObject(handle, timeout_ms); - if (wait_result == WAIT_OBJECT_0) - { - /* Release thread */ - ok = 1; - CloseHandle(handle); - } - } - return ok; -} - -void W32_WaitEndThread(W32_Thread *thread) -{ - b32 ok = W32_TryEndThread(thread, F32Infinity); - Assert(ok); -} - -//////////////////////////////////////////////////////////// -//~ Win32 fiber - -//- Acquire fiber -/* If `pool` is 0, then the currently running thread will be converted into a fiber */ -W32_Fiber *W32_AcquireFiber(W32_JobPool *pool) -{ - W32_SharedJobState *g = &W32_shared_job_state; - i16 fiber_id = 0; - W32_Fiber *fiber = 0; - char *new_name_cstr = 0; - { - if (pool != 0) - { - LockTicketMutex(&pool->free_fibers_tm); - if (pool->first_free_fiber_id) - { - fiber_id = pool->first_free_fiber_id; - fiber = &g->fibers[fiber_id]; - pool->first_free_fiber_id = fiber->return_id; - } - UnlockTicketMutex(&pool->free_fibers_tm); - } - if (!fiber_id) - { - LockTicketMutex(&g->fibers_tm); - { - { - fiber_id = g->num_fibers++; - if (fiber_id >= MaxFibers) - { - Panic(Lit("Max fibers reached")); - } - fiber = &g->fibers[fiber_id]; - if (!g->fiber_names_arena) - { - g->fiber_names_arena = AcquireArena(Gibi(64)); - } - new_name_cstr = PushStructs(g->fiber_names_arena, char, W32_FiberNameMaxSize); - } - } - UnlockTicketMutex(&g->fibers_tm); - } - - } - if (new_name_cstr != 0) - { - fiber->id = fiber_id; - - /* Id to ASCII */ - i32 id_div = fiber_id; - char id_chars[64] = ZI; - i32 id_chars_len = 0; - do - { - i32 digit = id_div % 10; - id_div /= 10; - id_chars[id_chars_len] = ("0123456789")[digit]; - ++id_chars_len; - } while (id_div > 0); - i32 rev_start = 0; - i32 rev_end = id_chars_len - 1; - while (rev_start < rev_end) - { - char swp = id_chars[rev_start]; - id_chars[rev_start] = id_chars[rev_end]; - id_chars[rev_end] = swp; - ++rev_start; - --rev_end; - } - - /* Concat fiber name */ - i32 name_size = 1; - Assert(sizeof(sizeof(W32_FiberNamePrefixCstr)) <= W32_FiberNameMaxSize); - CopyBytes(new_name_cstr, W32_FiberNamePrefixCstr, sizeof(W32_FiberNamePrefixCstr)); - name_size += sizeof(W32_FiberNamePrefixCstr) - 2; - CopyBytes(new_name_cstr + name_size, id_chars, id_chars_len); - name_size += id_chars_len; - CopyBytes(new_name_cstr + name_size, W32_FiberNameSuffixCstr, sizeof(W32_FiberNameSuffixCstr)); - name_size += sizeof(W32_FiberNameSuffixCstr) - 2; - - fiber->name_cstr = new_name_cstr; - - /* Init win32 fiber */ - if (pool != 0) - { - fiber->pool = pool->id; -#if VIRTUAL_FIBERS - fiber->addr = CreateThread(0, W32_FiberStackSize, W32_VirtualFiberEntryPoint, (void *)(i64)fiber_id, 0, 0); -#else - fiber->addr = CreateFiber(W32_FiberStackSize, W32_FiberEntryPoint, (void *)(i64)fiber_id); -#endif - } - else - { - /* Fiber is not a part of a job pool, convert thread to fiber */ - fiber->addr = ConvertThreadToFiber((void *)(i64)fiber_id); -#if VIRTUAL_FIBERS - fiber->addr = GetCurrentThread(); -#endif - } - } - fiber->task = 0; - fiber->return_id = 0; - return fiber; -} - -//- Release fiber -void W32_ReleaseFiber(W32_JobPool *pool, W32_Fiber *fiber) -{ - LockTicketMutex(&pool->free_fibers_tm); - { - i16 fiber_id = fiber->id; - fiber->return_id = pool->first_free_fiber_id; - pool->first_free_fiber_id = fiber_id; - } - UnlockTicketMutex(&pool->free_fibers_tm); -} - -//- Fiber id -ForceInline W32_Fiber *W32_FiberFromId(i16 id) -{ - return id > 0 ? &W32_shared_job_state.fibers[id] : 0; -} - -void W32_SwitchToFiber(W32_Fiber *target) -{ -#if VIRTUAL_FIBERS - W32_Fiber *self = W32_FiberFromId(FiberId()); - Atomic8Set(&self->virtual_yield, 1); - /* Signal virtual target */ - { - Atomic8Set(&target->virtual_yield, 0); - WakeByAddressSingle(&target->virtual_yield); - } - /* Wait for return */ - { - i8 vswitch = 1; - while (vswitch != 0) - { - WaitOnAddress(&self->virtual_yield, &vswitch, sizeof(vswitch), INFINITE); - vswitch = Atomic8Fetch(&self->virtual_yield); - } - } -#else - SwitchToFiber(target->addr); -#endif -} - -//////////////////////////////////////////////////////////// -//~ Win32 fiber entry - -void W32_FiberEntryPoint(void *_) -{ - i16 fiber_id = FiberId(); - volatile W32_Fiber *fiber = W32_FiberFromId(fiber_id); - W32_JobPool *pool = &W32_shared_job_state.job_pools[fiber->pool]; - JobPoolId pool_id = fiber->pool; - char *fiber_name_cstr = fiber->name_cstr; - for (;;) - { - W32_Task *task = fiber->task; - Job *job = task->job; - - /* Run task */ - job->func(job->sig, task->task_id); - - /* Check if we've completed the last task in the job */ - if (Atomic32FetchAdd(&job->num_tasks_completed.v, 1) + 1 >= job->count) - { - /* Increment fence */ - if (job->fence) - { - FetchAddFence(job->fence, 1); - } - /* Free job */ - LockTicketMutex(&pool->free_jobs_tm); - { - SllStackPush(pool->first_free_job, job); - } - UnlockTicketMutex(&pool->free_jobs_tm); - } - - /* Free task */ - { - LockTicketMutex(&pool->free_tasks_tm); - { - SllStackPush(pool->first_free_task, task); - } - UnlockTicketMutex(&pool->free_tasks_tm); - } - - /* Yield to worker */ - { - W32_Fiber *parent_fiber = W32_FiberFromId(fiber->return_id); - W32_SwitchToFiber(parent_fiber); - } - } -} - -DWORD WINAPI W32_VirtualFiberEntryPoint(LPVOID arg) -{ - ConvertThreadToFiber(arg); - - Arena *perm = PermArena(); - char *fiber_name_cstr = W32_FiberFromId(FiberId())->name_cstr; - wchar_t *fiber_name_wstr = WstrFromString(perm, StringFromCstrNoLimit(fiber_name_cstr)); - SetThreadDescription(GetCurrentThread(), fiber_name_wstr); - - CoInitializeEx(0, COINIT_MULTITHREADED); - W32_FiberEntryPoint(0); - CoUninitialize(); - return 0; -} - -//////////////////////////////////////////////////////////// -//~ Win32 job worker entry - -W32_ThreadDef(W32_JobWorkerEntryPoint, worker_ctx_arg) -{ - W32_WorkerCtx *ctx = worker_ctx_arg; - JobPoolId pool_id = ctx->pool_id; - W32_JobPool *pool = &W32_shared_job_state.job_pools[pool_id]; - i16 worker_fiber_id = FiberId(); - - - { - /* TODO: Set win32 IO priority as well */ - HANDLE thread_handle = GetCurrentThread(); - switch (pool->priority) - { - case JobPoolPriority_Background: - { - SetThreadPriority(thread_handle, THREAD_PRIORITY_BELOW_NORMAL); - } break; - - case JobPoolPriority_Async: - { - SetThreadPriority(thread_handle, THREAD_PRIORITY_NORMAL); - } break; - - case JobPoolPriority_Graphics: - { - SetThreadPriority(thread_handle, THREAD_PRIORITY_ABOVE_NORMAL); - } break; - - case JobPoolPriority_Simulation: - { - SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST); - } break; - - case JobPoolPriority_Critical: - { - SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST); - } break; - - case JobPoolPriority_Audio: - { - /* https://learn.microsoft.com/en-us/windows/win32/procthread/multimedia-class-scheduler-service#registry-settings */ - DWORD task = 0; - AvSetMmThreadCharacteristics(L"Pro Audio", &task); - SetThreadPriority(thread_handle, THREAD_PRIORITY_TIME_CRITICAL); - } - } - } - - W32_Fiber *task_fiber = 0; - i64 tasks_count = 0; - while (tasks_count >= 0) - { - W32_Task *task = 0; - { - LockTicketMutex(&pool->tasks_tm); - { - task = pool->first_task; - if (task) - { - SllQueuePop(pool->first_task, pool->last_task); - Atomic64FetchAdd(&pool->tasks_count.v, -1); - } - } - UnlockTicketMutex(&pool->tasks_tm); - } - - if (task) - { - if (task->fiber_id != 0) - { - /* Task is resuming with an existing fiber */ - if (task_fiber) - { - W32_ReleaseFiber(pool, task_fiber); - } - task_fiber = W32_FiberFromId(task->fiber_id); - } - else - { - /* Task is unstarted, wrap it in a fiber */ - if (!task_fiber) - { - task_fiber = W32_AcquireFiber(pool); - } - task_fiber->task = task; - task->fiber_id = task_fiber->id; - } - - /* Run task fiber */ - task_fiber->return_id = worker_fiber_id; - W32_SwitchToFiber(task_fiber); - - if (Atomic8Fetch(&task_fiber->status) == W32_FiberStatus_Suspending) - { - /* Fiber suspended during execution */ - Atomic8Set(&task_fiber->status, W32_FiberStatus_Suspended); - task_fiber = 0; - } - } - - /* Park worker */ - tasks_count = Atomic64Fetch(&pool->tasks_count.v); - while (tasks_count == 0) - { - /* TODO: Add toggleable flag that allows for workers to skip parking - * for high-throughput workloads, and instead just spin until work is available */ - WaitOnAddress(&pool->tasks_count, &tasks_count, sizeof(tasks_count), INFINITE); - tasks_count = Atomic64Fetch(&pool->tasks_count.v); - } - } - - /* Worker shutdown */ - if (task_fiber) - { - W32_ReleaseFiber(pool, task_fiber); - } -} - -//////////////////////////////////////////////////////////// -//~ @hookimpl Fiber suspend/resume operations - -void SuspendFiber(void) -{ - i16 fiber_id = FiberId(); - W32_Fiber *fiber = W32_FiberFromId(FiberId()); - { - Atomic8Set(&fiber->status, W32_FiberStatus_Suspending); - W32_Fiber *parent_fiber = W32_FiberFromId(fiber->return_id); - W32_SwitchToFiber(parent_fiber); - } -} - -/* NOTE: Must only be called on fibers suspended via SuspendFiber */ -void ResumeFibers(i16 fiber_ids_count, i16 *fiber_ids) -{ - TempArena scratch = BeginScratchNoConflict(); - W32_SharedJobState *g = &W32_shared_job_state; - i32 num_pools = Atomic32Fetch(&g->num_pools); - - /* Group tasks by pool */ - W32_TaskList *tasks_by_pool = PushStructs(scratch.arena, W32_TaskList, num_pools); - for (i16 id_index = 0; id_index < fiber_ids_count; ++id_index) - { - i16 fiber_id = fiber_ids[id_index]; - W32_Fiber *fiber = W32_FiberFromId(fiber_id); - - /* Wait for fiber to complete suspending */ - W32_FiberStatus status = Atomic8Fetch(&fiber->status); - while (status != W32_FiberStatus_Suspended) - { - _mm_pause(); - status = Atomic8Fetch(&fiber->status); - } - - /* Update fiber status */ - Atomic8Set(&fiber->status, W32_FiberStatus_None); - - W32_Task *task = fiber->task; - // if (task->job->flags & JobFlag_Dedicated) - if (0) - { - /* TODO: Wake dedicated fiber right now */ - WakeByAddressSingle(&fiber->status); - } - else - { - /* Group task based on pool */ - JobPoolId pool_id = fiber->pool; - W32_TaskList *pool_tasks = &tasks_by_pool[pool_id]; - SllQueuePush(pool_tasks->first, pool_tasks->last, task); - ++pool_tasks->count; - } - } - - /* Submit tasks */ - for (JobPoolId pool_id = 0; pool_id < num_pools; ++pool_id) - { - W32_TaskList *tasks = &tasks_by_pool[pool_id]; - i16 count = tasks->count; - if (count > 0) - { - W32_JobPool *pool = &g->job_pools[pool_id]; - - /* Push tasks to front of queue */ - LockTicketMutex(&pool->tasks_tm); - { - tasks->last->next = pool->first_task; - pool->first_task = tasks->first; - if (!pool->last_task) - { - pool->last_task = tasks->last; - } - Atomic64FetchAdd(&pool->tasks_count.v, count); - } - UnlockTicketMutex(&pool->tasks_tm); - - /* Wake workers */ - if (count >= W32_WakeAllWorkersThreshold) - { - WakeByAddressAll(&pool->tasks_count); - } - else - { - for (i16 i = 0; i < count; ++i) - { - WakeByAddressSingle(&pool->tasks_count); - } - } - } - } - - EndScratch(scratch); -} - -//////////////////////////////////////////////////////////// -//~ @hookimpl Job pool operations - -JobPoolId InitJobPool(u32 thread_count, String name, JobPoolPriority priority) -{ - W32_SharedJobState *g = &W32_shared_job_state; - JobPoolId pool_id = Atomic32FetchAdd(&g->num_pools, 1); - W32_JobPool *pool = &g->job_pools[pool_id]; - pool->num_worker_threads = thread_count; - pool->id = pool_id; - pool->name = name; - pool->priority = priority; - - Arena *perm = PermArena(); - pool->worker_threads = PushStructs(perm, W32_Thread *, pool->num_worker_threads); - pool->worker_contexts = PushStructs(perm, W32_WorkerCtx, pool->num_worker_threads); - for (i32 i = 0; i < pool->num_worker_threads; ++i) - { - W32_WorkerCtx *ctx = &pool->worker_contexts[i]; - ctx->pool_id = pool_id; - ctx->id = i; - String worker_name = StringF(perm, "%F [%F]", FmtString(name), FmtSint(i)); - pool->worker_threads[i] = W32_StartThread(W32_JobWorkerEntryPoint, ctx, worker_name); - } - return pool_id; -} - -JobPoolId CurrentPool(void) -{ - W32_Fiber *fiber = W32_FiberFromId(FiberId()); - return fiber->pool; -} - -JobPoolId AsyncPool(void) -{ - return W32_shared_job_state.async_pool; -} - -JobPoolId HyperPool(void) -{ - return W32_shared_job_state.hyper_pool; -} - -//////////////////////////////////////////////////////////// -//~ @hookimpl Job operations - -Job *OpenJob(JobFunc *func, JobPoolId pool_id) -{ - W32_JobPool *pool = &W32_shared_job_state.job_pools[pool_id]; - Job *job = 0; - { - Arena *job_arena = 0; - { - { - LockTicketMutex(&pool->free_jobs_tm); - Job *free_job = pool->first_free_job; - if (free_job) - { - pool->first_free_job = free_job->next; - job_arena = free_job->arena; - } - UnlockTicketMutex(&pool->free_jobs_tm); - } - if (job_arena) - { - ResetArena(job_arena); - } - else - { - job_arena = AcquireArena(Mebi(4)); - } - } - job = PushStruct(job_arena, Job); - job->arena = job_arena; - } - job->pool = pool_id; - job->func = func; - job->count = 1; - return job; -} - -u32 CloseJob(Job *job) -{ - TempArena scratch = BeginScratchNoConflict(); - - JobPoolId pool_id = job->pool; - W32_JobPool *pool = &W32_shared_job_state.job_pools[pool_id]; - u32 num_tasks = job->count; - - if (num_tasks == 0) - { - job->func = W32_DummyJob; - num_tasks = 1; - } - - /* Allocate tasks from free list */ - u32 num_tasks_allocated = 0; - W32_Task **tasks_array = PushStructsNoZero(scratch.arena, W32_Task *, num_tasks); - { - LockTicketMutex(&pool->free_tasks_tm); - { - while (num_tasks_allocated < num_tasks) - { - W32_Task *task = pool->first_free_task; - if (task) - { - tasks_array[num_tasks_allocated++] = task; - SllStackPop(pool->first_free_task); - } - else - { - break; - } - } - } - UnlockTicketMutex(&pool->free_tasks_tm); - } - - /* Allocate new tasks from memory */ - u32 remaining = num_tasks - num_tasks_allocated; - if (remaining > 0) - { - Arena *perm = PermArena(); - PushAlign(perm, CachelineSize); - W32_Task *pushed_tasks = PushStructsNoZero(perm, W32_Task, remaining); - for (u32 i = 0; i < remaining; ++i) - { - tasks_array[num_tasks_allocated + i] = &pushed_tasks[i]; - } - num_tasks_allocated += remaining; - PushAlign(perm, CachelineSize); - } - - /* FIXME: Handle dedicated jobs separately */ - - /* Generate task list */ - W32_TaskList tasks = ZI; - for (u32 i = 0; i < num_tasks; ++i) - { - W32_Task *task = tasks_array[i]; - ZeroStruct(task); - task->job = job; - task->task_id = tasks.count++; - SllQueuePush(tasks.first, tasks.last, task); - } - - /* Push tasks to back of pool */ - { - LockTicketMutex(&pool->tasks_tm); - { - if (pool->last_task) - { - pool->last_task->next = tasks.first; - } - else - { - pool->first_task = tasks.first; - } - pool->last_task = tasks.last; - Atomic64FetchAdd(&pool->tasks_count.v, num_tasks); - } - UnlockTicketMutex(&pool->tasks_tm); - } - - /* Wake workers */ - if (num_tasks >= W32_WakeAllWorkersThreshold) - { - WakeByAddressAll(&pool->tasks_count); - } - else - { - for (u32 i = 0; i < num_tasks; ++i) - { - WakeByAddressSingle(&pool->tasks_count); - } - } - - EndScratch(scratch); - return 1; -} diff --git a/src/base/base_win32/base_win32_job.h b/src/base/base_win32/base_win32_job.h deleted file mode 100644 index 75a73174..00000000 --- a/src/base/base_win32/base_win32_job.h +++ /dev/null @@ -1,205 +0,0 @@ -//////////////////////////////////////////////////////////// -//~ Win32 libs - -#pragma warning(push, 0) -# include -# include -# include -# include -# include -# include -# include -# include -# include -#pragma warning(pop) - -#pragma comment(lib, "kernel32") -#pragma comment(lib, "user32") -#pragma comment(lib, "shell32") -#pragma comment(lib, "ole32") -#pragma comment(lib, "winmm") -#pragma comment(lib, "dwmapi") -#pragma comment(lib, "synchronization") -#pragma comment(lib, "avrt") -#pragma comment(lib, "ws2_32.lib") - -//////////////////////////////////////////////////////////// -//~ Thread types - -#define W32_ThreadDef(name, arg_name) void name(void *arg_name) -typedef W32_ThreadDef(W32_ThreadFunc, data); - -Struct(W32_Thread) -{ - String thread_name; - W32_ThreadFunc *entry_point; - void *thread_udata; - - HANDLE handle; -}; - -//////////////////////////////////////////////////////////// -//~ Fiber types - -#define W32_FiberStackSize Mebi(1) -#define W32_FiberNamePrefixCstr "[" -#define W32_FiberNameSuffixCstr "]" -#define W32_FiberNameMaxSize 64 - -Enum(W32_FiberStatus) -{ - W32_FiberStatus_None = 0, - W32_FiberStatus_Suspending = 1, - W32_FiberStatus_Suspended = 2, -}; - -AlignedStruct(W32_Fiber, CachelineSize) -{ - /* ---------------------------------------------------- */ - void *addr; /* 08 bytes */ - /* ---------------------------------------------------- */ - char *name_cstr; /* 08 bytes */ - /* ---------------------------------------------------- */ - Atomic8 status; /* 01 bytes */ - Atomic8 virtual_yield; /* 01 bytes */ - i16 id; /* 02 bytes */ - i16 pool; /* 02 bytes */ - i16 return_id; /* 02 bytes */ - /* ---------------------------------------------------- */ - struct W32_Task *task; /* 08 bytes */ - /* ---------------------------------------------------- */ -}; -StaticAssert(alignof(W32_Fiber) == CachelineSize && sizeof(W32_Fiber) % CachelineSize == 0); /* False sharing validation */ - -//////////////////////////////////////////////////////////// -//~ Job pool types - -//- Worker ctx -AlignedStruct(W32_WorkerCtx, CachelineSize) -{ - JobPoolId pool_id; - i32 id; -}; - -//- Task -Struct(W32_Task) -{ - W32_Task *next; - Job *job; - u32 task_id; - i16 fiber_id; -}; - -Struct(W32_TaskList) -{ - W32_Task *first; - W32_Task *last; - u64 count; -}; - -//- Job pool -Struct(W32_JobPool) -{ - JobPoolId id; - String name; - JobPoolPriority priority; - i32 num_worker_threads; - W32_Thread **worker_threads; - W32_WorkerCtx *worker_contexts; - - /* Tasks */ - AlignedBlock(CachelineSize) - { - TicketMutex tasks_tm; - Atomic64Padded tasks_count; - W32_Task *first_task; - W32_Task *last_task; - }; - - /* TODO: Make lists wait-free */ - - /* Free fibers */ - AlignedBlock(CachelineSize) - { - TicketMutex free_fibers_tm; - i16 first_free_fiber_id; - }; - - /* Free jobs */ - AlignedBlock(CachelineSize) - { - TicketMutex free_jobs_tm; - Job *first_free_job; - }; - - /* Free tasks */ - AlignedBlock(CachelineSize) - { - TicketMutex free_tasks_tm; - W32_Task *first_free_task; - }; -}; - -//////////////////////////////////////////////////////////// -//~ State types - -/* Arbitrary threshold for determining when to use a looped WakeByAddressSingle vs a WakeByAddressAll to wake parked workers */ -#define W32_WakeAllWorkersThreshold 8 -#define W32_MaxJobPools 1024 - -Struct(W32_SharedJobState) -{ - JobPoolId async_pool; - JobPoolId hyper_pool; - - //- Fibers - AlignedBlock(CachelineSize) - { - TicketMutex fibers_tm; - i16 num_fibers; - W32_Fiber fibers[MaxFibers]; - Arena *fiber_names_arena; - }; - - //- Job pools - AlignedBlock(CachelineSize) - { - Atomic32 num_pools; - W32_JobPool job_pools[W32_MaxJobPools]; - }; - -} extern W32_shared_job_state; - -//////////////////////////////////////////////////////////// -//~ Startup - -void StartupJobs(void); - -//////////////////////////////////////////////////////////// -//~ Thread operations - -DWORD WINAPI W32_Win32ThreadProc(LPVOID vt); -W32_Thread *W32_StartThread(W32_ThreadFunc *entry_point, void *thread_udata, String thread_name); -b32 W32_TryEndThread(W32_Thread *thread, f32 timeout_seconds); -void W32_WaitEndThread(W32_Thread *thread); -JobDecl(W32_DummyJob, EmptySig); - -//////////////////////////////////////////////////////////// -//~ Fiber operations - -W32_Fiber *W32_AcquireFiber(W32_JobPool *pool); -void W32_ReleaseFiber(W32_JobPool *pool, W32_Fiber *fiber); -ForceInline W32_Fiber *W32_FiberFromId(i16 id); -void W32_SwitchToFiber(W32_Fiber *target); -void W32_YieldFiber(W32_Fiber *fiber, W32_Fiber *parent_fiber); - -//////////////////////////////////////////////////////////// -//~ Fiber entry - -void W32_FiberEntryPoint(void *wi32_fiber_data); -DWORD WINAPI W32_VirtualFiberEntryPoint(LPVOID arg); - -//////////////////////////////////////////////////////////// -//~ Job worker entry - -W32_ThreadDef(W32_JobWorkerEntryPoint, worker_ctx_arg); diff --git a/src/base/base_win32/base_win32_log.c b/src/base/base_win32/base_win32_log.c index 320f123c..0769b2e2 100644 --- a/src/base/base_win32/base_win32_log.c +++ b/src/base/base_win32/base_win32_log.c @@ -45,7 +45,6 @@ void W32_Log(i32 level, String msg) DateTime datetime = LocalDateTime(); i64 now_ns = TimeNs(); i32 thread_id = GetCurrentThreadId(); - i32 fiber_id = FiberId(); //- Log message to file /* TODO: Log asynchronously */ @@ -53,7 +52,7 @@ void W32_Log(i32 level, String msg) String shorthand = settings.shorthand; String msg_formatted = StringF( scratch.arena, - "[%F:%F:%F.%F] |%F| <%F> [%F] %F\n", + "[%F:%F:%F.%F] <%F> [%F] %F\n", /* Time */ FmtUintZ(datetime.hour, 2), @@ -64,9 +63,6 @@ void W32_Log(i32 level, String msg) /* Thread id */ FmtUintZ(thread_id, 5), - /* Fiber id */ - FmtUintZ(fiber_id, 4), - /* Level */ FmtString(shorthand), @@ -87,7 +83,6 @@ void W32_Log(i32 level, String msg) ev->time_ns = now_ns; ev->level = level; ev->thread_id = thread_id; - ev->fiber_id = fiber_id; ev->id = g->logs_count++; ev->level_id = g->log_level_counts[level]++; Atomic64Set(&g->readable_logs_count, g->logs_count); diff --git a/src/base/base_win32/base_win32_memory.c b/src/base/base_win32/base_win32_memory.c new file mode 100644 index 00000000..1c41bf0a --- /dev/null +++ b/src/base/base_win32/base_win32_memory.c @@ -0,0 +1,42 @@ +//////////////////////////////////////////////////////////// +//~ @hookimpl Memory allocation + +//- Reserve + +void *ReserveMemory(u64 size) +{ + void *ptr = VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS); + return ptr; +} + +void ReleaseMemory(void *address) +{ + VirtualFree(address, 0, MEM_RELEASE); +} + +//- Commit + +void *CommitMemory(void *address, u64 size) +{ + void *ptr = VirtualAlloc(address, size, MEM_COMMIT, PAGE_READWRITE); + return ptr; +} + +void DecommitMemory(void *address, u64 size) +{ + VirtualFree(address, size, MEM_DECOMMIT); +} + +//- Protect + +void SetMemoryReadonly(void *address, u64 size) +{ + DWORD old; + VirtualProtect(address, size, PAGE_READONLY, &old); +} + +void SetMemoryReadWrite(void *address, u64 size) +{ + DWORD old; + VirtualProtect(address, size, PAGE_READWRITE, &old); +} diff --git a/src/base/base_win32/base_win32_wave.c b/src/base/base_win32/base_win32_wave.c new file mode 100644 index 00000000..dff950e1 --- /dev/null +++ b/src/base/base_win32/base_win32_wave.c @@ -0,0 +1,79 @@ +//////////////////////////////////////////////////////////// +//~ Thread + +void W32_InitCurrentThread(String name) +{ + /* Init thread arenas */ + { + ThreadArenasCtx *tctx = &t_arena_ctx; + tctx->perm_arena = AcquireArena(Gibi(64)); + for (i32 i = 0; i < (i32)countof(tctx->scratch_arenas); ++i) + { + tctx->scratch_arenas[i] = AcquireArena(Gibi(64)); + } + } + + Arena *perm = PermArena(); + + /* Fixme: Set thread name */ + // SetThreadDescription(GetCurrentThread(), thread_name_wstr); + //LogInfoF("New thread \"%F\" created with ID %F", FmtString(StringFromCstrNoLimit(t->thread_name_cstr)), FmtUint(ThreadId())); + + /* Initialize COM */ + CoInitializeEx(0, COINIT_MULTITHREADED); +} + +DWORD WINAPI W32_ThreadProc(LPVOID thread_args_vp) +{ + W32_ThreadArgs *thread_args = (W32_ThreadArgs *)thread_args_vp; + W32_InitCurrentThread(thread_args->name); + thread_args->entry(thread_args->lane, thread_args->udata); + return 0; +} + +//////////////////////////////////////////////////////////// +//~ @hookimpl Dispatch + +void DispatchWave(u32 num_lanes, WaveLaneEntryFunc *entry, void *udata) +{ + /* FIXME: Impl */ + + Arena *perm = PermArena(); + + WaveCtx *wave_ctx = 0; + { + PushAlign(perm, CachelineSize); + wave_ctx = PushStruct(perm, WaveCtx); + PushAlign(perm, CachelineSize); + } + wave_ctx->lanes_count = num_lanes; + + for (u32 lane_idx = 0; lane_idx < num_lanes; ++lane_idx) + { + PushAlign(perm, CachelineSize); + WaveLaneCtx *lane_ctx = PushStruct(perm, WaveLaneCtx); + PushAlign(perm, CachelineSize); + + lane_ctx->idx = lane_idx; + lane_ctx->wave = wave_ctx; + + W32_ThreadArgs *thread_args = PushStruct(perm, W32_ThreadArgs); + thread_args->lane = lane_ctx; + thread_args->udata = udata; + thread_args->entry = entry; + + HANDLE handle = CreateThread(0, Mebi(4), W32_ThreadProc, thread_args, 0, 0); + if (!handle) + { + Panic(Lit("Failed to create thread")); + } + } +} + +//////////////////////////////////////////////////////////// +//~ @hookimpl Thread + +i32 ThreadId(void) +{ + return GetCurrentThreadId(); +} diff --git a/src/base/base_win32/base_win32_wave.h b/src/base/base_win32/base_win32_wave.h new file mode 100644 index 00000000..8fddf80d --- /dev/null +++ b/src/base/base_win32/base_win32_wave.h @@ -0,0 +1,17 @@ +//////////////////////////////////////////////////////////// +//~ Thread types + +Struct(W32_ThreadArgs) +{ + void *udata; + WaveLaneCtx *lane; + WaveLaneEntryFunc *entry; + String name; +}; + +//////////////////////////////////////////////////////////// +//~ Thread + +void W32_InitCurrentThread(String name); + +DWORD WINAPI W32_ThreadProc(LPVOID thread_args_vp); diff --git a/src/meta/meta.c b/src/meta/meta.c index a799613d..5ecc972c 100644 --- a/src/meta/meta.c +++ b/src/meta/meta.c @@ -103,26 +103,6 @@ LineCol LineColFromPos(String data, i64 pos) return result; } -//////////////////////////////////////////////////////////// -//~ OS command job - -Struct(RunCommandResult) -{ - OS_CommandResult cmd_result; - i64 elapsed_ns; -}; - -JobDecl(RunCommand, { String *cmds; RunCommandResult *results; }); -JobImpl(RunCommand, sig, id) -{ - i64 start_ns = TimeNs(); - Arena *arena = PermArena(); - String cmd = sig->cmds[id]; - RunCommandResult *result = &sig->results[id]; - result->cmd_result = OS_RunCommand(arena, cmd); - result->elapsed_ns = TimeNs() - start_ns; -} - //////////////////////////////////////////////////////////// //~ Compiler params @@ -148,6 +128,8 @@ Struct(CompilerParams) //////////////////////////////////////////////////////////// //~ Build step job +#if 0 + Enum(StepParamsFlag) { StepParamsFlag_None = 0, @@ -155,6 +137,7 @@ Enum(StepParamsFlag) StepParamsFlag_CompileShaders = (1 << 1), StepParamsFlag_CompileEmbeddedDirs = (1 << 2), StepParamsFlag_CompileArc = (1 << 3), + StepParamsFlag_RunCommand = (1 << 4), }; Struct(StepParams) @@ -166,6 +149,8 @@ Struct(StepParams) String arc_store; String arc_dir; + + String cmd; }; Struct(StepResult) @@ -173,6 +158,7 @@ Struct(StepResult) StringList obj_files; StringList output_lines; M_ErrorList meta_errors; + OS_CommandResult cmd_result; i32 return_code; i64 elapsed_ns; }; @@ -198,8 +184,7 @@ void InheritStepResults(Arena *arena, StepResult *dst, u64 srcs_count, StepResul } } -JobDecl(Step, { StepParams *params; StepResult *results; }); -JobImpl(Step, sig, id) +void Step(void) { StepParams *params = &sig->params[id]; StepParamsFlag flags = params->flags; @@ -409,12 +394,12 @@ JobImpl(Step, sig, id) if (flags & StepParamsFlag_CompileShaders) { - String shaders_out_dir = shader_store_name; - OS_Mkdir(shaders_out_dir); + String shader_store_name = shader_store_name; + OS_Mkdir(shader_store_name); { /* Remove all old shaders */ StringList files = ZI; - F_FilesFromDir(arena, &files, shaders_out_dir, F_IterFlag_None); + F_FilesFromDir(arena, &files, shader_store_name, F_IterFlag_None); for (StringListNode *n = files.first; n; n = n->next) { String file = n->s; @@ -427,7 +412,7 @@ JobImpl(Step, sig, id) } //- Generate GPU file & shader entries - String gpu_out_file = F_GetFull(arena, StringF(arena, "%F_gen_gpu.gpu", FmtString(cmdline.leaf_layer_name))); + String gpu_out_file = F_GetFull(arena, StringF(arena, "%F_gen_gpu.hlsl", FmtString(cmdline.leaf_layer_name))); Enum(ShaderEntryKind) { ShaderEntryKind_VS, ShaderEntryKind_PS, ShaderEntryKind_CS, }; Struct(ShaderEntry) { ShaderEntry *next; ShaderEntryKind kind; String name; }; ShaderEntry *first_shader_entry = 0; @@ -538,7 +523,7 @@ JobImpl(Step, sig, id) "cmd /c dxc.exe -T %F -E %F -Fo %F/%F %F %F %F", FmtString(target), FmtString(e->name), - FmtString(shaders_out_dir), + FmtString(shader_store_name), FmtString(e->name), FmtString(gpu_out_file), FmtString(StringFromList(arena, cp.defs, Lit(" "))), @@ -817,23 +802,76 @@ JobImpl(Step, sig, id) } } + ////////////////////////////// + //- Run command + + if (flags & StepParamsFlag_RunCommand) + { + result->cmd_result = OS_RunCommand(arena, cmd); + } + result->elapsed_ns = TimeNs() - start_ns; } +#endif + + + + + + + + +//////////////////////////////////////////////////////////// +//~ Stub funcs + + +/* FIXME: Move and implement these */ + + + + + + + + + + + + + + + + +Struct(CpuTopologyInfo) +{ + i32 num_logical_cores; +}; + +CpuTopologyInfo GetCpuTopologyInfo(void) +{ + /* FIXME: Implement this */ + CpuTopologyInfo res = ZI; + res.num_logical_cores = 8; + return res; +} + + + //////////////////////////////////////////////////////////// //~ Startup -JobDecl(Build, EmptySig); -JobImpl(Build, _, __) +void BuildEntryPoint(WaveLaneCtx *lane, void *udata) { Arena *arena = PermArena(); - M_ErrorList errors = ZI; + M_ErrorList meta_parse_errors = ZI; i32 ret = 0; ////////////////////////////// //- Dirty check /* Return rebuild code if metaprogram is dirty */ + if (lane->idx == 0) { /* Read old metahash */ u64 old_metahash = 0; @@ -873,10 +911,10 @@ JobImpl(Build, _, __) } } - ////////////////////////////// //- Command line + if (lane->idx == 0) { String layer_name = ZI; CommandlineArg arg = CommandlineArgFromName(Lit("layer")); @@ -993,34 +1031,552 @@ JobImpl(Build, _, __) ////////////////////////////// //- Parse - //- Lex layers - M_TokenFileList lexed = ZI; - { - StringList src_dirs = ZI; - PushStringToList(arena, &src_dirs, Lit("../src")); - lexed = M_TokensFromSrcDirs(arena, src_dirs); - } - - //- Parse layers - M_LayerList parsed = ZI; - { - parsed = M_LayersFromTokenFiles(arena, lexed); - } - - //- Flatten layers M_Layer flattened = ZI; + if (lane->idx == 0) { - StringList starting_layer_names = ZI; - PushStringToList(arena, &starting_layer_names, cmdline.leaf_layer_name); - flattened = M_GetFlattenedEntries(arena, parsed, starting_layer_names); + //- Lex layers + M_TokenFileList lexed = ZI; + { + StringList src_dirs = ZI; + PushStringToList(arena, &src_dirs, Lit("../src")); + lexed = M_TokensFromSrcDirs(arena, src_dirs); + } + + //- Parse layers + M_LayerList parsed = ZI; + { + parsed = M_LayersFromTokenFiles(arena, lexed); + } + + //- Flatten layers + { + StringList starting_layer_names = ZI; + PushStringToList(arena, &starting_layer_names, cmdline.leaf_layer_name); + flattened = M_GetFlattenedEntries(arena, parsed, starting_layer_names); + } + M_AppendErrors(arena, &meta_parse_errors, flattened.errors); } - M_AppendErrors(arena, &errors, flattened.errors); - ret = errors.count > 0; + + ////////////////////////////// + //- Prep + + /* + * ## Phase 1 + * + * + * | + * ------------------------------------------------------------- + * + * ## Phase 2 + * + * | | + * ------------------------------------------------------------- + * + * ## Phase 3 + * + * ------------------------------------------------------------- + * + * ## Phase 4 + * + * + */ + + Struct(SharedResults) + { + /* C compilation */ + AlignedBlock(CachelineSize) + { + M_ErrorList errors; + StringList obj_files; + StringList output; + i32 return_code; + } c_comp; + + /* HLSL compilation */ + AlignedBlock(CachelineSize) + { + M_ErrorList errors; + StringList obj_files; + StringList output; + i32 return_code; + } gpu_comp; + + /* Resource generation */ + AlignedBlock(CachelineSize) + { + M_ErrorList errors; + StringList obj_files; + StringList output; + i32 return_code; + } res_comp; + }; + + SharedResults *shared = 0; + if (lane->idx == 0) + { + shared = PushStruct(arena, SharedResults); + } + WaveSyncBroadcast(lane, 0, &shared); + + String shader_store_name = Lit("ShadersStore"); + + ////////////////////////////// + //- Compile C + + { + /* Compile C */ + { + //- Generate C file + String c_out_file = F_GetFull(arena, StringF(arena, "%F_gen_c.c", FmtString(cmdline.leaf_layer_name))); + { + StringList c_store_lines = ZI; + StringList c_shader_lines = ZI; + StringList c_include_lines = ZI; + StringList c_startup_lines = ZI; + { + for (M_Entry *entry = flattened.first; entry->valid; entry = entry->next) + { + M_EntryKind kind = entry->kind; + M_Token *entry_tok = entry->name_token; + M_Token *arg0_tok = entry->arg_tokens[0]; + M_Token *arg1_tok = entry->arg_tokens[1]; + switch (kind) + { + default: break; + case M_EntryKind_EmbedDir: + { + if (arg0_tok->valid && arg1_tok->valid) + { + String store_name = arg0_tok->s; + String token_file = arg1_tok->file->name; + String token_parent_dir = F_GetParentDir(token_file); + String arg_dir = arg1_tok->s; + String full = F_GetFullCrossPlatform(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_dir))); + if (F_IsDir(full)) + { + u64 hash = HashFnv64(Fnv64Basis, StringF(arena, "%F/", FmtString(store_name))); + String line = StringF(arena, "ResourceStore %F = { 0x%F };", FmtString(store_name), FmtHex(hash)); + PushStringToList(arena, &c_store_lines, line); + } + else + { + String err = StringF(arena, "Directory '%F' not found", FmtString(full)); + M_PushError(arena, &shared->c_comp.errors, arg1_tok, err); + } + } + else + { + M_PushError(arena, &shared->c_comp.errors, entry_tok, Lit("Expected resource store & directory name")); + } + } break; + case M_EntryKind_VertexShader: + case M_EntryKind_PixelShader: + case M_EntryKind_ComputeShader: + { + if (arg0_tok->valid) + { + String shader_type = kind == M_EntryKind_VertexShader ? Lit("VertexShader") + : kind == M_EntryKind_PixelShader ? Lit("PixelShader") + : kind == M_EntryKind_ComputeShader ? Lit("ComputeShader") + : Lit(""); + String shader_name = arg0_tok->s; + u64 hash = HashFnv64(Fnv64Basis, StringF(arena, "%F/%F", FmtString(shader_store_name), FmtString(shader_name))); + String line = StringF(arena, "%F %F = { 0x%F };", FmtString(shader_type), FmtString(shader_name), FmtHex(hash)); + PushStringToList(arena, &c_shader_lines, line); + } + else + { + M_PushError(arena, &shared->c_comp.errors, entry_tok, Lit("Expected shader name")); + } + } break; + case M_EntryKind_IncludeC: + { + if (arg0_tok->valid) + { + String token_file = arg0_tok->file->name; + String token_parent_dir = F_GetParentDir(token_file); + String arg_file = arg0_tok->s; + String full = F_GetFull(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_file))); + if (F_IsFile(full)) + { + String line = StringF(arena, "#include \"%F\"", FmtString(full)); + PushStringToList(arena, &c_include_lines, line); + } + else + { + String err = StringF(arena, "File '%F' not found", FmtString(full)); + M_PushError(arena, &shared->c_comp.errors, arg0_tok, err); + } + } + else + { + M_PushError(arena, &shared->c_comp.errors, entry_tok, Lit("Expected file name")); + } + } break; + case M_EntryKind_Startup: + { + if (arg0_tok->valid) + { + String startup = arg0_tok->s; + String line = StringF(arena, " %F();", FmtString(startup)); + PushStringToList(arena, &c_startup_lines, line); + } + else + { + M_PushError(arena, &shared->c_comp.errors, entry_tok, Lit("Expected startup function name")); + } + } break; + } + } + } + if (&shared->c_comp.errors.count == 0) + { + StringList c_out_lines = ZI; + PushStringToList(arena, &c_out_lines, Lit("// Auto generated file")); + /* Include base layer */ + { + String base_inc_path = F_GetFull(arena, Lit("../src/base/base_inc.h")); + PushStringToList(arena, &c_out_lines, Lit("")); + PushStringToList(arena, &c_out_lines, Lit("//- Base layer includes")); + PushStringToList(arena, &c_out_lines, StringF(arena, "#include \"%F\"", FmtString(base_inc_path))); + } + /* Define resource stores */ + if (c_store_lines.count > 0) + { + PushStringToList(arena, &c_out_lines, Lit("")); + PushStringToList(arena, &c_out_lines, Lit("//- Resource stores")); + for (StringListNode *n = c_store_lines.first; n; n = n->next) + { + PushStringToList(arena, &c_out_lines, n->s); + } + } + /* Define shaders */ + if (c_shader_lines.count > 0) + { + PushStringToList(arena, &c_out_lines, Lit("")); + PushStringToList(arena, &c_out_lines, Lit("//- Shaders")); + for (StringListNode *n = c_shader_lines.first; n; n = n->next) + { + PushStringToList(arena, &c_out_lines, n->s); + } + } + /* Include dependency layers */ + if (c_include_lines.count > 0) + { + PushStringToList(arena, &c_out_lines, Lit("")); + PushStringToList(arena, &c_out_lines, Lit("//- Dependency graph includes")); + for (StringListNode *n = c_include_lines.first; n; n = n->next) + { + PushStringToList(arena, &c_out_lines, n->s); + } + } + /* Define StartupLayers */ + { + PushStringToList(arena, &c_out_lines, Lit("")); + PushStringToList(arena, &c_out_lines, Lit("//- Startup")); + PushStringToList(arena, &c_out_lines, Lit("void StartupLayers(void)")); + PushStringToList(arena, &c_out_lines, Lit("{")); + for (StringListNode *n = c_startup_lines.first; n; n = n->next) + { + PushStringToList(arena, &c_out_lines, n->s); + } + PushStringToList(arena, &c_out_lines, Lit("}")); + } + /* Write to file */ + PushStringToList(arena, &c_out_lines, Lit("")); + String c_out = StringFromList(arena, c_out_lines, Lit("\n")); + F_ClearWrite(c_out_file, c_out); + } + } + + //- Compile C + String c_out_obj_file = StringF(arena, "%F_gen_c.obj", FmtString(cmdline.leaf_layer_name)); + PushStringToList(arena, &shared->c_comp.obj_files, c_out_obj_file); + if (shared->c_comp.errors.count == 0) + { + String cmd = StringF(arena, + "cmd /c cl.exe /c %F -Fo:%F %F %F %F %F", + FmtString(c_out_file), + FmtString(c_out_obj_file), + FmtString(StringFromList(arena, cp.flags_msvc, Lit(" "))), + FmtString(StringFromList(arena, cp.compiler_only_flags_msvc, Lit(" "))), + FmtString(StringFromList(arena, cp.warnings_msvc, Lit(" "))), + FmtString(StringFromList(arena, cp.defs, Lit(" ")))); + OS_CommandResult cmd_result = OS_RunCommand(arena, cmd); + String cmd_output = TrimWhitespace(cmd_result.output); + if (cmd_output.len > 0) + { + PushStringToList(arena, &shared->c_comp.output, cmd_output); + } + shared->c_comp.return_code = cmd_result.code; + } + } + } + + ////////////////////////////// + //- Compile shaders + + { + OS_Mkdir(shader_store_name); + { + /* Remove all old shaders */ + StringList files = ZI; + F_FilesFromDir(arena, &files, shader_store_name, F_IterFlag_None); + for (StringListNode *n = files.first; n; n = n->next) + { + String file = n->s; + /* Safety check to prevent non-shader files from being removed */ + if (StringEndsWith(file, Lit("VS")) || StringEndsWith(file, Lit("CS")) || StringEndsWith(file, Lit("PS"))) + { + OS_Rm(n->s); + } + } + } + + //- Generate GPU file & shader entries + String gpu_out_file = F_GetFull(arena, StringF(arena, "%F_gen_gpu.hlsl", FmtString(cmdline.leaf_layer_name))); + Enum(ShaderEntryKind) { ShaderEntryKind_VS, ShaderEntryKind_PS, ShaderEntryKind_CS, }; + Struct(ShaderEntry) { ShaderEntry *next; ShaderEntryKind kind; String name; }; + ShaderEntry *first_shader_entry = 0; + ShaderEntry *last_shader_entry = 0; + u64 shader_entries_count = 0; + { + StringList gpu_include_lines = ZI; + { + for (M_Entry *entry = flattened.first; entry->valid; entry = entry->next) + { + M_EntryKind kind = entry->kind; + M_Token *entry_tok = entry->name_token; + M_Token *arg0_tok = entry->arg_tokens[0]; + M_Token *arg1_tok = entry->arg_tokens[1]; + switch (kind) + { + default: break; + case M_EntryKind_IncludeGpu: + { + if (arg0_tok->valid) + { + String token_file = arg0_tok->file->name; + String token_parent_dir = F_GetParentDir(token_file); + String arg_file = arg0_tok->s; + String full = F_GetFull(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_file))); + if (F_IsFile(full)) + { + String line = StringF(arena, "#include \"%F\"", FmtString(full)); + PushStringToList(arena, &gpu_include_lines, line); + } + else + { + String err = StringF(arena, "File '%F' not found", FmtString(full)); + M_PushError(arena, &shared->gpu_comp.errors, arg0_tok, err); + } + } + else + { + M_PushError(arena, &shared->gpu_comp.errors, entry_tok, Lit("Expected file name")); + } + } break; + case M_EntryKind_VertexShader: + case M_EntryKind_PixelShader: + case M_EntryKind_ComputeShader: + { + if (arg0_tok->valid) + { + ShaderEntryKind shader_kind = kind == M_EntryKind_VertexShader ? ShaderEntryKind_VS + : kind == M_EntryKind_PixelShader ? ShaderEntryKind_PS + : kind == M_EntryKind_ComputeShader ? ShaderEntryKind_CS + : ShaderEntryKind_VS; + String shader_name = arg0_tok->s; + ShaderEntry *e = PushStruct(arena, ShaderEntry); + e->kind = shader_kind; + e->name = shader_name; + SllQueuePush(first_shader_entry, last_shader_entry, e); + ++shader_entries_count; + } + else + { + M_PushError(arena, &shared->gpu_comp.errors, entry_tok, Lit("Expected shader name")); + } + } break; + } + } + } + if (shared->gpu_comp.errors.count == 0) + { + StringList gpu_out_lines = ZI; + PushStringToList(arena, &gpu_out_lines, Lit("// Auto generated file")); + /* Include base layer */ + { + String base_inc_path = F_GetFull(arena, Lit("../src/base/base_inc.h")); + PushStringToList(arena, &gpu_out_lines, Lit("")); + PushStringToList(arena, &gpu_out_lines, Lit("//- Base layer includes")); + PushStringToList(arena, &gpu_out_lines, StringF(arena, "#include \"%F\"", FmtString(base_inc_path))); + } + /* Include dependency layers */ + if (gpu_out_lines.count > 0) + { + PushStringToList(arena, &gpu_out_lines, Lit("")); + PushStringToList(arena, &gpu_out_lines, Lit("//- Dependency graph includes")); + for (StringListNode *n = gpu_include_lines.first; n; n = n->next) + { + PushStringToList(arena, &gpu_out_lines, n->s); + } + } + /* Write to file */ + PushStringToList(arena, &gpu_out_lines, Lit("")); + String c_out = StringFromList(arena, gpu_out_lines, Lit("\n")); + F_ClearWrite(gpu_out_file, c_out); + } + } + + //- Compile shaders + String *compile_cmds = PushStructs(arena, String, shader_entries_count); + { + i32 i = 0; + for (ShaderEntry *e = first_shader_entry; e; e = e->next) + { + String target = e->kind == ShaderEntryKind_VS ? Lit("vs_6_6") + : e->kind == ShaderEntryKind_PS ? Lit("ps_6_6") + : e->kind == ShaderEntryKind_CS ? Lit("cs_6_6") + : Lit("vs_6_6"); + String *compile_cmd = &compile_cmds[i]; + *compile_cmd = StringF(arena, + "cmd /c dxc.exe -T %F -E %F -Fo %F/%F %F %F %F", + FmtString(target), + FmtString(e->name), + FmtString(shader_store_name), + FmtString(e->name), + FmtString(gpu_out_file), + FmtString(StringFromList(arena, cp.defs, Lit(" "))), + FmtString(StringFromList(arena, cp.flags_dxc, Lit(" ")))); + ++i; + } + } + + for (ShaderEntry *e = first_shader_entry; e; e = e->next) + { + String target = e->kind == ShaderEntryKind_VS ? Lit("vs_6_6") + : e->kind == ShaderEntryKind_PS ? Lit("ps_6_6") + : e->kind == ShaderEntryKind_CS ? Lit("cs_6_6") + : Lit("vs_6_6"); + String compile_cmd = StringF(arena, + "cmd /c dxc.exe -T %F -E %F -Fo %F/%F %F %F %F", + FmtString(target), + FmtString(e->name), + FmtString(shader_store_name), + FmtString(e->name), + FmtString(gpu_out_file), + FmtString(StringFromList(arena, cp.defs, Lit(" "))), + FmtString(StringFromList(arena, cp.flags_dxc, Lit(" ")))); + + OS_CommandResult cmd_result = OS_RunCommand(arena, compile_cmd); + + if (cmd_result.code == 0) + { + // f64 elapsed = SecondsFromNs(cmd_result.elapsed_ns); + f64 elapsed = 0; + PushStringToList(arena, &shared->gpu_comp.output, StringF(arena, "%F:%F %Fs", FmtString(F_GetFileName(gpu_out_file)), FmtString(e->name), FmtFloat(elapsed))); + if (cmd_result.output.len > 0) + { + PushStringToList(arena, &shared->gpu_comp.output, cmd_result.output); + } + cmd_result.code = cmd_result.code; + } + } + + //- Build embedded shader archive + // if (result->code == 0) + // { + // StepParams arc_params = *params; + // StepResult arc_results = ZI; + + // arc_params.flags = StepParamsFlag_CompileArc; + // arc_params.arc_store = shader_store_name; + // arc_params.arc_dir = shader_store_name; + + // u32 job_count = 0; Fence job_fence = ZI; + // job_count += RunJob(Step, .fence = &job_fence, .sig.params = &arc_params, .sig.results = &arc_results); + // YieldOnFence(&job_fence, job_count); + // InheritStepResults(arena, result, 1, &arc_results); + // } + } + + ////////////////////////////// + //- Compile resources + + + ////////////////////////////// + //- Link + + { + String exe_file = StringF(arena, "%F.exe", FmtString(cmdline.leaf_layer_name)); + { + /* Wait for exe to become writable (wait for program to close) */ + f32 timeout = 1.0; + OS_File file = OS_OpenFile(exe_file, OS_FileFlag_Write, NsFromSeconds(timeout)); + OS_CloseFile(file); + } + + i64 link_elapsed_ns = 0; + if (ret == 0) + { + i64 start_ns = TimeNs(); + String program_obj_files_str = StringFromList(arena, shared->c_comp.obj_files, Lit(" ")); + String shader_obj_files_str = StringFromList(arena, shared->gpu_comp.obj_files, Lit(" ")); + String resource_obj_files_str = StringFromList(arena, shared->res_comp.obj_files, Lit(" ")); + + String cmd = StringF(arena, + "cmd /c link.exe %F %F %F /OUT:%F %F %F", + FmtString(program_obj_files_str), + FmtString(shader_obj_files_str), + FmtString(resource_obj_files_str), + FmtString(exe_file), + FmtString(StringFromList(arena, cp.flags_msvc, Lit(" "))), + FmtString(StringFromList(arena, cp.linker_only_flags_msvc, Lit(" ")))); + OS_CommandResult result = OS_RunCommand(arena, cmd); + String output = TrimWhitespace(result.output); + if (output.len > 0) + { + EchoLine(output); + } + ret = result.code; + link_elapsed_ns = TimeNs() - start_ns; + } + + if (ret == 0) + { + EchoLine(StringF(arena, ">>>>> Linked in %Fs", FmtFloat(SecondsFromNs(link_elapsed_ns)))); + } + } + + + + + + + + + + + + + + + + + + + + + + ////////////////////////////// //- Compile & link - if (errors.count == 0) +#if 0 + ret = meta_parse_errors.count > 0; + if (ret == 0) { //- Compile StepParams params_array[3] = ZI; @@ -1115,12 +1671,13 @@ JobImpl(Build, _, __) EchoLine(StringF(arena, ">>>>> Linked in %Fs", FmtFloat(SecondsFromNs(link_elapsed_ns)))); } } +#endif ////////////////////////////// - //- Echo errors + //- Echo meta errors - for (M_Error *e = errors.first; e; e = e->next) + for (M_Error *e = meta_parse_errors.first; e; e = e->next) { String msg = ZI; M_Token *token = e->token; @@ -1165,5 +1722,8 @@ JobImpl(Build, _, __) void StartupLayers(void) { OS_Startup(); - RunJob(Build, .pool = HyperPool()); + + CpuTopologyInfo cpu_info = GetCpuTopologyInfo(); + // DispatchWave(cpu_info.num_logical_cores - 1, BuildEntryPoint, 0); + DispatchWave(1, BuildEntryPoint, 0); } diff --git a/src/meta/meta.h b/src/meta/meta.h index 8b137891..e69de29b 100644 --- a/src/meta/meta.h +++ b/src/meta/meta.h @@ -1 +0,0 @@ -