blob: f6dd2ee29e9fd7bf48aa3b7b1715de428dc683bc [file] [log] [blame]
/*
* Copyright 2014-2015 Olivier Houchard.
* Copyright 2012-2015 Samy Al Bahra.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <ck_cc.h>
#include <ck_rhs.h>
#include <ck_limits.h>
#include <ck_md.h>
#include <ck_pr.h>
#include <ck_stdint.h>
#include <ck_stdbool.h>
#include <ck_string.h>
#include "ck_internal.h"
#ifndef CK_RHS_PROBE_L1_SHIFT
#define CK_RHS_PROBE_L1_SHIFT 3ULL
#endif /* CK_RHS_PROBE_L1_SHIFT */
#define CK_RHS_PROBE_L1 (1 << CK_RHS_PROBE_L1_SHIFT)
#define CK_RHS_PROBE_L1_MASK (CK_RHS_PROBE_L1 - 1)
#ifndef CK_RHS_PROBE_L1_DEFAULT
#define CK_RHS_PROBE_L1_DEFAULT CK_MD_CACHELINE
#endif
#define CK_RHS_VMA_MASK ((uintptr_t)((1ULL << CK_MD_VMA_BITS) - 1))
#define CK_RHS_VMA(x) \
((void *)((uintptr_t)(x) & CK_RHS_VMA_MASK))
#define CK_RHS_EMPTY NULL
#define CK_RHS_G (1024)
#define CK_RHS_G_MASK (CK_RHS_G - 1)
#if defined(CK_F_PR_LOAD_8) && defined(CK_F_PR_STORE_8)
#define CK_RHS_WORD uint8_t
#define CK_RHS_WORD_MAX UINT8_MAX
#define CK_RHS_STORE(x, y) ck_pr_store_8(x, y)
#define CK_RHS_LOAD(x) ck_pr_load_8(x)
#elif defined(CK_F_PR_LOAD_16) && defined(CK_F_PR_STORE_16)
#define CK_RHS_WORD uint16_t
#define CK_RHS_WORD_MAX UINT16_MAX
#define CK_RHS_STORE(x, y) ck_pr_store_16(x, y)
#define CK_RHS_LOAD(x) ck_pr_load_16(x)
#elif defined(CK_F_PR_LOAD_32) && defined(CK_F_PR_STORE_32)
#define CK_RHS_WORD uint32_t
#define CK_RHS_WORD_MAX UINT32_MAX
#define CK_RHS_STORE(x, y) ck_pr_store_32(x, y)
#define CK_RHS_LOAD(x) ck_pr_load_32(x)
#else
#error "ck_rhs is not supported on your platform."
#endif
#define CK_RHS_MAX_WANTED 0xffff
enum ck_rhs_probe_behavior {
CK_RHS_PROBE = 0, /* Default behavior. */
CK_RHS_PROBE_RH, /* Short-circuit if RH slot found. */
CK_RHS_PROBE_INSERT, /* Short-circuit on probe bound if tombstone found. */
CK_RHS_PROBE_ROBIN_HOOD,/* Look for the first slot available for the entry we are about to replace, only used to internally implement Robin Hood */
CK_RHS_PROBE_NO_RH, /* Don't do the RH dance */
};
struct ck_rhs_entry_desc {
unsigned int probes;
unsigned short wanted;
CK_RHS_WORD probe_bound;
bool in_rh;
const void *entry;
} CK_CC_ALIGN(16);
struct ck_rhs_no_entry_desc {
unsigned int probes;
unsigned short wanted;
CK_RHS_WORD probe_bound;
bool in_rh;
} CK_CC_ALIGN(8);
typedef long ck_rhs_probe_cb_t(struct ck_rhs *hs,
struct ck_rhs_map *map,
unsigned long *n_probes,
long *priority,
unsigned long h,
const void *key,
const void **object,
unsigned long probe_limit,
enum ck_rhs_probe_behavior behavior);
struct ck_rhs_map {
unsigned int generation[CK_RHS_G];
unsigned int probe_maximum;
unsigned long mask;
unsigned long step;
unsigned int probe_limit;
unsigned long n_entries;
unsigned long capacity;
unsigned long size;
unsigned long max_entries;
char offset_mask;
union {
struct ck_rhs_entry_desc *descs;
struct ck_rhs_no_entry {
const void **entries;
struct ck_rhs_no_entry_desc *descs;
} no_entries;
} entries;
bool read_mostly;
ck_rhs_probe_cb_t *probe_func;
};
static CK_CC_INLINE const void *
ck_rhs_entry(struct ck_rhs_map *map, long offset)
{
if (map->read_mostly)
return (map->entries.no_entries.entries[offset]);
else
return (map->entries.descs[offset].entry);
}
static CK_CC_INLINE const void **
ck_rhs_entry_addr(struct ck_rhs_map *map, long offset)
{
if (map->read_mostly)
return (&map->entries.no_entries.entries[offset]);
else
return (&map->entries.descs[offset].entry);
}
static CK_CC_INLINE struct ck_rhs_entry_desc *
ck_rhs_desc(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
return ((struct ck_rhs_entry_desc *)(void *)&map->entries.no_entries.descs[offset]);
else
return (&map->entries.descs[offset]);
}
static CK_CC_INLINE void
ck_rhs_wanted_inc(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
map->entries.no_entries.descs[offset].wanted++;
else
map->entries.descs[offset].wanted++;
}
static CK_CC_INLINE unsigned int
ck_rhs_probes(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
return (map->entries.no_entries.descs[offset].probes);
else
return (map->entries.descs[offset].probes);
}
static CK_CC_INLINE void
ck_rhs_set_probes(struct ck_rhs_map *map, long offset, unsigned int value)
{
if (CK_CC_UNLIKELY(map->read_mostly))
map->entries.no_entries.descs[offset].probes = value;
else
map->entries.descs[offset].probes = value;
}
static CK_CC_INLINE CK_RHS_WORD
ck_rhs_probe_bound(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
return (map->entries.no_entries.descs[offset].probe_bound);
else
return (map->entries.descs[offset].probe_bound);
}
static CK_CC_INLINE CK_RHS_WORD *
ck_rhs_probe_bound_addr(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
return (&map->entries.no_entries.descs[offset].probe_bound);
else
return (&map->entries.descs[offset].probe_bound);
}
static CK_CC_INLINE bool
ck_rhs_in_rh(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
return (map->entries.no_entries.descs[offset].in_rh);
else
return (map->entries.descs[offset].in_rh);
}
static CK_CC_INLINE void
ck_rhs_set_rh(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
map->entries.no_entries.descs[offset].in_rh = true;
else
map->entries.descs[offset].in_rh = true;
}
static CK_CC_INLINE void
ck_rhs_unset_rh(struct ck_rhs_map *map, long offset)
{
if (CK_CC_UNLIKELY(map->read_mostly))
map->entries.no_entries.descs[offset].in_rh = false;
else
map->entries.descs[offset].in_rh = false;
}
#define CK_RHS_DEFAULT_LOAD_FACTOR 50
static ck_rhs_probe_cb_t ck_rhs_map_probe;
static ck_rhs_probe_cb_t ck_rhs_map_probe_rm;
bool
ck_rhs_set_load_factor(struct ck_rhs *hs, unsigned int load_factor)
{
struct ck_rhs_map *map = hs->map;
if (load_factor == 0 || load_factor > 100)
return false;
hs->load_factor = load_factor;
map->max_entries = (map->capacity * (unsigned long)hs->load_factor) / 100;
while (map->n_entries > map->max_entries) {
if (ck_rhs_grow(hs, map->capacity << 1) == false)
return false;
map = hs->map;
}
return true;
}
void
ck_rhs_iterator_init(struct ck_rhs_iterator *iterator)
{
iterator->cursor = NULL;
iterator->offset = 0;
return;
}
bool
ck_rhs_next(struct ck_rhs *hs, struct ck_rhs_iterator *i, void **key)
{
struct ck_rhs_map *map = hs->map;
void *value;
if (i->offset >= map->capacity)
return false;
do {
value = CK_CC_DECONST_PTR(ck_rhs_entry(map, i->offset));
if (value != CK_RHS_EMPTY) {
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT)
value = CK_RHS_VMA(value);
#endif
i->offset++;
*key = value;
return true;
}
} while (++i->offset < map->capacity);
return false;
}
void
ck_rhs_stat(struct ck_rhs *hs, struct ck_rhs_stat *st)
{
struct ck_rhs_map *map = hs->map;
st->n_entries = map->n_entries;
st->probe_maximum = map->probe_maximum;
return;
}
unsigned long
ck_rhs_count(struct ck_rhs *hs)
{
return hs->map->n_entries;
}
static void
ck_rhs_map_destroy(struct ck_malloc *m, struct ck_rhs_map *map, bool defer)
{
m->free(map, map->size, defer);
return;
}
void
ck_rhs_destroy(struct ck_rhs *hs)
{
ck_rhs_map_destroy(hs->m, hs->map, false);
return;
}
static struct ck_rhs_map *
ck_rhs_map_create(struct ck_rhs *hs, unsigned long entries)
{
struct ck_rhs_map *map;
unsigned long size, n_entries, limit;
n_entries = ck_internal_power_2(entries);
if (n_entries < CK_RHS_PROBE_L1)
n_entries = CK_RHS_PROBE_L1;
if (hs->mode & CK_RHS_MODE_READ_MOSTLY)
size = sizeof(struct ck_rhs_map) +
(sizeof(void *) * n_entries +
sizeof(struct ck_rhs_no_entry_desc) * n_entries +
2 * CK_MD_CACHELINE - 1);
else
size = sizeof(struct ck_rhs_map) +
(sizeof(struct ck_rhs_entry_desc) * n_entries +
CK_MD_CACHELINE - 1);
map = hs->m->malloc(size);
if (map == NULL)
return NULL;
map->read_mostly = !!(hs->mode & CK_RHS_MODE_READ_MOSTLY);
map->size = size;
/* We should probably use a more intelligent heuristic for default probe length. */
limit = ck_internal_max(n_entries >> (CK_RHS_PROBE_L1_SHIFT + 2), CK_RHS_PROBE_L1_DEFAULT);
if (limit > UINT_MAX)
limit = UINT_MAX;
map->probe_limit = (unsigned int)limit;
map->probe_maximum = 0;
map->capacity = n_entries;
map->step = ck_internal_bsf(n_entries);
map->mask = n_entries - 1;
map->n_entries = 0;
map->max_entries = (map->capacity * (unsigned long)hs->load_factor) / 100;
/* Align map allocation to cache line. */
if (map->read_mostly) {
map->entries.no_entries.entries = (void *)(((uintptr_t)&map[1] +
CK_MD_CACHELINE - 1) & ~(CK_MD_CACHELINE - 1));
map->entries.no_entries.descs = (void *)(((uintptr_t)map->entries.no_entries.entries + (sizeof(void *) * n_entries) + CK_MD_CACHELINE - 1) &~ (CK_MD_CACHELINE - 1));
memset(map->entries.no_entries.entries, 0,
sizeof(void *) * n_entries);
memset(map->entries.no_entries.descs, 0,
sizeof(struct ck_rhs_no_entry_desc) * n_entries);
map->offset_mask = (CK_MD_CACHELINE / sizeof(void *)) - 1;
map->probe_func = ck_rhs_map_probe_rm;
} else {
map->entries.descs = (void *)(((uintptr_t)&map[1] +
CK_MD_CACHELINE - 1) & ~(CK_MD_CACHELINE - 1));
memset(map->entries.descs, 0, sizeof(struct ck_rhs_entry_desc) * n_entries);
map->offset_mask = (CK_MD_CACHELINE / sizeof(struct ck_rhs_entry_desc)) - 1;
map->probe_func = ck_rhs_map_probe;
}
memset(map->generation, 0, sizeof map->generation);
/* Commit entries purge with respect to map publication. */
ck_pr_fence_store();
return map;
}
bool
ck_rhs_reset_size(struct ck_rhs *hs, unsigned long capacity)
{
struct ck_rhs_map *map, *previous;
previous = hs->map;
map = ck_rhs_map_create(hs, capacity);
if (map == NULL)
return false;
ck_pr_store_ptr(&hs->map, map);
ck_rhs_map_destroy(hs->m, previous, true);
return true;
}
bool
ck_rhs_reset(struct ck_rhs *hs)
{
struct ck_rhs_map *previous;
previous = hs->map;
return ck_rhs_reset_size(hs, previous->capacity);
}
static inline unsigned long
ck_rhs_map_probe_next(struct ck_rhs_map *map,
unsigned long offset,
unsigned long probes)
{
if (probes & map->offset_mask) {
offset = (offset &~ map->offset_mask) +
((offset + 1) & map->offset_mask);
return offset;
} else
return (offset + probes) & map->mask;
}
static inline unsigned long
ck_rhs_map_probe_prev(struct ck_rhs_map *map, unsigned long offset,
unsigned long probes)
{
if (probes & map->offset_mask) {
offset = (offset &~ map->offset_mask) + ((offset - 1) &
map->offset_mask);
return offset;
} else
return ((offset - probes) & map->mask);
}
static inline void
ck_rhs_map_bound_set(struct ck_rhs_map *m,
unsigned long h,
unsigned long n_probes)
{
unsigned long offset = h & m->mask;
struct ck_rhs_entry_desc *desc;
if (n_probes > m->probe_maximum)
ck_pr_store_uint(&m->probe_maximum, n_probes);
if (!(m->read_mostly)) {
desc = &m->entries.descs[offset];
if (desc->probe_bound < n_probes) {
if (n_probes > CK_RHS_WORD_MAX)
n_probes = CK_RHS_WORD_MAX;
CK_RHS_STORE(&desc->probe_bound, n_probes);
ck_pr_fence_store();
}
}
return;
}
static inline unsigned int
ck_rhs_map_bound_get(struct ck_rhs_map *m, unsigned long h)
{
unsigned long offset = h & m->mask;
unsigned int r = CK_RHS_WORD_MAX;
if (m->read_mostly)
r = ck_pr_load_uint(&m->probe_maximum);
else {
r = CK_RHS_LOAD(&m->entries.descs[offset].probe_bound);
if (r == CK_RHS_WORD_MAX)
r = ck_pr_load_uint(&m->probe_maximum);
}
return r;
}
bool
ck_rhs_grow(struct ck_rhs *hs,
unsigned long capacity)
{
struct ck_rhs_map *map, *update;
const void *previous, *prev_saved;
unsigned long k, offset, probes;
restart:
map = hs->map;
if (map->capacity > capacity)
return false;
update = ck_rhs_map_create(hs, capacity);
if (update == NULL)
return false;
for (k = 0; k < map->capacity; k++) {
unsigned long h;
prev_saved = previous = ck_rhs_entry(map, k);
if (previous == CK_RHS_EMPTY)
continue;
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT)
previous = CK_RHS_VMA(previous);
#endif
h = hs->hf(previous, hs->seed);
offset = h & update->mask;
probes = 0;
for (;;) {
const void **cursor = ck_rhs_entry_addr(update, offset);
if (probes++ == update->probe_limit) {
/*
* We have hit the probe limit, map needs to be even larger.
*/
ck_rhs_map_destroy(hs->m, update, false);
capacity <<= 1;
goto restart;
}
if (CK_CC_LIKELY(*cursor == CK_RHS_EMPTY)) {
*cursor = prev_saved;
update->n_entries++;
ck_rhs_set_probes(update, offset, probes);
ck_rhs_map_bound_set(update, h, probes);
break;
} else if (ck_rhs_probes(update, offset) < probes) {
const void *tmp = prev_saved;
unsigned int old_probes;
prev_saved = previous = *cursor;
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT)
previous = CK_RHS_VMA(previous);
#endif
*cursor = tmp;
ck_rhs_map_bound_set(update, h, probes);
h = hs->hf(previous, hs->seed);
old_probes = ck_rhs_probes(update, offset);
ck_rhs_set_probes(update, offset, probes);
probes = old_probes - 1;
continue;
}
ck_rhs_wanted_inc(update, offset);
offset = ck_rhs_map_probe_next(update, offset, probes);
}
}
ck_pr_fence_store();
ck_pr_store_ptr(&hs->map, update);
ck_rhs_map_destroy(hs->m, map, true);
return true;
}
bool
ck_rhs_rebuild(struct ck_rhs *hs)
{
return ck_rhs_grow(hs, hs->map->capacity);
}
static long
ck_rhs_map_probe_rm(struct ck_rhs *hs,
struct ck_rhs_map *map,
unsigned long *n_probes,
long *priority,
unsigned long h,
const void *key,
const void **object,
unsigned long probe_limit,
enum ck_rhs_probe_behavior behavior)
{
const void *k;
const void *compare;
long pr = -1;
unsigned long offset, probes, opl;
#ifdef CK_RHS_PP
/* If we are storing object pointers, then we may leverage pointer packing. */
unsigned long hv = 0;
if (hs->mode & CK_RHS_MODE_OBJECT) {
hv = (h >> 25) & CK_RHS_KEY_MASK;
compare = CK_RHS_VMA(key);
} else {
compare = key;
}
#else
compare = key;
#endif
*object = NULL;
if (behavior != CK_RHS_PROBE_ROBIN_HOOD) {
probes = 0;
offset = h & map->mask;
} else {
/* Restart from the bucket we were previously in */
probes = *n_probes;
offset = ck_rhs_map_probe_next(map, *priority,
probes);
}
opl = probe_limit;
for (;;) {
if (probes++ == probe_limit) {
if (probe_limit == opl || pr != -1) {
k = CK_RHS_EMPTY;
goto leave;
}
/*
* If no eligible slot has been found yet, continue probe
* sequence with original probe limit.
*/
probe_limit = opl;
}
k = ck_pr_load_ptr(&map->entries.no_entries.entries[offset]);
if (k == CK_RHS_EMPTY)
goto leave;
if (behavior != CK_RHS_PROBE_NO_RH) {
struct ck_rhs_entry_desc *desc = (void *)&map->entries.no_entries.descs[offset];
if (pr == -1 &&
desc->in_rh == false && desc->probes < probes) {
pr = offset;
*n_probes = probes;
if (behavior == CK_RHS_PROBE_RH ||
behavior == CK_RHS_PROBE_ROBIN_HOOD) {
k = CK_RHS_EMPTY;
goto leave;
}
}
}
if (behavior != CK_RHS_PROBE_ROBIN_HOOD) {
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT) {
if (((uintptr_t)k >> CK_MD_VMA_BITS) != hv) {
offset = ck_rhs_map_probe_next(map, offset, probes);
continue;
}
k = CK_RHS_VMA(k);
}
#endif
if (k == compare)
goto leave;
if (hs->compare == NULL) {
offset = ck_rhs_map_probe_next(map, offset, probes);
continue;
}
if (hs->compare(k, key) == true)
goto leave;
}
offset = ck_rhs_map_probe_next(map, offset, probes);
}
leave:
if (probes > probe_limit) {
offset = -1;
} else {
*object = k;
}
if (pr == -1)
*n_probes = probes;
*priority = pr;
return offset;
}
static long
ck_rhs_map_probe(struct ck_rhs *hs,
struct ck_rhs_map *map,
unsigned long *n_probes,
long *priority,
unsigned long h,
const void *key,
const void **object,
unsigned long probe_limit,
enum ck_rhs_probe_behavior behavior)
{
const void *k;
const void *compare;
long pr = -1;
unsigned long offset, probes, opl;
#ifdef CK_RHS_PP
/* If we are storing object pointers, then we may leverage pointer packing. */
unsigned long hv = 0;
if (hs->mode & CK_RHS_MODE_OBJECT) {
hv = (h >> 25) & CK_RHS_KEY_MASK;
compare = CK_RHS_VMA(key);
} else {
compare = key;
}
#else
compare = key;
#endif
*object = NULL;
if (behavior != CK_RHS_PROBE_ROBIN_HOOD) {
probes = 0;
offset = h & map->mask;
} else {
/* Restart from the bucket we were previously in */
probes = *n_probes;
offset = ck_rhs_map_probe_next(map, *priority,
probes);
}
opl = probe_limit;
if (behavior == CK_RHS_PROBE_INSERT)
probe_limit = ck_rhs_map_bound_get(map, h);
for (;;) {
if (probes++ == probe_limit) {
if (probe_limit == opl || pr != -1) {
k = CK_RHS_EMPTY;
goto leave;
}
/*
* If no eligible slot has been found yet, continue probe
* sequence with original probe limit.
*/
probe_limit = opl;
}
k = ck_pr_load_ptr(&map->entries.descs[offset].entry);
if (k == CK_RHS_EMPTY)
goto leave;
if ((behavior != CK_RHS_PROBE_NO_RH)) {
struct ck_rhs_entry_desc *desc = &map->entries.descs[offset];
if (pr == -1 &&
desc->in_rh == false && desc->probes < probes) {
pr = offset;
*n_probes = probes;
if (behavior == CK_RHS_PROBE_RH ||
behavior == CK_RHS_PROBE_ROBIN_HOOD) {
k = CK_RHS_EMPTY;
goto leave;
}
}
}
if (behavior != CK_RHS_PROBE_ROBIN_HOOD) {
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT) {
if (((uintptr_t)k >> CK_MD_VMA_BITS) != hv) {
offset = ck_rhs_map_probe_next(map, offset, probes);
continue;
}
k = CK_RHS_VMA(k);
}
#endif
if (k == compare)
goto leave;
if (hs->compare == NULL) {
offset = ck_rhs_map_probe_next(map, offset, probes);
continue;
}
if (hs->compare(k, key) == true)
goto leave;
}
offset = ck_rhs_map_probe_next(map, offset, probes);
}
leave:
if (probes > probe_limit) {
offset = -1;
} else {
*object = k;
}
if (pr == -1)
*n_probes = probes;
*priority = pr;
return offset;
}
static inline const void *
ck_rhs_marshal(unsigned int mode, const void *key, unsigned long h)
{
#ifdef CK_RHS_PP
const void *insert;
if (mode & CK_RHS_MODE_OBJECT) {
insert = (void *)((uintptr_t)CK_RHS_VMA(key) | ((h >> 25) << CK_MD_VMA_BITS));
} else {
insert = key;
}
return insert;
#else
(void)mode;
(void)h;
return key;
#endif
}
bool
ck_rhs_gc(struct ck_rhs *hs)
{
unsigned long i;
struct ck_rhs_map *map = hs->map;
unsigned int max_probes = 0;
for (i = 0; i < map->capacity; i++) {
if (ck_rhs_probes(map, i) > max_probes)
max_probes = ck_rhs_probes(map, i);
}
map->probe_maximum = max_probes;
return true;
}
static void
ck_rhs_add_wanted(struct ck_rhs *hs, long end_offset, long old_slot,
unsigned long h)
{
struct ck_rhs_map *map = hs->map;
long offset;
unsigned int probes = 1;
bool found_slot = false;
struct ck_rhs_entry_desc *desc;
offset = h & map->mask;
if (old_slot == -1)
found_slot = true;
while (offset != end_offset) {
if (offset == old_slot)
found_slot = true;
if (found_slot) {
desc = ck_rhs_desc(map, offset);
if (desc->wanted < CK_RHS_MAX_WANTED)
desc->wanted++;
}
offset = ck_rhs_map_probe_next(map, offset, probes);
probes++;
}
}
static unsigned long
ck_rhs_remove_wanted(struct ck_rhs *hs, long offset, long limit)
{
struct ck_rhs_map *map = hs->map;
int probes = ck_rhs_probes(map, offset);
bool do_remove = true;
struct ck_rhs_entry_desc *desc;
while (probes > 1) {
probes--;
offset = ck_rhs_map_probe_prev(map, offset, probes);
if (offset == limit)
do_remove = false;
if (do_remove) {
desc = ck_rhs_desc(map, offset);
if (desc->wanted != CK_RHS_MAX_WANTED)
desc->wanted--;
}
}
return offset;
}
static long
ck_rhs_get_first_offset(struct ck_rhs_map *map, unsigned long offset, unsigned int probes)
{
while (probes > (unsigned long)map->offset_mask + 1) {
offset -= ((probes - 1) &~ map->offset_mask);
offset &= map->mask;
offset = (offset &~ map->offset_mask) +
((offset - map->offset_mask) & map->offset_mask);
probes -= map->offset_mask + 1;
}
return ((offset &~ map->offset_mask) + ((offset - (probes - 1)) & map->offset_mask));
}
#define CK_RHS_MAX_RH 512
static int
ck_rhs_put_robin_hood(struct ck_rhs *hs,
long orig_slot, struct ck_rhs_entry_desc *desc)
{
long slot, first;
const void *object, *insert;
unsigned long n_probes;
struct ck_rhs_map *map;
unsigned long h = 0;
long prev;
void *key;
long prevs[CK_RHS_MAX_RH];
unsigned int prevs_nb = 0;
unsigned int i;
map = hs->map;
first = orig_slot;
n_probes = desc->probes;
restart:
key = CK_CC_DECONST_PTR(ck_rhs_entry(map, first));
insert = key;
#ifdef CK_RHS_PP
if (hs->mode & CK_RHS_MODE_OBJECT)
key = CK_RHS_VMA(key);
#endif
orig_slot = first;
ck_rhs_set_rh(map, orig_slot);
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object,
map->probe_limit, prevs_nb == CK_RHS_MAX_RH ?
CK_RHS_PROBE_NO_RH : CK_RHS_PROBE_ROBIN_HOOD);
if (slot == -1 && first == -1) {
if (ck_rhs_grow(hs, map->capacity << 1) == false) {
desc->in_rh = false;
for (i = 0; i < prevs_nb; i++)
ck_rhs_unset_rh(map, prevs[i]);
return -1;
}
return 1;
}
if (first != -1) {
desc = ck_rhs_desc(map, first);
int old_probes = desc->probes;
desc->probes = n_probes;
h = ck_rhs_get_first_offset(map, first, n_probes);
ck_rhs_map_bound_set(map, h, n_probes);
prev = orig_slot;
prevs[prevs_nb++] = prev;
n_probes = old_probes;
goto restart;
} else {
/* An empty slot was found. */
h = ck_rhs_get_first_offset(map, slot, n_probes);
ck_rhs_map_bound_set(map, h, n_probes);
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), insert);
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
ck_rhs_set_probes(map, slot, n_probes);
desc->in_rh = 0;
ck_rhs_add_wanted(hs, slot, orig_slot, h);
}
while (prevs_nb > 0) {
prev = prevs[--prevs_nb];
ck_pr_store_ptr(ck_rhs_entry_addr(map, orig_slot),
ck_rhs_entry(map, prev));
h = ck_rhs_get_first_offset(map, orig_slot,
desc->probes);
ck_rhs_add_wanted(hs, orig_slot, prev, h);
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
orig_slot = prev;
desc->in_rh = false;
desc = ck_rhs_desc(map, orig_slot);
}
return 0;
}
static void
ck_rhs_do_backward_shift_delete(struct ck_rhs *hs, long slot)
{
struct ck_rhs_map *map = hs->map;
struct ck_rhs_entry_desc *desc, *new_desc = NULL;
unsigned long h;
desc = ck_rhs_desc(map, slot);
h = ck_rhs_remove_wanted(hs, slot, -1);
while (desc->wanted > 0) {
unsigned long offset = 0, tmp_offset;
unsigned long wanted_probes = 1;
unsigned int probe = 0;
unsigned int max_probes;
/* Find a successor */
while (wanted_probes < map->probe_maximum) {
probe = wanted_probes;
offset = ck_rhs_map_probe_next(map, slot, probe);
while (probe < map->probe_maximum) {
new_desc = ck_rhs_desc(map, offset);
if (new_desc->probes == probe + 1)
break;
probe++;
offset = ck_rhs_map_probe_next(map, offset,
probe);
}
if (probe < map->probe_maximum)
break;
wanted_probes++;
}
if (!(wanted_probes < map->probe_maximum)) {
desc->wanted = 0;
break;
}
desc->probes = wanted_probes;
h = ck_rhs_remove_wanted(hs, offset, slot);
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot),
ck_rhs_entry(map, offset));
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
if (wanted_probes < CK_RHS_WORD_MAX) {
struct ck_rhs_entry_desc *hdesc = ck_rhs_desc(map, h);
if (hdesc->wanted == 1)
CK_RHS_STORE(&hdesc->probe_bound,
wanted_probes);
else if (hdesc->probe_bound == CK_RHS_WORD_MAX ||
hdesc->probe_bound == new_desc->probes) {
probe++;
if (hdesc->probe_bound == CK_RHS_WORD_MAX)
max_probes = map->probe_maximum;
else {
max_probes = hdesc->probe_bound;
max_probes--;
}
tmp_offset = ck_rhs_map_probe_next(map, offset,
probe);
while (probe < max_probes) {
if (h == (unsigned long)ck_rhs_get_first_offset(map, tmp_offset, probe))
break;
probe++;
tmp_offset = ck_rhs_map_probe_next(map, tmp_offset, probe);
}
if (probe == max_probes)
CK_RHS_STORE(&hdesc->probe_bound,
wanted_probes);
}
}
if (desc->wanted < CK_RHS_MAX_WANTED)
desc->wanted--;
slot = offset;
desc = new_desc;
}
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), CK_RHS_EMPTY);
if ((desc->probes - 1) < CK_RHS_WORD_MAX)
CK_RHS_STORE(ck_rhs_probe_bound_addr(map, h),
desc->probes - 1);
desc->probes = 0;
}
bool
ck_rhs_fas(struct ck_rhs *hs,
unsigned long h,
const void *key,
void **previous)
{
long slot, first;
const void *object;
const void *insert;
unsigned long n_probes;
struct ck_rhs_map *map = hs->map;
struct ck_rhs_entry_desc *desc, *desc2;
*previous = NULL;
restart:
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object,
ck_rhs_map_bound_get(map, h), CK_RHS_PROBE);
/* Replacement semantics presume existence. */
if (object == NULL)
return false;
insert = ck_rhs_marshal(hs->mode, key, h);
if (first != -1) {
int ret;
desc = ck_rhs_desc(map, slot);
desc2 = ck_rhs_desc(map, first);
desc->in_rh = true;
ret = ck_rhs_put_robin_hood(hs, first, desc2);
desc->in_rh = false;
if (CK_CC_UNLIKELY(ret == 1))
goto restart;
else if (CK_CC_UNLIKELY(ret != 0))
return false;
ck_pr_store_ptr(ck_rhs_entry_addr(map, first), insert);
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
desc2->probes = n_probes;
ck_rhs_add_wanted(hs, first, -1, h);
ck_rhs_do_backward_shift_delete(hs, slot);
} else {
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), insert);
ck_rhs_set_probes(map, slot, n_probes);
}
*previous = CK_CC_DECONST_PTR(object);
return true;
}
/*
* An apply function takes two arguments. The first argument is a pointer to a
* pre-existing object. The second argument is a pointer to the fifth argument
* passed to ck_hs_apply. If a non-NULL pointer is passed to the first argument
* and the return value of the apply function is NULL, then the pre-existing
* value is deleted. If the return pointer is the same as the one passed to the
* apply function then no changes are made to the hash table. If the first
* argument is non-NULL and the return pointer is different than that passed to
* the apply function, then the pre-existing value is replaced. For
* replacement, it is required that the value itself is identical to the
* previous value.
*/
bool
ck_rhs_apply(struct ck_rhs *hs,
unsigned long h,
const void *key,
ck_rhs_apply_fn_t *fn,
void *cl)
{
const void *insert;
const void *object, *delta = false;
unsigned long n_probes;
long slot, first;
struct ck_rhs_map *map;
bool delta_set = false;
restart:
map = hs->map;
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object, map->probe_limit, CK_RHS_PROBE_INSERT);
if (slot == -1 && first == -1) {
if (ck_rhs_grow(hs, map->capacity << 1) == false)
return false;
goto restart;
}
if (!delta_set) {
delta = fn(CK_CC_DECONST_PTR(object), cl);
delta_set = true;
}
if (delta == NULL) {
/*
* The apply function has requested deletion. If the object doesn't exist,
* then exit early.
*/
if (CK_CC_UNLIKELY(object == NULL))
return true;
/* Otherwise, delete it. */
ck_rhs_do_backward_shift_delete(hs, slot);
return true;
}
/* The apply function has not requested hash set modification so exit early. */
if (delta == object)
return true;
/* A modification or insertion has been requested. */
ck_rhs_map_bound_set(map, h, n_probes);
insert = ck_rhs_marshal(hs->mode, delta, h);
if (first != -1) {
/*
* This follows the same semantics as ck_hs_set, please refer to that
* function for documentation.
*/
struct ck_rhs_entry_desc *desc = NULL, *desc2;
if (slot != -1) {
desc = ck_rhs_desc(map, slot);
desc->in_rh = true;
}
desc2 = ck_rhs_desc(map, first);
int ret = ck_rhs_put_robin_hood(hs, first, desc2);
if (slot != -1)
desc->in_rh = false;
if (CK_CC_UNLIKELY(ret == 1))
goto restart;
if (CK_CC_UNLIKELY(ret == -1))
return false;
/* If an earlier bucket was found, then store entry there. */
ck_pr_store_ptr(ck_rhs_entry_addr(map, first), insert);
desc2->probes = n_probes;
/*
* If a duplicate key was found, then delete it after
* signaling concurrent probes to restart. Optionally,
* it is possible to install tombstone after grace
* period if we can guarantee earlier position of
* duplicate key.
*/
ck_rhs_add_wanted(hs, first, -1, h);
if (object != NULL) {
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
ck_rhs_do_backward_shift_delete(hs, slot);
}
} else {
/*
* If we are storing into same slot, then atomic store is sufficient
* for replacement.
*/
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), insert);
ck_rhs_set_probes(map, slot, n_probes);
if (object == NULL)
ck_rhs_add_wanted(hs, slot, -1, h);
}
if (object == NULL) {
map->n_entries++;
if ((map->n_entries ) > map->max_entries)
ck_rhs_grow(hs, map->capacity << 1);
}
return true;
}
bool
ck_rhs_set(struct ck_rhs *hs,
unsigned long h,
const void *key,
void **previous)
{
long slot, first;
const void *object;
const void *insert;
unsigned long n_probes;
struct ck_rhs_map *map;
*previous = NULL;
restart:
map = hs->map;
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object, map->probe_limit, CK_RHS_PROBE_INSERT);
if (slot == -1 && first == -1) {
if (ck_rhs_grow(hs, map->capacity << 1) == false)
return false;
goto restart;
}
ck_rhs_map_bound_set(map, h, n_probes);
insert = ck_rhs_marshal(hs->mode, key, h);
if (first != -1) {
struct ck_rhs_entry_desc *desc = NULL, *desc2;
if (slot != -1) {
desc = ck_rhs_desc(map, slot);
desc->in_rh = true;
}
desc2 = ck_rhs_desc(map, first);
int ret = ck_rhs_put_robin_hood(hs, first, desc2);
if (slot != -1)
desc->in_rh = false;
if (CK_CC_UNLIKELY(ret == 1))
goto restart;
if (CK_CC_UNLIKELY(ret == -1))
return false;
/* If an earlier bucket was found, then store entry there. */
ck_pr_store_ptr(ck_rhs_entry_addr(map, first), insert);
desc2->probes = n_probes;
/*
* If a duplicate key was found, then delete it after
* signaling concurrent probes to restart. Optionally,
* it is possible to install tombstone after grace
* period if we can guarantee earlier position of
* duplicate key.
*/
ck_rhs_add_wanted(hs, first, -1, h);
if (object != NULL) {
ck_pr_inc_uint(&map->generation[h & CK_RHS_G_MASK]);
ck_pr_fence_atomic_store();
ck_rhs_do_backward_shift_delete(hs, slot);
}
} else {
/*
* If we are storing into same slot, then atomic store is sufficient
* for replacement.
*/
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), insert);
ck_rhs_set_probes(map, slot, n_probes);
if (object == NULL)
ck_rhs_add_wanted(hs, slot, -1, h);
}
if (object == NULL) {
map->n_entries++;
if ((map->n_entries ) > map->max_entries)
ck_rhs_grow(hs, map->capacity << 1);
}
*previous = CK_CC_DECONST_PTR(object);
return true;
}
static bool
ck_rhs_put_internal(struct ck_rhs *hs,
unsigned long h,
const void *key,
enum ck_rhs_probe_behavior behavior)
{
long slot, first;
const void *object;
const void *insert;
unsigned long n_probes;
struct ck_rhs_map *map;
restart:
map = hs->map;
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object,
map->probe_limit, behavior);
if (slot == -1 && first == -1) {
if (ck_rhs_grow(hs, map->capacity << 1) == false)
return false;
goto restart;
}
/* Fail operation if a match was found. */
if (object != NULL)
return false;
ck_rhs_map_bound_set(map, h, n_probes);
insert = ck_rhs_marshal(hs->mode, key, h);
if (first != -1) {
struct ck_rhs_entry_desc *desc = ck_rhs_desc(map, first);
int ret = ck_rhs_put_robin_hood(hs, first, desc);
if (CK_CC_UNLIKELY(ret == 1))
return ck_rhs_put_internal(hs, h, key, behavior);
else if (CK_CC_UNLIKELY(ret == -1))
return false;
/* Insert key into first bucket in probe sequence. */
ck_pr_store_ptr(ck_rhs_entry_addr(map, first), insert);
desc->probes = n_probes;
ck_rhs_add_wanted(hs, first, -1, h);
} else {
/* An empty slot was found. */
ck_pr_store_ptr(ck_rhs_entry_addr(map, slot), insert);
ck_rhs_set_probes(map, slot, n_probes);
ck_rhs_add_wanted(hs, slot, -1, h);
}
map->n_entries++;
if ((map->n_entries ) > map->max_entries)
ck_rhs_grow(hs, map->capacity << 1);
return true;
}
bool
ck_rhs_put(struct ck_rhs *hs,
unsigned long h,
const void *key)
{
return ck_rhs_put_internal(hs, h, key, CK_RHS_PROBE_INSERT);
}
bool
ck_rhs_put_unique(struct ck_rhs *hs,
unsigned long h,
const void *key)
{
return ck_rhs_put_internal(hs, h, key, CK_RHS_PROBE_RH);
}
void *
ck_rhs_get(struct ck_rhs *hs,
unsigned long h,
const void *key)
{
long first;
const void *object;
struct ck_rhs_map *map;
unsigned long n_probes;
unsigned int g, g_p, probe;
unsigned int *generation;
do {
map = ck_pr_load_ptr(&hs->map);
generation = &map->generation[h & CK_RHS_G_MASK];
g = ck_pr_load_uint(generation);
probe = ck_rhs_map_bound_get(map, h);
ck_pr_fence_load();
first = -1;
map->probe_func(hs, map, &n_probes, &first, h, key, &object, probe, CK_RHS_PROBE_NO_RH);
ck_pr_fence_load();
g_p = ck_pr_load_uint(generation);
} while (g != g_p);
return CK_CC_DECONST_PTR(object);
}
void *
ck_rhs_remove(struct ck_rhs *hs,
unsigned long h,
const void *key)
{
long slot, first;
const void *object;
struct ck_rhs_map *map = hs->map;
unsigned long n_probes;
slot = map->probe_func(hs, map, &n_probes, &first, h, key, &object,
ck_rhs_map_bound_get(map, h), CK_RHS_PROBE_NO_RH);
if (object == NULL)
return NULL;
map->n_entries--;
ck_rhs_do_backward_shift_delete(hs, slot);
return CK_CC_DECONST_PTR(object);
}
bool
ck_rhs_move(struct ck_rhs *hs,
struct ck_rhs *source,
ck_rhs_hash_cb_t *hf,
ck_rhs_compare_cb_t *compare,
struct ck_malloc *m)
{
if (m == NULL || m->malloc == NULL || m->free == NULL || hf == NULL)
return false;
hs->mode = source->mode;
hs->seed = source->seed;
hs->map = source->map;
hs->load_factor = source->load_factor;
hs->m = m;
hs->hf = hf;
hs->compare = compare;
return true;
}
bool
ck_rhs_init(struct ck_rhs *hs,
unsigned int mode,
ck_rhs_hash_cb_t *hf,
ck_rhs_compare_cb_t *compare,
struct ck_malloc *m,
unsigned long n_entries,
unsigned long seed)
{
if (m == NULL || m->malloc == NULL || m->free == NULL || hf == NULL)
return false;
hs->m = m;
hs->mode = mode;
hs->seed = seed;
hs->hf = hf;
hs->compare = compare;
hs->load_factor = CK_RHS_DEFAULT_LOAD_FACTOR;
hs->map = ck_rhs_map_create(hs, n_entries);
return hs->map != NULL;
}