#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;
};
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 uint8_t
ch_meta_locate(struct ch_group *g, uint64_t mask)
{
uint64_t lookup;
uint8_t flags, i, hits;
lookup = g->cg_meta ^ mask;
flags = cg_meta_get_flags(g);
hits = flags & CH_EVER_FULL;
for (i = 0; i < 7; i++) {
if (((lookup >> i * 8) & CH_H3_MASK) == 0)
hits |= flags & (1 << i);
}
return 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;
struct ch_group *g = &table[bucket], *ins_g = NULL;
memset(&mask, CH_H3(h), sizeof(mask));
while (1) {
hits = ch_meta_locate(g, mask);
for (i = 0; i < 7; i++) {
if (hits & (1 << i)) {
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;
struct ch_group *g = &table[bucket];
memset(&mask, CH_H3(h), sizeof(mask));
while (1) {
hits = ch_meta_locate(g, mask);
for (i = 0; i < 7; i++) {
if (hits & (1 << i)) {
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;
struct ch_group *g = &table[bucket];
memset(&mask, CH_H3(h), sizeof(mask));
while (1) {
hits = ch_meta_locate(g, mask);
for (i = 0; i < 7; i++) {
if (hits & (1 << i)) {
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 (*cmp)(const void *, void *), void *arg)
{
uint64_t mask;
uint32_t bucket = CH_H2(h);
int i;
uint8_t hits;
struct ch_group *g = &table[bucket];
memset(&mask, CH_H3(h), sizeof(mask));
while (1) {
hits = ch_meta_locate(g, mask);
for (i = 0; i < 7; i++) {
if (hits & (1 << i)) {
if (cmp(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)
{
t->ch_counts.cc_num_tables--;
type->t_counts->cc_num_tables--;
free(table);
free(meta);
}
static int
ch_table_resize(struct ch_table *t)
{
struct ch_group **tables;
struct ch_meta **metas;
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_tables == NULL) {
oldsize = 0;
newsize = 1;
}
tables = reallocarray(t->ch_tables, newsize, sizeof(*tables));
if (tables == NULL)
return -1;
metas = reallocarray(t->ch_metas, newsize, sizeof(*metas));
if (metas == NULL) {
free(tables);
return -1;
}
for (idx = oldsize - 1; idx >= 0; idx--) {
tables[idx * 2] = tables[idx];
tables[idx * 2 + 1] = tables[idx];
metas[idx * 2] = metas[idx];
metas[idx * 2 + 1] = metas[idx];
}
if (t->ch_tables != NULL)
t->ch_level++;
t->ch_tables = tables;
t->ch_metas = metas;
t->ch_counts.cc_num_extendible += oldsize;
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_tables[idx + i] = table;
t->ch_metas[idx + i] = meta;
}
}
static struct ch_group *
ch_table_buddy(struct ch_table *t, uint64_t idx, uint32_t local_level,
struct ch_meta **meta)
{
struct ch_meta *m;
if (local_level == 0)
return NULL;
idx ^= 1ULL << (t->ch_level - local_level);
m = t->ch_metas[idx];
if (m->cs_local_level == local_level) {
*meta = m;
return t->ch_tables[idx];
}
return NULL;
}
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(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_group *buddy, *to = NULL;
struct ch_meta *buddymeta, *tometa = NULL;
uint64_t idx;
idx = CH_H1(h, t->ch_level);
buddy = ch_table_buddy(t, idx, meta->cs_local_level, &buddymeta);
if (buddy == NULL || ch_sub_fillfactor(meta) +
ch_sub_fillfactor(buddymeta) > 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, tometa, meta, buddymeta) ==
-1)
goto fail;
idx = CH_H1(h, tometa->cs_local_level);
ch_table_fill(t, idx, to, tometa);
ch_sub_free(type, t, buddy, buddymeta);
ch_sub_free(type, t, table, meta);
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(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_tables[idx])
continue;
table = t->ch_tables[idx];
ch_sub_free(type, t, t->ch_tables[idx], t->ch_metas[idx]);
}
free(t->ch_tables);
free(t->ch_metas);
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_tables == NULL)
if (_ch_init(type, t) == -1)
return CH_INS_FAILED;
idx = CH_H1(h, t->ch_level);
table = t->ch_tables[idx];
meta = t->ch_metas[idx];
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_tables[idx];
meta = t->ch_metas[idx];
}
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_tables == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_tables[idx];
meta = t->ch_metas[idx];
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_tables[idx];
meta = t->ch_metas[idx];
}
}
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_tables == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_tables[idx];
return ch_sub_find(type, table, h, needle);
}
void *
_ch_locate(const struct ch_type *type, struct ch_table *t, uint64_t h,
int (*cmp)(const void *, void *), void *arg)
{
struct ch_group *table;
uint64_t idx;
if (t->ch_tables == NULL)
return NULL;
idx = CH_H1(h, t->ch_level);
table = t->ch_tables[idx];
return ch_sub_locate(type, table, h, cmp, 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_tables == NULL)
return NULL;
idx = it->ci_ext_idx = 0;
table = t->ch_tables[idx];
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_tables == NULL)
return NULL;
max = 1ULL << t->ch_level;
idx = it->ci_ext_idx;
if (idx >= max)
return NULL;
table = t->ch_tables[idx];
v = ch_sub_next(type, table, it);
if (v != NULL)
return v;
for (idx++; idx < max; idx++) {
if (table != t->ch_tables[idx])
break;
}
if (idx >= max)
return NULL;
it->ci_ext_idx = idx;
table = t->ch_tables[idx];
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_group *) + sizeof(struct ch_meta *));
}
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);
}