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