power_play/src/base/base_futex.c

186 lines
5.2 KiB
C

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;
FutexNeqListBin *bin = &g->neq_bins[RandU64FromSeed((u64)addr) % countof(g->neq_bins)];
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;
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 = PushDry(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);
}