#include <ck_backoff.h>
#include <ck_cc.h>
#include <ck_hp.h>
#include <ck_pr.h>
#include <ck_stack.h>
#include <ck_stdbool.h>
#include <ck_stddef.h>
#include <ck_stdlib.h>
#include <ck_string.h>
CK_STACK_CONTAINER(struct ck_hp_record, global_entry, ck_hp_record_container)
CK_STACK_CONTAINER(struct ck_hp_hazard, pending_entry, ck_hp_hazard_container)
void
ck_hp_init(struct ck_hp *state,
unsigned int degree,
unsigned int threshold,
ck_hp_destructor_t destroy)
{
state->threshold = threshold;
state->degree = degree;
state->destroy = destroy;
state->n_subscribers = 0;
state->n_free = 0;
ck_stack_init(&state->subscribers);
ck_pr_fence_store();
return;
}
void
ck_hp_set_threshold(struct ck_hp *state, unsigned int threshold)
{
ck_pr_store_uint(&state->threshold, threshold);
return;
}
struct ck_hp_record *
ck_hp_recycle(struct ck_hp *global)
{
struct ck_hp_record *record;
ck_stack_entry_t *entry;
int state;
if (ck_pr_load_uint(&global->n_free) == 0)
return NULL;
CK_STACK_FOREACH(&global->subscribers, entry) {
record = ck_hp_record_container(entry);
if (ck_pr_load_int(&record->state) == CK_HP_FREE) {
ck_pr_fence_load();
state = ck_pr_fas_int(&record->state, CK_HP_USED);
if (state == CK_HP_FREE) {
ck_pr_dec_uint(&global->n_free);
return record;
}
}
}
return NULL;
}
void
ck_hp_unregister(struct ck_hp_record *entry)
{
entry->n_pending = 0;
entry->n_peak = 0;
entry->n_reclamations = 0;
ck_stack_init(&entry->pending);
ck_pr_fence_store();
ck_pr_store_int(&entry->state, CK_HP_FREE);
ck_pr_inc_uint(&entry->global->n_free);
return;
}
void
ck_hp_register(struct ck_hp *state,
struct ck_hp_record *entry,
void **pointers)
{
entry->state = CK_HP_USED;
entry->global = state;
entry->pointers = pointers;
entry->n_pending = 0;
entry->n_peak = 0;
entry->n_reclamations = 0;
memset(pointers, 0, state->degree * sizeof(void *));
ck_stack_init(&entry->pending);
ck_pr_fence_store();
ck_stack_push_upmc(&state->subscribers, &entry->global_entry);
ck_pr_inc_uint(&state->n_subscribers);
return;
}
static int
hazard_compare(const void *a, const void *b)
{
void * const *x;
void * const *y;
x = a;
y = b;
return ((*x > *y) - (*x < *y));
}
CK_CC_INLINE static bool
ck_hp_member_scan(ck_stack_entry_t *entry, unsigned int degree, void *pointer)
{
struct ck_hp_record *record;
unsigned int i;
void *hazard;
do {
record = ck_hp_record_container(entry);
if (ck_pr_load_int(&record->state) == CK_HP_FREE)
continue;
if (ck_pr_load_ptr(&record->pointers) == NULL)
continue;
for (i = 0; i < degree; i++) {
hazard = ck_pr_load_ptr(&record->pointers[i]);
if (hazard == pointer)
return (true);
}
} while ((entry = CK_STACK_NEXT(entry)) != NULL);
return (false);
}
CK_CC_INLINE static void *
ck_hp_member_cache(struct ck_hp *global, void **cache, unsigned int *n_hazards)
{
struct ck_hp_record *record;
ck_stack_entry_t *entry;
unsigned int hazards = 0;
unsigned int i;
void *pointer;
CK_STACK_FOREACH(&global->subscribers, entry) {
record = ck_hp_record_container(entry);
if (ck_pr_load_int(&record->state) == CK_HP_FREE)
continue;
if (ck_pr_load_ptr(&record->pointers) == NULL)
continue;
for (i = 0; i < global->degree; i++) {
if (hazards > CK_HP_CACHE)
break;
pointer = ck_pr_load_ptr(&record->pointers[i]);
if (pointer != NULL)
cache[hazards++] = pointer;
}
}
*n_hazards = hazards;
return (entry);
}
void
ck_hp_reclaim(struct ck_hp_record *thread)
{
struct ck_hp_hazard *hazard;
struct ck_hp *global = thread->global;
unsigned int n_hazards;
void **cache, *marker, *match;
ck_stack_entry_t *previous, *entry, *next;
cache = thread->cache;
marker = ck_hp_member_cache(global, cache, &n_hazards);
qsort(cache, n_hazards, sizeof(void *), hazard_compare);
previous = NULL;
CK_STACK_FOREACH_SAFE(&thread->pending, entry, next) {
hazard = ck_hp_hazard_container(entry);
match = bsearch(&hazard->pointer, cache, n_hazards,
sizeof(void *), hazard_compare);
if (match != NULL) {
previous = entry;
continue;
}
if (marker != NULL &&
ck_hp_member_scan(marker, global->degree, hazard->pointer)) {
previous = entry;
continue;
}
thread->n_pending -= 1;
if (previous)
CK_STACK_NEXT(previous) = CK_STACK_NEXT(entry);
else
CK_STACK_FIRST(&thread->pending) = CK_STACK_NEXT(entry);
global->destroy(hazard->data);
thread->n_reclamations++;
}
return;
}
void
ck_hp_retire(struct ck_hp_record *thread,
struct ck_hp_hazard *hazard,
void *data,
void *pointer)
{
ck_pr_store_ptr(&hazard->pointer, pointer);
ck_pr_store_ptr(&hazard->data, data);
ck_stack_push_spnc(&thread->pending, &hazard->pending_entry);
thread->n_pending += 1;
if (thread->n_pending > thread->n_peak)
thread->n_peak = thread->n_pending;
return;
}
void
ck_hp_free(struct ck_hp_record *thread,
struct ck_hp_hazard *hazard,
void *data,
void *pointer)
{
struct ck_hp *global;
global = ck_pr_load_ptr(&thread->global);
ck_pr_store_ptr(&hazard->data, data);
ck_pr_store_ptr(&hazard->pointer, pointer);
ck_stack_push_spnc(&thread->pending, &hazard->pending_entry);
thread->n_pending += 1;
if (thread->n_pending > thread->n_peak)
thread->n_peak = thread->n_pending;
if (thread->n_pending >= global->threshold)
ck_hp_reclaim(thread);
return;
}
void
ck_hp_purge(struct ck_hp_record *thread)
{
ck_backoff_t backoff = CK_BACKOFF_INITIALIZER;
while (thread->n_pending > 0) {
ck_hp_reclaim(thread);
if (thread->n_pending > 0)
ck_backoff_eb(&backoff);
}
return;
}