threading refactor progress

This commit is contained in:
jacob 2025-12-05 23:40:26 -06:00
parent 94210da6be
commit 4a787ee618
26 changed files with 900 additions and 1516 deletions

View File

@ -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 <Windows.h>
#include <combaseapi.h>
#include <dcommon.h>
#include <initguid.h>
#include <unknwn.h>
#include <objbase.h>
#include <uuids.h>
#include <Knownfolders.h>
#pragma warning(pop)
#if IsLanguageC
#include <stdint.h>
#include <stdarg.h>
#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 <stdint.h>
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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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"

View File

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

View File

@ -13,7 +13,6 @@ Struct(LogEvent)
i32 level;
i32 thread_id;
i32 fiber_id;
};
Struct(LogEventsArray)

View File

@ -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

View File

@ -1,5 +1,5 @@
////////////////////////////////////////////////////////////
//~ Memory allocation
//~ @hookdecl Memory allocation
//- Reserve
void *ReserveMemory(u64 size);

View File

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

View File

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

7
src/base/base_wave.c Normal file
View File

@ -0,0 +1,7 @@
////////////////////////////////////////////////////////////
//~ Wave sync ops
void WaveSyncBroadcast_(WaveLaneCtx *lane_ctx, i32 broadcast_lane_idx, void *broadcast_ptr, u64 broadcast_size)
{
/* FIXME: Impl */
}

31
src/base/base_wave.h Normal file
View File

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

View File

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

View File

@ -1,14 +1,48 @@
////////////////////////////////////////////////////////////
//~ Win32 libs
//- Windows headers
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#pragma warning(push, 0)
#include <Windows.h>
#include <combaseapi.h>
#include <dcommon.h>
#include <initguid.h>
#include <unknwn.h>
#include <objbase.h>
#include <uuids.h>
#include <Knownfolders.h>
#include <WinSock2.h>
#include <TlHelp32.h>
#include <WS2tcpip.h>
#include <windowsx.h>
#include <ShlObj_core.h>
#include <fileapi.h>
#include <dwmapi.h>
#include <avrt.h>
#include <shellapi.h>
#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

View File

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

View File

@ -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"

View File

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

View File

@ -1,205 +0,0 @@
////////////////////////////////////////////////////////////
//~ Win32 libs
#pragma warning(push, 0)
# include <WinSock2.h>
# include <TlHelp32.h>
# include <WS2tcpip.h>
# include <windowsx.h>
# include <ShlObj_core.h>
# include <fileapi.h>
# include <dwmapi.h>
# include <avrt.h>
# include <shellapi.h>
#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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +1031,9 @@ JobImpl(Build, _, __)
//////////////////////////////
//- Parse
M_Layer flattened = ZI;
if (lane->idx == 0)
{
//- Lex layers
M_TokenFileList lexed = ZI;
{
@ -1008,19 +1049,534 @@ JobImpl(Build, _, __)
}
//- Flatten layers
M_Layer flattened = ZI;
{
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, &errors, flattened.errors);
ret = errors.count > 0;
M_AppendErrors(arena, &meta_parse_errors, flattened.errors);
}
//////////////////////////////
//- Prep
/*
* ## Phase 1
*
* <C compile oscmd> <shader compile oscmds>
* |
* <FULL BARRIER> -------------------------------------------------------------
*
* ## Phase 2
* <shader resource arcs> <embedded resource arcs>
* | |
* <FULL BARRIER> -------------------------------------------------------------
*
* ## Phase 3
* <shader resource oscmd> <embedded resource oscmds>
* <FULL BARRIER> -------------------------------------------------------------
*
* ## Phase 4
*
* <link oscmd>
*/
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);
}

View File

@ -1 +0,0 @@