#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "chash.h"
#define CH_MAX_LOAD 875
#define CH_H3_BITS 8
#define CH_H3_SIZE (1 << CH_H3_BITS)
#define CH_H3_MASK (CH_H3_SIZE - 1)
#define CH_H2_SHIFT CH_H3_BITS
#define CH_H2_BITS 9
#define CH_H2_SIZE (1 << CH_H2_BITS)
#define CH_H2_MASK (CH_H2_SIZE - 1)
#define CH_H1_BITS (64 - CH_H2_BITS - CH_H3_BITS)
#define CH_H1(h, l) ((l) == 0 ? 0 : h >> (64 - (l)))
#define CH_H2(h) ((h >> CH_H2_SHIFT) & CH_H2_MASK)
#define CH_H3(h) (h & CH_H3_MASK)
struct ch_group {
uint64_t cg_meta;
void *cg_data[7];
};
#define CH_EVER_FULL 0x80
#define CH_SLOT_MASK 0x7f
#define CH_FLAGS_SHIFT 56
struct ch_meta {
uint32_t cs_num_elm;
uint32_t cs_num_tomb;
uint32_t cs_num_ever_full;
uint32_t cs_local_level;
};
struct ch_ext {
struct ch_group *ce_table;
struct ch_meta *ce_meta;
};
static inline int
cg_meta_set_flags(struct ch_group *g, uint8_t flag)
{
uint64_t f = flag, oldf;
oldf = g->cg_meta & (f << CH_FLAGS_SHIFT);
g->cg_meta |= f << CH_FLAGS_SHIFT;
return oldf != 0;
}
static inline int
cg_meta_clear_flags(struct ch_group *g, uint8_t flag)
{
uint64_t f = flag, oldf;
oldf = g->cg_meta & (f << CH_FLAGS_SHIFT);
g->cg_meta &= ~(f << CH_FLAGS_SHIFT);
return oldf != 0;
}
static inline uint8_t
cg_meta_get_flags(const struct ch_group *g)
{
return g->cg_meta >> CH_FLAGS_SHIFT;
}
static inline int
cg_meta_check_flags(const struct ch_group *g, uint8_t flag)
{
uint64_t f = flag;
return (g->cg_meta & (f << CH_FLAGS_SHIFT)) != 0;
}
static inline void
cg_meta_set_hash(struct ch_group *g, int slot, uint64_t hash)
{
uint64_t newval;
newval = g->cg_meta & ~(0xffULL << (slot * 8));
newval |= hash << (slot * 8);
g->cg_meta = newval;
}
static inline uint8_t
ch_haszero(uint64_t lookup)
{
uint64_t a, b;
a = lookup - 0x0101010101010101ULL;
b = ~lookup & 0x8080808080808080ULL;
lookup = a & b;
lookup *= 0x0002040810204080ULL;
return lookup >> 56;
}
static uint8_t
ch_meta_locate(struct ch_group *g, uint64_t mask)
{
uint64_t lookup;
uint8_t flags, hits;
lookup = g->cg_meta ^ mask;
flags = cg_meta_get_flags(g);
hits = ch_haszero(lookup);
return flags & (CH_EVER_FULL | hits);
}
static int
ch_sub_loadfactor(struct ch_meta *m)
{
uint32_t max = CH_H2_SIZE * 7;
uint32_t used = m->cs_num_elm + m->cs_num_tomb;
return used * 1000 / max;
}
static int
ch_sub_fillfactor(struct ch_meta *m)
{
uint32_t max = CH_H2_SIZE * 7;
uint32_t used = m->cs_num_elm;
return used * 1000 / max;
}
static void *
ch_sub_insert(const struct ch_type *type, struct ch_group *table,
struct ch_meta *meta, uint64_t h, void *elm)
{
uint64_t mask;
uint32_t bucket = CH_H2(h);
int i;
uint8_t empties, hits, ins_i, slots;
struct ch_group *g = &table[bucket], *ins_g = NULL;
mask = CH_H3(h) * 0x0101010101010101ULL;
while (1) {
hits = ch_meta_locate(g, mask);
for (slots = hits & CH_SLOT_MASK; slots != 0;
slots &= slots - 1) {
i = ffs(slots) - 1;
if (type->t_equal(g->cg_data[i], elm))
return g->cg_data[i];
}
empties = ~cg_meta_get_flags(g) & CH_SLOT_MASK;
if (empties == 0) {
if (cg_meta_set_flags(g, CH_EVER_FULL) == 0)
meta->cs_num_ever_full++;
} else {
if (ins_g == NULL) {
ins_g = g;
ins_i = ffs(empties) - 1;
}
if (cg_meta_check_flags(g, CH_EVER_FULL) == 0)
break;
}
g = &table[++bucket & CH_H2_MASK];
}
ins_g->cg_data[ins_i] = elm;
cg_meta_set_hash(ins_g, ins_i, h & CH_H3_MASK);
cg_meta_set_flags(ins_g, 1 << ins_i);
if (cg_meta_check_flags(ins_g, CH_EVER_FULL))
meta->cs_num_tomb--;
meta->cs_num_elm++;
return NULL;
}
static void *
ch_sub_remove(const struct ch_type *type, struct ch_group *table,
struct ch_meta *meta, uint64_t h, const void *needle)
{
uint64_t mask;
uint32_t bucket = CH_H2(h);
int i;
uint8_t hits, slots;
struct ch_group *g = &table[bucket];
mask = CH_H3(h) * 0x0101010101010101ULL;
while (1) {
hits = ch_meta_locate(g, mask);
for (slots = hits & CH_SLOT_MASK; slots != 0;
slots &= slots - 1) {
i = ffs(slots) - 1;
if (type->t_equal(g->cg_data[i], needle)) {
void *elm = g->cg_data[i];
g->cg_data[i] = NULL;
cg_meta_set_hash(g, i, 0);
cg_meta_clear_flags(g, 1 << i);
if (hits & CH_EVER_FULL)
meta->cs_num_tomb++;
meta->cs_num_elm--;
return elm;
}
}
if ((hits & CH_EVER_FULL) == 0)
return NULL;
g = &table[++bucket & CH_H2_MASK];
}
}
static void *
ch_sub_find(const struct ch_type *type, struct ch_group *table, uint64_t h,
const void *needle)
{
uint64_t mask;
uint32_t bucket = CH_H2(h);
int i;
uint8_t hits, slots;
struct ch_group *g = &table[bucket];
mask = CH_H3(h) * 0x0101010101010101ULL;
while (1) {
hits = ch_meta_locate(g, mask);
for (slots = hits & CH_SLOT_MASK; slots != 0;
slots &= slots - 1) {
i = ffs(slots) - 1;
if (type->t_equal(g->cg_data[i], needle))
return g->cg_data[i];
}
if ((hits & CH_EVER_FULL) == 0)
return NULL;
g = &table[++bucket & CH_H2_MASK];
}
}
static void *
ch_sub_locate(const struct ch_type *type, struct ch_group *table, uint64_t h,
int (*eq)(const void *, const void *), const void *arg)
{
uint64_t mask;
uint32_t bucket = CH_H2(h);
int i;
uint8_t hits, slots;
struct ch_group *g = &table[bucket];
mask = CH_H3(h) * 0x0101010101010101ULL;
while (1) {
hits = ch_meta_locate(g, mask);
for (slots = hits & CH_SLOT_MASK; slots != 0;
slots &= slots - 1) {
i = ffs(slots) - 1;
if (eq(g->cg_data[i], arg))
return g->cg_data[i];
}
if ((hits & CH_EVER_FULL) == 0)
return NULL;
g = &table[++bucket & CH_H2_MASK];
}
}
static void *
ch_sub_first(const struct ch_type *type, struct ch_group *table,
struct ch_iter *iter)
{
struct ch_group *g;
uint32_t n;
uint8_t elms;
for (n = 0; n < CH_H2_SIZE; n++, g++) {
g = &table[n];
elms = cg_meta_get_flags(g) & CH_SLOT_MASK;
if (elms == 0)
continue;
iter->ci_set_idx = n;
iter->ci_grp_idx = ffs(elms);
return g->cg_data[iter->ci_grp_idx - 1];
}
iter->ci_set_idx = n;
return NULL;
}
static void *
ch_sub_next(const struct ch_type *type, struct ch_group *table,
struct ch_iter *iter)
{
struct ch_group *g;
uint32_t n;
uint8_t elms;
for (n = iter->ci_set_idx; n < CH_H2_SIZE; n++, g++) {
g = &table[n];
elms = cg_meta_get_flags(g) & CH_SLOT_MASK;
elms &= CH_SLOT_MASK << iter->ci_grp_idx;
if (elms == 0) {
iter->ci_grp_idx = 0;
continue;
}
iter->ci_set_idx = n;
iter->ci_grp_idx = ffs(elms);
return g->cg_data[iter->ci_grp_idx - 1];
}
iter->ci_set_idx = n;
return NULL;
}
static int
ch_sub_split(const struct ch_type *type, struct ch_group *from,
struct ch_group *low, struct ch_group *high, struct ch_meta *frommeta,
struct ch_meta *lowmeta, struct ch_meta *highmeta)
{
struct ch_group *g = &from[0];
uint32_t n;
uint8_t elms, i;
lowmeta->cs_local_level = frommeta->cs_local_level + 1;
highmeta->cs_local_level = frommeta->cs_local_level + 1;
for (n = 0; n < CH_H2_SIZE; n++, g++) {
elms = cg_meta_get_flags(g) & CH_SLOT_MASK;
if (elms == 0)
continue;
for (i = 0; i < 7; i++) {
if (elms & (1 << i)) {
void *v;
uint64_t h = type->t_hash(g->cg_data[i]);
if (CH_H1(h, lowmeta->cs_local_level) & 1)
v = ch_sub_insert(type, high, highmeta,
h, g->cg_data[i]);
else
v = ch_sub_insert(type, low, lowmeta,
h, g->cg_data[i]);
if (v != NULL) {
errno = EINVAL;
return -1;
}
}
}
}
return 0;
}
static int
ch_sub_merge_one(const struct ch_type *type, struct ch_group *table,
struct ch_meta *meta, const struct ch_group *from)
{
uint8_t elms, i;
elms = cg_meta_get_flags(from) & CH_SLOT_MASK;
if (elms == 0)
return 0;
for (i = 0; i < 7; i++) {
if (elms & (1 << i)) {
void *v;
uint64_t h = type->t_hash(from->cg_data[i]);
v = ch_sub_insert(type, table, meta, h,
from->cg_data[i]);
if (v != NULL)
return -1;
}
}
return 0;
}
static int
ch_sub_merge(const struct ch_type *type, struct ch_group *to,
struct ch_group *from, struct ch_group *buddy, struct ch_meta *tometa,
struct ch_meta *frommeta, struct ch_meta *buddymeta)
{
struct ch_group *g = &from[0];
struct ch_group *b = &buddy[0];
uint32_t n;
tometa->cs_local_level = frommeta->cs_local_level - 1;
for (n = 0; n < CH_H2_SIZE; n++, g++, b++) {
if (ch_sub_merge_one(type, to, tometa, g) == -1)
return -1;
if (ch_sub_merge_one(type, to, tometa, b) == -1)
return -1;
}
return 0;
}
static int
ch_sub_alloc(const struct ch_type *type, struct ch_table *t,
struct ch_group **table, struct ch_meta **meta)
{
if ((*table = calloc(CH_H2_SIZE, sizeof(**table))) == NULL)
return -1;
if ((*meta = calloc(1, sizeof(**meta))) == NULL) {
free(*table);
*table = NULL;
return -1;
}
t->ch_counts.cc_num_tables++;
type->t_counts->cc_num_tables++;
return 0;
}
static void
ch_sub_free(const struct ch_type *type, struct ch_table *t,
struct ch_group *table, struct ch_meta *meta)
{
if (table != NULL) {
t->ch_counts.cc_num_tables--;
type->t_counts->cc_num_tables--;
}
free(table);
free(meta);
}
static int
ch_table_resize(const struct ch_type *type, struct ch_table *t)
{
struct ch_ext *new;
uint64_t oldsize = 1ULL << t->ch_level;
uint64_t newsize = oldsize * 2;
int64_t idx;
if (t->ch_level + 1 >= CH_H1_BITS) {
errno = E2BIG;
return -1;
}
if (t->ch_exts == NULL) {
oldsize = 0;
newsize = 2;
}
new = reallocarray(t->ch_exts, newsize, sizeof(*t->ch_exts));
if (new == NULL)
return -1;
t->ch_level++;
t->ch_exts = new;
t->ch_counts.cc_num_extendible += newsize - oldsize;
type->t_counts->cc_num_extendible += newsize - oldsize;
if (oldsize == 0)
return 0;
for (idx = oldsize - 1; idx >= 0; idx--) {
t->ch_exts[idx * 2] = t->ch_exts[idx];
t->ch_exts[idx * 2 + 1] = t->ch_exts[idx];
}
return 0;
}
static void
ch_table_fill(struct ch_table *t, uint64_t idx, struct ch_group *table,
struct ch_meta *meta)
{
uint64_t cnt, i;
idx <<= (t->ch_level - meta->cs_local_level);
cnt = 1ULL << (t->ch_level - meta->cs_local_level);
for (i = 0; i < cnt; i++) {
t->ch_exts[idx + i].ce_table = table;
t->ch_exts[idx + i].ce_meta = meta;
}
}
static struct ch_ext *
ch_table_buddy(struct ch_table *t, uint64_t idx, uint32_t local_level)
{
struct ch_meta *m;
if (local_level == 0)
return NULL;
idx ^= 1ULL << (t->ch_level - local_level);
m = t->ch_exts[idx].ce_meta;
if (m->cs_local_level != local_level)
return NULL;
return &t->ch_exts[idx];
}
static int
ch_table_grow(const struct ch_type *type, struct ch_table *t, uint64_t h,
struct ch_group *table, struct ch_meta *meta)
{
struct ch_group *left = NULL, *right = NULL;
struct ch_meta *leftmeta = NULL, *rightmeta = NULL;
uint64_t idx;
if (meta->cs_local_level == t->ch_level) {
if (ch_table_resize(type, t) == -1)
goto fail;
}
if (ch_sub_alloc(type, t, &left, &leftmeta) == -1)
goto fail;
if (ch_sub_alloc(type, t, &right, &rightmeta) == -1)
goto fail;
if (ch_sub_split(type, table, left, right,
meta, leftmeta, rightmeta) == -1)
goto fail;
idx = CH_H1(h, meta->cs_local_level) << 1;
ch_table_fill(t, idx, left, leftmeta);
ch_table_fill(t, idx | 1, right, rightmeta);
ch_sub_free(type, t, table, meta);
return 0;
fail:
ch_sub_free(type, t, right, rightmeta);
ch_sub_free(type, t, left, leftmeta);
return -1;
}
static int
ch_table_compact(const struct ch_type *type, struct ch_table *t, uint64_t h,
struct ch_group *table, struct ch_meta *meta)
{
struct ch_ext *buddy;
struct ch_group *to = NULL;
struct ch_meta *tometa = NULL;
uint64_t idx;
idx = CH_H1(h, t->ch_level);
buddy = ch_table_buddy(t, idx, meta->cs_local_level);
if (buddy == NULL || ch_sub_fillfactor(meta) +
ch_sub_fillfactor(buddy->ce_meta) > CH_MAX_LOAD * 2 / 3)
return -1;
if (ch_sub_alloc(type, t, &to, &tometa) == -1)
goto fail;
if (ch_sub_merge(type, to, table, buddy->ce_table,
tometa, meta, buddy->ce_meta) == -1)
goto fail;
idx = CH_H1(h, tometa->cs_local_level);
ch_sub_free(type, t, buddy->ce_table, buddy->ce_meta);
ch_sub_free(type, t, table, meta);
ch_table_fill(t, idx, to, tometa);
return 0;
fail:
ch_sub_free(type, t, to, tometa);
return -1;
}
int
_ch_init(const struct ch_type *type, struct ch_table *t)
{
struct ch_group *table = NULL;
struct ch_meta *meta = NULL;
t->ch_level = 0;
memset(&t->ch_counts, 0, sizeof(t->ch_counts));
if (ch_sub_alloc(type, t, &table, &meta) == -1)
goto fail;
if (ch_table_resize(type, t) == -1)
goto fail;
ch_table_fill(t, 0, table, meta);
return 0;
fail:
ch_sub_free(type, t, table, meta);
return -1;
}
void
_ch_destroy(const struct ch_type *type, struct ch_table *t)
{
uint64_t idx, max = 1ULL << t->ch_level;
struct ch_group *table = NULL;
for (idx = 0; idx < max; idx++) {
if (table == t->ch_exts[idx].ce_table)
continue;
table = t->ch_exts[idx].ce_table;
ch_sub_free(type, t, t->ch_exts[idx].ce_table,
t->ch_exts[idx].ce_meta);
}
free(t->ch_exts);
type->t_counts->cc_num_extendible -= max;
memset(t, 0, sizeof(*t));
}
void *
_ch_insert(const struct ch_type *type, struct ch_table *t, uint64_t h,
void *elm)
{
struct ch_group *table;
struct ch_meta *meta;
void *v;
uint64_t idx;
if (t->ch_exts == NULL)
if (_ch_init(type, t) == -1)
return CH_INS_FAILED;
idx = CH_H1(h, t->ch_level);
table = t->ch_exts[idx].ce_table;
meta = t->ch_exts[idx].ce_meta;
if (ch_sub_loadfactor(meta) >= CH_MAX_LOAD) {
if (ch_table_grow(type, t, h, table, meta) == -1)
return CH_INS_FAILED;
idx = CH_H1(h, t->ch_level);
table = t->ch_exts[idx].ce_table;
meta = t->ch_exts[idx].ce_meta;
}
v = ch_sub_insert(type, table, meta, h, elm);
if (v == NULL) {
t->ch_counts.cc_num_elm++;
type->t_counts->cc_num_elm++;
}
return v;
}
void *
_ch_remove(const struct ch_type *type, struct ch_table *t, uint64_t h,
const void *needle)
{
struct ch_group *table;
struct ch_meta *meta;
void *v;
uint64_t idx;
if (t->ch_exts == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_exts[idx].ce_table;
meta = t->ch_exts[idx].ce_meta;
v = ch_sub_remove(type, table, meta, h, needle);
if (v != NULL) {
t->ch_counts.cc_num_elm--;
type->t_counts->cc_num_elm--;
while (ch_sub_fillfactor(meta) <= CH_MAX_LOAD / 4) {
if (ch_table_compact(type, t, h, table, meta) == -1)
break;
table = t->ch_exts[idx].ce_table;
meta = t->ch_exts[idx].ce_meta;
}
}
return v;
}
void *
_ch_find(const struct ch_type *type, struct ch_table *t, uint64_t h,
const void *needle)
{
struct ch_group *table;
uint64_t idx;
if (t->ch_exts == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_exts[idx].ce_table;
return ch_sub_find(type, table, h, needle);
}
void *
_ch_locate(const struct ch_type *type, struct ch_table *t, uint64_t h,
int (*eq)(const void *, const void *), const void *arg)
{
struct ch_group *table;
uint64_t idx;
if (t->ch_exts == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_exts[idx].ce_table;
return ch_sub_locate(type, table, h, eq, arg);
}
void *
_ch_first(const struct ch_type *type, struct ch_table *t, struct ch_iter *it)
{
struct ch_group *table;
uint64_t idx;
if (t->ch_exts == NULL)
return NULL;
idx = it->ci_ext_idx = 0;
table = t->ch_exts[idx].ce_table;
return ch_sub_first(type, table, it);
}
void *
_ch_next(const struct ch_type *type, struct ch_table *t, struct ch_iter *it)
{
struct ch_group *table;
uint64_t idx, max;
void *v;
if (t->ch_exts == NULL)
return NULL;
max = 1ULL << t->ch_level;
idx = it->ci_ext_idx;
if (idx >= max)
return NULL;
table = t->ch_exts[idx].ce_table;
v = ch_sub_next(type, table, it);
if (v != NULL)
return v;
for (idx++; idx < max; idx++) {
if (table != t->ch_exts[idx].ce_table)
break;
}
if (idx >= max)
return NULL;
it->ci_ext_idx = idx;
table = t->ch_exts[idx].ce_table;
return ch_sub_first(type, table, it);
}
void
_ch_get_stats(struct ch_stats *stats, const struct ch_counts *counts)
{
stats->cs_num_elm = counts->cc_num_elm;
stats->cs_num_tables = counts->cc_num_tables;
stats->cs_num_extendible = counts->cc_num_extendible;
stats->cs_max_elm = counts->cc_num_tables * CH_H2_SIZE * 7;
stats->cs_size_tables = counts->cc_num_tables *
(CH_H2_SIZE * sizeof(struct ch_group) + sizeof(struct ch_meta));
stats->cs_size_extendible = stats->cs_num_extendible *
sizeof(struct ch_ext);
}
static const uint64_t prime = 0x9ae16a3b2f90404fULL;
static inline uint64_t
ch_rotate(uint64_t val, int shift)
{
return shift == 0 ? val : ((val >> shift) | (val << (64 - shift)));
}
static inline uint64_t
ch_mix(uint64_t u, uint64_t v, uint64_t mul)
{
uint64_t a, b;
a = (u ^ v) * mul;
a ^= (a >> 47);
b = (v ^ a) * mul;
b ^= (b >> 47);
return b * mul;
}
uint64_t
ch_qhash64(uint64_t hash, uint64_t value)
{
uint64_t mul = prime + 2 * sizeof(value);
uint64_t a = value + prime;
uint64_t b = hash;
uint64_t c, d;
c = ch_rotate(b, 37) * mul + a;
d = (ch_rotate(a, 25) + b) * mul;
return ch_mix(c, d, mul);
}