28#define SHT_DEF_CAPCITY 6
40#define SHT_DEF_PSL_LIMIT UINT8_C(127)
46#define SHT_MAX_TSIZE (UINT32_C(1) << 24)
52#define SHT_MAX_ITERS UINT16_C(0x7fff)
75 union sht_bckt *buckets;
114enum sht_iter_type: _Bool {
127 enum sht_iter_type type;
153static void sht_err_print(
const char *msg)
155 fprintf(stderr,
"Fatal SHT error: %s\n", msg);
161void (*sht_abort_print)(
const char *msg) = sht_err_print;
169static void sht_abort(
const char *msg)
203 static const char *err_msgs[] = {
215 _Static_assert(
sizeof err_msgs /
sizeof err_msgs[0] ==
SHT_ERR_COUNT,
219 sht_abort(
"sht_msg: Invalid error code");
221 return err_msgs[err];
263SHT_FNATTR(optimize(
"-fno-delete-null-pointer-checks"))
265static void sht_assert_nonnull(
const void *p,
const char *msg)
267 const void *
volatile vp = p;
299 size_t ealign,
enum sht_err *err)
304 sht_assert_nonnull((
const void *)(uintptr_t)hashfn,
305 "sht_new_: hashfn must not be NULL");
306 sht_assert_nonnull((
const void *)(uintptr_t)eqfn,
307 "sht_new_: eqfn must not be NULL");
308 if (__builtin_popcountg(ealign) != 1)
309 sht_abort(
"sht_new_: ealign not a power of 2");
310 if (esize % ealign != 0)
311 sht_abort(
"sht_new_: Incompatible values of esize and ealign");
313 if (esize > SHT_MAX_ESIZE) {
318 if ((ht = calloc(1,
sizeof *ht)) == NULL) {
327 ht->lft = SHT_DEF_LFT;
328 ht->psl_limit = SHT_DEF_PSL_LIMIT;
353 sht_abort(
"sht_set_hash_ctx: Table already initialized");
354 ht->hash_ctx = context;
377 sht_abort(
"sht_set_eq_ctx: Table already initialized");
378 ht->eq_ctx = context;
403 sht_abort(
"sht_set_freefn: Table already initialized");
405 ht->free_ctx = context;
432 sht_abort(
"sht_set_lft: Table already initialized");
433 if (lft < 1 || lft > 100)
434 sht_abort(
"sht_set_lft: Invalid load factor threshold");
462 sht_abort(
"sht_set_psl_limit: Table already initialized");
463 if (limit < 1 || limit > 127)
464 sht_abort(
"sht_set_psl_limit: Invalid PSL threshold");
465 ht->psl_limit = limit;
484static _Bool sht_alloc_arrays(
struct sht_ht *ht, uint32_t tsize)
492 _Static_assert(SHT_MAX_TSIZE == 1 << 24,
"maximum table size");
493 _Static_assert(
sizeof(
union sht_bckt) == 4,
"sht_bckt size");
494 _Static_assert(_Alignof(
union sht_bckt) == 4,
"sht_bckt alignment");
496 assert(tsize <= SHT_MAX_TSIZE);
498 b_size = tsize *
sizeof(
union sht_bckt);
500 if (__builtin_mul_overflow(tsize, ht->esize, &e_size)) {
501 ht->err = SHT_ERR_TOOBIG;
505 pad = (ht->ealign - b_size % ht->ealign) % ht->ealign;
507 if (__builtin_add_overflow(b_size + pad, e_size, &size)) {
512 if ((
new = malloc(size)) == NULL) {
518 memset(
new, 0xff, b_size);
520 ht->buckets = (
union sht_bckt *)(
void *)
new;
521 ht->entries =
new + b_size + pad;
523 ht->mask = tsize - 1;
524 ht->thold = tsize * ht->lft / 100;
558_Bool
sht_init(
struct sht_ht *ht, uint32_t capacity)
561 sht_abort(
"sht_init: Table already initialized");
564 if (capacity > SHT_MAX_TSIZE) {
570 capacity = SHT_DEF_CAPCITY;
573 capacity = (capacity * 100 + ht->lft - 1) / ht->lft;
576 capacity = UINT32_C(1) << (32 - __builtin_clzg(capacity - 1, 1));
579 if (capacity > SHT_MAX_TSIZE) {
584 return sht_alloc_arrays(ht, capacity);
604 sht_abort(
"sht_size: Table not initialized");
626 sht_abort(
"sht_empty: Table not initialized");
627 return ht->count == 0;
641static void sht_set_entry(
struct sht_ht *ht,
const uint8_t *restrict c_entry,
642 const union sht_bckt *restrict c_bckt,
643 uint8_t *restrict o_entry,
644 union sht_bckt *restrict o_bckt)
647 memcpy(o_entry, c_entry, ht->esize);
650 ht->psl_sum += c_bckt->psl;
652 if (c_bckt->psl > ht->peak_psl)
653 ht->peak_psl = c_bckt->psl;
655 if (c_bckt->psl == ht->psl_limit) {
657 assert(ht->max_psl_ct < ht->thold);
673static void sht_remove_entry(
struct sht_ht *ht,
const uint8_t *restrict o_entry,
674 const union sht_bckt *restrict o_bckt,
675 uint8_t *restrict t_entry,
676 union sht_bckt *restrict t_bckt)
679 memcpy(t_entry, o_entry, ht->esize);
682 ht->psl_sum -= o_bckt->psl;
684 if (o_bckt->psl == ht->psl_limit) {
685 assert(ht->max_psl_ct > 0);
721static int32_t sht_probe(
struct sht_ht *ht, uint32_t hash,
const void *key,
722 const uint8_t *ce, _Bool c_uniq)
724 uint8_t e_tmp[2][ht->esize];
725 union sht_bckt b_tmp[2];
732 assert( (key != NULL && ce == NULL && c_uniq == 0)
733 || (key != NULL && ce != NULL && c_uniq == 0)
734 || (key == NULL && ce != NULL && c_uniq == 1)
748 ob = ht->buckets + p;
749 oe = ht->entries + p * ht->esize;
754 if (ht->count == ht->thold)
756 sht_set_entry(ht, ce, cb, oe, ob);
763 && cb->all == ob->all
764 && ht->eqfn(key, oe, ht->eq_ctx)
770 if (cb->psl > ob->psl) {
775 if (!c_uniq && ht->count == ht->thold)
778 sht_remove_entry(ht, oe, ob, e_tmp[ti], b_tmp + ti);
780 sht_set_entry(ht, ce, cb, oe, ob);
791 assert(cb->psl < ht->psl_limit);
807static _Bool sht_ht_grow(
struct sht_ht *ht)
809 union sht_bckt *b, *old;
814 if (ht->tsize == SHT_MAX_TSIZE) {
823 if (!sht_alloc_arrays(ht, ht->tsize * 2))
826 for (i = 0; i < ht->tsize / 2; ++i, ++b, e += ht->esize) {
828 result = sht_probe(ht, b->hash, NULL, e, 1);
829 assert(result == -1);
859static int sht_insert(
struct sht_ht *ht,
const void *key,
860 const void *entry, _Bool replace)
867 sht_abort(
"sht_add/sht_set: Table not initialized");
868 if (ht->iter_lock != 0)
869 sht_abort(
"sht_add/sht_set: Table has iterator(s)");
871 assert(key != NULL && entry != NULL);
873 if (ht->max_psl_ct != 0) {
878 hash = ht->hashfn(key, ht->hash_ctx);
880 result = sht_probe(ht, hash, key, entry, 0);
884 current = ht->entries + result * ht->esize;
885 if (ht->freefn != NULL)
886 ht->freefn(current, ht->free_ctx);
887 memcpy(current, entry, ht->esize);
895 assert(result == -2);
897 if (!sht_ht_grow(ht))
900 result = sht_probe(ht, hash, NULL, entry, 1);
901 assert(result == -1);
928int sht_add(
struct sht_ht *ht,
const void *key,
const void *entry)
930 return sht_insert(ht, key, entry, 0);
956int sht_set(
struct sht_ht *ht,
const void *key,
const void *entry)
958 return sht_insert(ht, key, entry, 1);
984const void *
sht_get(
struct sht_ht *ht,
const void *restrict key)
990 sht_abort(
"sht_get: Table not initialized");
992 hash = ht->hashfn(key, ht->hash_ctx);
993 result = sht_probe(ht, hash, key, NULL, 0);
996 assert(result == -1);
1000 return ht->entries + result * ht->esize;
1014static void sht_change_at(
struct sht_ht *ht, uint32_t pos,
1015 const void *entry,
void *out)
1019 e = ht->entries + pos * ht->esize;
1022 if (ht->freefn != NULL)
1023 ht->freefn(e, ht->free_ctx);
1024 memcpy(e, entry, ht->esize);
1026 else if (out == entry) {
1027 uint8_t tmp[ht->esize];
1028 memcpy(tmp, e, ht->esize);
1029 memcpy(e, entry, ht->esize);
1030 memcpy(out, tmp, ht->esize);
1033 memcpy(out, e, ht->esize);
1034 memcpy(e, entry, ht->esize);
1056static _Bool sht_change(
struct sht_ht *ht,
const void *key,
1057 const void *entry,
void *out)
1063 sht_abort(
"sht_replace/sht_swap: Table not initialized");
1065 hash = ht->hashfn(key, ht->hash_ctx);
1066 pos = sht_probe(ht, hash, key, NULL, 0);
1073 sht_change_at(ht, pos, entry, out);
1096_Bool
sht_replace(
struct sht_ht *ht,
const void *key,
const void *entry)
1098 return sht_change(ht, key, entry, NULL);
1123_Bool
sht_swap(
struct sht_ht *ht,
const void *key,
const void *entry,
void *out)
1125 return sht_change(ht, key, entry, out);
1137static void sht_shift(
struct sht_ht *ht, uint32_t dest, uint32_t count)
1141 assert(dest + count < ht->tsize);
1144 memmove(ht->entries + dest * ht->esize,
1145 ht->entries + (dest + 1) * ht->esize,
1149 memmove(ht->buckets + dest,
1150 ht->buckets + dest + 1,
1151 count *
sizeof(
union sht_bckt));
1154 for (i = dest; i < dest + count; ++i) {
1156 if (ht->buckets[i].psl == ht->psl_limit) {
1157 assert(ht->max_psl_ct > 0);
1161 ht->buckets[i].psl--;
1165 ht->psl_sum -= count;
1173static void sht_shift_wrap(
struct sht_ht *ht)
1178 memcpy(ht->entries + ht->mask * ht->esize, ht->entries, ht->esize);
1181 ht->buckets[ht->mask] = ht->buckets[0];
1184 if (ht->buckets[ht->mask].psl == ht->psl_limit) {
1185 assert(ht->max_psl_ct > 0);
1189 ht->buckets[ht->mask].psl--;
1202static void sht_remove_at(
struct sht_ht *ht, uint32_t pos,
void *restrict out)
1208 memcpy(out, ht->entries + pos * ht->esize, ht->esize);
1210 else if (ht->freefn != NULL) {
1211 ht->freefn(ht->entries + pos * ht->esize, ht->free_ctx);
1215 ht->psl_sum -= ht->buckets[pos].psl;
1220 next = (pos + 1) & ht->mask;
1221 while (!ht->buckets[next].empty && ht->buckets[next].psl != 0) {
1223 next = (next + 1) & ht->mask;
1227 if ((uint32_t)pos == end) {
1230 else if ((uint32_t)pos < end) {
1232 sht_shift(ht, pos, end - pos);
1236 if ((uint32_t)pos < ht->mask)
1237 sht_shift(ht, pos, ht->mask - pos);
1243 sht_shift(ht, 0, end);
1247 ht->buckets[end].empty = 1;
1268static _Bool sht_remove(
struct sht_ht *ht,
const void *restrict key,
1275 sht_abort(
"sht_pop/sht_delete: Table not initialized");
1276 if (ht->iter_lock != 0)
1277 sht_abort(
"sht_pop/sht_delete: Table has iterator(s)");
1280 hash = ht->hashfn(key, ht->hash_ctx);
1281 pos = sht_probe(ht, hash, key, NULL, 0);
1287 sht_remove_at(ht, pos, out);
1311_Bool
sht_pop(
struct sht_ht *ht,
const void *restrict key,
void *restrict out)
1313 return sht_remove(ht, key, out);
1336 return sht_remove(ht, key, NULL);
1357 if (ht->iter_lock != 0)
1358 sht_abort(
"sht_free: Table has iterator(s)");
1360 if (ht->freefn != NULL) {
1362 for (i = 0, b = ht->buckets; i < ht->tsize; ++i, ++b) {
1365 e = ht->entries + i * ht->esize;
1366 ht->freefn(e, ht->free_ctx);
1388static struct sht_iter *sht_iter_new(
struct sht_ht *ht,
1389 enum sht_iter_type type)
1391 struct sht_iter *iter;
1395 sht_abort(
"sht_ro_iter/sht_rw_iter: Table not initialized");
1397 if (type == SHT_ITER_RO) {
1399 if (ht->iter_lock == UINT16_MAX) {
1404 if ((lock = ht->iter_lock + 1) > SHT_MAX_ITERS) {
1410 if (ht->iter_lock != 0) {
1418 if ((iter = calloc(1,
sizeof *iter)) == NULL) {
1427 ht->iter_lock = lock;
1450 return (
struct sht_ro_iter *)sht_iter_new(ht, SHT_ITER_RO);
1471 return (
struct sht_rw_iter *)sht_iter_new(ht, SHT_ITER_RW);
1570static void *sht_iter_next(
struct sht_iter *iter)
1573 const struct sht_ht *ht;
1575 if (iter->last == INT32_MAX)
1578 assert(iter->last < (int32_t)SHT_MAX_TSIZE);
1580 if (iter->last == -1)
1583 next = iter->last + 1;
1585 for (ht = iter->ht; next < ht->tsize; ++next) {
1587 if (!ht->buckets[next].empty) {
1589 return ht->entries + next * ht->esize;
1593 iter->last = INT32_MAX;
1613 return sht_iter_next(&iter->i);
1632 return sht_iter_next(&iter->i);
1645 if (iter->i.last == -1 || iter->i.last == INT32_MAX) {
1650 assert(iter->i.last >= 0 && (uint32_t)iter->i.last < iter->i.ht->tsize);
1651 assert(!iter->i.ht->buckets[iter->i.last].empty);
1653 sht_remove_at(iter->i.ht, iter->i.last, NULL);
1681static _Bool sht_iter_replace(
struct sht_iter *iter,
const void *restrict entry)
1683 if (iter->last == -1 || iter->last == INT32_MAX) {
1688 assert(iter->last >= 0 && (uint32_t)iter->last < iter->ht->tsize);
1689 assert(!iter->ht->buckets[iter->last].empty);
1691 sht_change_at(iter->ht, iter->last, entry, NULL);
1718 const void *restrict entry)
1720 return sht_iter_replace(&iter->i, entry);
1745 const void *restrict entry)
1747 return sht_iter_replace(&iter->i, entry);
1759static void sht_iter_free(
struct sht_iter *iter)
1765 if (iter->type == SHT_ITER_RO) {
1766 assert(ht->iter_lock > 0 && ht->iter_lock <= SHT_MAX_ITERS);
1770 assert(ht->iter_lock == UINT16_MAX);
1790 sht_iter_free(&iter->i);
1806 sht_iter_free(&iter->i);
void * sht_rw_iter_next_(struct sht_rw_iter *iter)
(Get the next entry from a read/write iterator.)
const void * sht_get(struct sht_ht *ht, const void *restrict key)
Lookup an entry in a table.
struct sht_rw_iter * sht_rw_iter(struct sht_ht *ht)
Create a new read/write iterator.
_Bool sht_empty(const struct sht_ht *ht)
Determine whether a table is empty.
void sht_rw_iter_free_(struct sht_rw_iter *iter)
(Free a read/write iterator.)
int sht_set(struct sht_ht *ht, const void *key, const void *entry)
Unconditionally set the value associated with a key.
void sht_set_hash_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's hash function.
struct sht_ro_iter * sht_ro_iter(struct sht_ht *ht)
Create a new read-only iterator.
int sht_add(struct sht_ht *ht, const void *key, const void *entry)
Add an entry to the table, if its key is not already present.
const void * sht_ro_iter_next_(struct sht_ro_iter *iter)
(Get the next entry from a read-only iterator.)
const char * sht_rw_iter_msg_(const struct sht_rw_iter *iter)
(Get a description of a read/write iterator's last error.)
_Bool sht_ro_iter_replace_(struct sht_ro_iter *iter, const void *restrict entry)
(Replace the last entry returned by a read-only iterator.)
_Bool sht_delete(struct sht_ht *ht, const void *restrict key)
Remove an entry from the table.
_Bool sht_replace(struct sht_ht *ht, const void *key, const void *entry)
Replace the entry associated with an existing key.
_Bool sht_pop(struct sht_ht *ht, const void *restrict key, void *restrict out)
Remove and return an entry from the table.
uint32_t sht_size(const struct sht_ht *ht)
Get the number of entries in a table.
enum sht_err sht_get_err(const struct sht_ht *ht)
Get the error code of a table's last error.
enum sht_err sht_ro_iter_err_(const struct sht_ro_iter *iter)
(Get the error code of a read-only iterator's last error.)
_Bool sht_iter_delete(struct sht_rw_iter *iter)
Remove the last entry returned by a read/write iterator.
enum sht_err sht_rw_iter_err_(const struct sht_rw_iter *iter)
(Get the error code of a read/write iterator's last error.)
void sht_set_lft(struct sht_ht *ht, uint8_t lft)
Set the load factor threshold for a table.
void sht_set_eq_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's equality function.
void sht_set_psl_limit(struct sht_ht *ht, uint8_t limit)
Set the PSL limit of a table.
const char * sht_msg(enum sht_err err)
Get the description for an error code.
struct sht_ht * sht_new_(sht_hashfn_t hashfn, sht_eqfn_t eqfn, size_t esize, size_t ealign, enum sht_err *err)
(Create a new hash table.)
void sht_ro_iter_free_(struct sht_ro_iter *iter)
(Free a read-only iterator.)
const char * sht_get_msg(const struct sht_ht *ht)
Get a description of a table's last error.
_Bool sht_init(struct sht_ht *ht, uint32_t capacity)
Initialize a hash table.
void sht_set_freefn(struct sht_ht *ht, sht_freefn_t freefn, void *context)
Set the optional entry resource free function for a table.
_Bool sht_rw_iter_replace_(struct sht_rw_iter *iter, const void *restrict entry)
(Replace the last entry returned by a read/write iterator.)
void sht_free(struct sht_ht *ht)
Free the resources used by a hash table.
_Bool sht_swap(struct sht_ht *ht, const void *key, const void *entry, void *out)
Exchange an existing entry and a new entry.
const char * sht_ro_iter_msg_(const struct sht_ro_iter *iter)
(Get a description of a read-only iterator's last error.)
@ SHT_ERR_TOOBIG
Requested table size too large.
@ SHT_ERR_ITER_COUNT
Table has too many iterators.
@ SHT_ERR_BAD_ESIZE
Entry size too large (> 16KiB).
@ SHT_ERR_ALLOC
Memory allocation failed.
@ SHT_ERR_BAD_HASH
Too many hash collisions.
@ SHT_ERR_ITER_NO_LAST
Iterator at beginning or end.
@ SHT_ERR_COUNT
(Not an error; used for bounds checks.)
@ SHT_ERR_ITER_LOCK
Can't acquire iterator lock.
void(* sht_freefn_t)(const void *restrict entry, void *restrict context)
Free function type.
_Bool(* sht_eqfn_t)(const void *restrict key, const void *restrict entry, void *restrict context)
Equality comparison function type.
void(* sht_abort_print)(const char *msg)
Critical error printing function.
uint32_t(* sht_hashfn_t)(const void *restrict key, void *restrict context)
Hash function type.