34#define SHT_DEF_CAPCITY 6
48#define SHT_DEF_PSL_LIMIT UINT8_C(127)
55#define SHT_MAX_TSIZE (UINT32_C(1) << 24)
62#define SHT_MAX_ITERS UINT16_C(0x7fff)
85 union sht_bckt *buckets;
138static void sht_err_print(
const char *msg)
140 fprintf(stderr,
"Fatal SHT error: %s\n", msg);
146void (*sht_abort_print)(
const char *msg) = sht_err_print;
154static void sht_abort(
const char *msg)
188 static const char *err_msgs[] = {
200 static_assert(
sizeof err_msgs /
sizeof err_msgs[0] ==
SHT_ERR_COUNT);
203 sht_abort(
"sht_msg: Invalid error code");
205 return err_msgs[err];
248[[gnu::optimize(
"-fno-delete-null-pointer-checks")]]
250static void sht_assert_nonnull(
void (*p)(
void),
const char *msg)
252 void (*
volatile vp)(void) = p;
285 size_t ealign,
enum sht_err *err)
289 sht_assert_nonnull((
void (*)(
void))hashfn,
290 "sht_new_: hashfn must not be NULL");
291 sht_assert_nonnull((
void (*)(
void))eqfn,
292 "sht_new_: eqfn must not be NULL");
293 if (!stdc_has_single_bit(ealign))
294 sht_abort(
"sht_new_: ealign not a power of 2");
295 if (esize % ealign != 0)
296 sht_abort(
"sht_new_: Incompatible values of esize and ealign");
298 if (esize > SHT_MAX_ESIZE) {
303 if ((ht = calloc(1,
sizeof *ht)) ==
nullptr) {
313 ht->lft = SHT_DEF_LFT;
314 ht->psl_limit = SHT_DEF_PSL_LIMIT;
339 sht_abort(
"sht_set_hash_ctx: Table already initialized");
340 ht->hash_ctx = context;
363 sht_abort(
"sht_set_eq_ctx: Table already initialized");
364 ht->eq_ctx = context;
387 sht_abort(
"sht_set_freefn: Table already initialized");
388 ht->free_ctx = context;
415 sht_abort(
"sht_set_lft: Table already initialized");
416 if (lft < 1 || lft > 100)
417 sht_abort(
"sht_set_lft: Invalid load factor threshold");
445 sht_abort(
"sht_set_psl_limit: Table already initialized");
446 if (limit < 1 || limit > 127)
447 sht_abort(
"sht_set_psl_limit: Invalid PSL threshold");
448 ht->psl_limit = limit;
467static bool sht_alloc_arrays(
struct sht_ht *ht, uint32_t tsize)
475 static_assert(SHT_MAX_TSIZE == 1 << 24);
476 static_assert(
sizeof(
union sht_bckt) == 4);
477 static_assert(
alignof(
union sht_bckt) == 4);
479 assert(tsize <= SHT_MAX_TSIZE);
481 b_size = tsize *
sizeof(
union sht_bckt);
483 if (ckd_mul(&e_size, tsize, ht->esize)) {
484 ht->err = SHT_ERR_TOOBIG;
488 pad = (ht->ealign - b_size % ht->ealign) % ht->ealign;
490 if (ckd_add(&size, b_size + pad, e_size)) {
495 if ((
new = malloc(size)) ==
nullptr) {
501 memset(
new, 0xff, b_size);
503 ht->buckets = (
union sht_bckt *)(
void *)
new;
504 ht->entries =
new + b_size + pad;
506 ht->mask = tsize - 1;
507 ht->thold = tsize * ht->lft / 100;
544 sht_abort(
"sht_init: Table already initialized");
547 if (capacity > SHT_MAX_TSIZE) {
553 capacity = SHT_DEF_CAPCITY;
556 capacity = (capacity * 100 + ht->lft - 1) / ht->lft;
559 capacity = stdc_bit_ceil(capacity);
562 if (capacity > SHT_MAX_TSIZE) {
567 return sht_alloc_arrays(ht, capacity);
587 sht_abort(
"sht_size: Table not initialized");
609 sht_abort(
"sht_empty: Table not initialized");
610 return ht->count == 0;
642static void sht_set_entry(
struct sht_ht *ht,
const uint8_t *restrict c_entry,
643 const union sht_bckt *restrict c_bckt,
644 uint8_t *restrict o_entry,
645 union sht_bckt *restrict o_bckt)
648 memcpy(o_entry, c_entry, ht->esize);
651 ht->psl_sum += c_bckt->psl;
653 if (c_bckt->psl > ht->peak_psl)
654 ht->peak_psl = c_bckt->psl;
656 if (c_bckt->psl == ht->psl_limit) {
658 assert(ht->max_psl_ct < ht->thold);
674static void sht_remove_entry(
struct sht_ht *ht,
const uint8_t *restrict o_entry,
675 const union sht_bckt *restrict o_bckt,
676 uint8_t *restrict t_entry,
677 union sht_bckt *restrict t_bckt)
680 memcpy(t_entry, o_entry, ht->esize);
683 ht->psl_sum -= o_bckt->psl;
685 if (o_bckt->psl == ht->psl_limit) {
686 assert(ht->max_psl_ct > 0);
722static int32_t sht_probe(
struct sht_ht *ht, uint32_t hash,
const void *key,
723 const uint8_t *ce,
bool c_uniq)
725 uint8_t e_tmp[2][ht->esize];
726 union sht_bckt b_tmp[2];
733 assert( (key !=
nullptr && ce ==
nullptr && c_uniq == 0)
734 || (key !=
nullptr && ce !=
nullptr && c_uniq == 0)
735 || (key ==
nullptr && ce !=
nullptr && c_uniq == 1)
749 ob = ht->buckets + p;
750 oe = ht->entries + p * ht->esize;
755 if (ht->count == ht->thold)
757 sht_set_entry(ht, ce, cb, oe, ob);
764 && cb->all == ob->all
765 && ht->eqfn(key, oe, ht->eq_ctx)
771 if (cb->psl > ob->psl) {
776 if (!c_uniq && ht->count == ht->thold)
779 sht_remove_entry(ht, oe, ob, e_tmp[ti], b_tmp + ti);
781 sht_set_entry(ht, ce, cb, oe, ob);
792 assert(cb->psl < ht->psl_limit);
808static bool sht_ht_grow(
struct sht_ht *ht)
810 union sht_bckt *b, *old;
815 if (ht->tsize == SHT_MAX_TSIZE) {
824 if (!sht_alloc_arrays(ht, ht->tsize * 2))
827 for (i = 0; i < ht->tsize / 2; ++i, ++b, e += ht->esize) {
829 result = sht_probe(ht, b->hash,
nullptr, e, 1);
830 assert(result == -1);
860static int sht_insert(
struct sht_ht *ht,
const void *key,
861 const void *entry,
bool replace)
868 sht_abort(
"sht_add/sht_set: Table not initialized");
869 if (ht->iter_lock != 0)
870 sht_abort(
"sht_add/sht_set: Table has iterator(s)");
872 assert(key !=
nullptr && entry !=
nullptr);
874 if (ht->max_psl_ct != 0) {
879 hash = ht->hashfn(key, ht->hash_ctx);
881 result = sht_probe(ht, hash, key, entry, 0);
885 current = ht->entries + result * ht->esize;
886 if (ht->freefn !=
nullptr)
887 ht->freefn(current, ht->free_ctx);
888 memcpy(current, entry, ht->esize);
896 assert(result == -2);
898 if (!sht_ht_grow(ht))
901 result = sht_probe(ht, hash,
nullptr, entry, 1);
902 assert(result == -1);
929int sht_add(
struct sht_ht *ht,
const void *key,
const void *entry)
931 return sht_insert(ht, key, entry, 0);
957int sht_set(
struct sht_ht *ht,
const void *key,
const void *entry)
959 return sht_insert(ht, key, entry, 1);
985const void *
sht_get(
struct sht_ht *ht,
const void *restrict key)
991 sht_abort(
"sht_get: Table not initialized");
993 hash = ht->hashfn(key, ht->hash_ctx);
994 result = sht_probe(ht, hash, key,
nullptr, 0);
997 assert(result == -1);
1001 return ht->entries + result * ht->esize;
1015static void sht_change_at(
struct sht_ht *ht, uint32_t pos,
1016 const void *entry,
void *out)
1020 e = ht->entries + pos * ht->esize;
1022 if (out ==
nullptr) {
1023 if (ht->freefn !=
nullptr)
1024 ht->freefn(e, ht->free_ctx);
1025 memcpy(e, entry, ht->esize);
1027 else if (out == entry) {
1028 uint8_t tmp[ht->esize];
1029 memcpy(tmp, e, ht->esize);
1030 memcpy(e, entry, ht->esize);
1031 memcpy(out, tmp, ht->esize);
1034 memcpy(out, e, ht->esize);
1035 memcpy(e, entry, ht->esize);
1057static bool sht_change(
struct sht_ht *ht,
const void *key,
1058 const void *entry,
void *out)
1064 sht_abort(
"sht_replace/sht_swap: Table not initialized");
1066 hash = ht->hashfn(key, ht->hash_ctx);
1067 pos = sht_probe(ht, hash, key,
nullptr, 0);
1074 sht_change_at(ht, pos, entry, out);
1097bool sht_replace(
struct sht_ht *ht,
const void *key,
const void *entry)
1099 return sht_change(ht, key, entry,
nullptr);
1124bool sht_swap(
struct sht_ht *ht,
const void *key,
const void *entry,
void *out)
1126 return sht_change(ht, key, entry, out);
1138static void sht_shift(
struct sht_ht *ht, uint32_t dest, uint32_t count)
1142 assert(dest + count < ht->tsize);
1145 memmove(ht->entries + dest * ht->esize,
1146 ht->entries + (dest + 1) * ht->esize,
1150 memmove(ht->buckets + dest,
1151 ht->buckets + dest + 1,
1152 count *
sizeof(
union sht_bckt));
1155 for (i = dest; i < dest + count; ++i) {
1157 if (ht->buckets[i].psl == ht->psl_limit) {
1158 assert(ht->max_psl_ct > 0);
1162 ht->buckets[i].psl--;
1166 ht->psl_sum -= count;
1174static void sht_shift_wrap(
struct sht_ht *ht)
1179 memcpy(ht->entries + ht->mask * ht->esize, ht->entries, ht->esize);
1182 ht->buckets[ht->mask] = ht->buckets[0];
1185 if (ht->buckets[ht->mask].psl == ht->psl_limit) {
1186 assert(ht->max_psl_ct > 0);
1190 ht->buckets[ht->mask].psl--;
1203static void sht_remove_at(
struct sht_ht *ht, uint32_t pos,
void *restrict out)
1208 if (out !=
nullptr) {
1209 memcpy(out, ht->entries + pos * ht->esize, ht->esize);
1211 else if (ht->freefn !=
nullptr) {
1212 ht->freefn(ht->entries + pos * ht->esize, ht->free_ctx);
1216 ht->psl_sum -= ht->buckets[pos].psl;
1221 next = (pos + 1) & ht->mask;
1222 while (!ht->buckets[next].empty && ht->buckets[next].psl != 0) {
1224 next = (next + 1) & ht->mask;
1228 if ((uint32_t)pos == end) {
1231 else if ((uint32_t)pos < end) {
1233 sht_shift(ht, pos, end - pos);
1237 if ((uint32_t)pos < ht->mask)
1238 sht_shift(ht, pos, ht->mask - pos);
1244 sht_shift(ht, 0, end);
1248 ht->buckets[end].empty = 1;
1269static bool sht_remove(
struct sht_ht *ht,
const void *restrict key,
1276 sht_abort(
"sht_pop/sht_delete: Table not initialized");
1277 if (ht->iter_lock != 0)
1278 sht_abort(
"sht_pop/sht_delete: Table has iterator(s)");
1281 hash = ht->hashfn(key, ht->hash_ctx);
1282 pos = sht_probe(ht, hash, key,
nullptr, 0);
1288 sht_remove_at(ht, pos, out);
1312bool sht_pop(
struct sht_ht *ht,
const void *restrict key,
void *restrict out)
1314 return sht_remove(ht, key, out);
1337 return sht_remove(ht, key,
nullptr);
1358 if (ht->iter_lock != 0)
1359 sht_abort(
"sht_free: Table has iterator(s)");
1361 if (ht->freefn !=
nullptr) {
1363 for (i = 0, b = ht->buckets; i < ht->tsize; ++i, ++b) {
1366 e = ht->entries + i * ht->esize;
1367 ht->freefn(e, ht->free_ctx);
1388 struct sht_iter *iter;
1392 sht_abort(
"sht_iter_new: Table not initialized");
1396 if (ht->iter_lock == UINT16_MAX) {
1401 if ((lock = ht->iter_lock + 1) > SHT_MAX_ITERS) {
1407 if (ht->iter_lock != 0) {
1415 if ((iter = calloc(1,
sizeof *iter)) ==
nullptr) {
1424 ht->iter_lock = lock;
1470 const struct sht_ht *ht;
1472 if (iter->last == INT32_MAX)
1475 assert(iter->last < (int32_t)SHT_MAX_TSIZE);
1477 if (iter->last == -1)
1480 next = iter->last + 1;
1482 for (ht = iter->ht; next < ht->tsize; ++next) {
1484 if (!ht->buckets[next].empty) {
1486 return ht->entries + next * ht->esize;
1490 iter->last = INT32_MAX;
1510 sht_abort(
"sht_iter_delete: Iterator is read-only");
1512 if (iter->last == -1 || iter->last == INT32_MAX) {
1517 assert(iter->last >= 0 && (uint32_t)iter->last < iter->ht->tsize);
1518 assert(!iter->ht->buckets[iter->last].empty);
1520 sht_remove_at(iter->ht, iter->last,
nullptr);
1546 if (iter->last == -1 || iter->last == INT32_MAX) {
1551 assert(iter->last >= 0 && (uint32_t)iter->last < iter->ht->tsize);
1552 assert(!iter->ht->buckets[iter->last].empty);
1554 sht_change_at(iter->ht, iter->last, entry,
nullptr);
1571 assert(ht->iter_lock > 0 && ht->iter_lock <= SHT_MAX_ITERS);
1575 assert(ht->iter_lock == UINT16_MAX);
const void * sht_get(struct sht_ht *ht, const void *restrict key)
Lookup an entry in a table.
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.
const char * sht_iter_msg(const struct sht_iter *iter)
Get a description of an iterator's last error.
uint8_t sht_peak_psl(const struct sht_ht *ht)
Get the "peak" probe sequence length (PSL) of a table.
const void * sht_iter_next(struct sht_iter *iter)
Get the next entry from an 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.
bool sht_init(struct sht_ht *ht, uint32_t capacity)
Initialize a hash table.
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.
bool sht_iter_replace(struct sht_iter *iter, const void *restrict entry)
Replace the last entry returned by an iterator.
bool sht_empty(const struct sht_ht *ht)
Determine whether a table is empty.
bool sht_swap(struct sht_ht *ht, const void *key, const void *entry, void *out)
Exchange an existing entry and a new entry.
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.
struct sht_iter * sht_iter_new(struct sht_ht *ht, enum sht_iter_type type)
Create a new iterator.
bool sht_iter_delete(struct sht_iter *iter)
Remove the last entry returned by a read/write iterator.
enum sht_err sht_get_err(const struct sht_ht *ht)
Get the error code of a table'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.
const char * sht_get_msg(const struct sht_ht *ht)
Get a description of a table's last error.
void sht_set_free_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's free function.
enum sht_err sht_iter_err(const struct sht_iter *iter)
Get the error code of an iterator's last error.
void sht_iter_free(struct sht_iter *iter)
Free an iterator.
struct sht_ht * sht_new_(sht_hashfn_t hashfn, sht_eqfn_t eqfn, sht_freefn_t freefn, size_t esize, size_t ealign, enum sht_err *err)
Create a new hash table (call via SHT_NEW()).
void sht_free(struct sht_ht *ht)
Free the resources used by a hash table.
@ 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.
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.
sht_iter_type
Iterator types.
@ SHT_ITER_RO
Read-only iterator.
@ SHT_ITER_RW
Read/write iterator.
bool(* sht_eqfn_t)(const void *restrict key, const void *restrict entry, void *restrict context)
Equality comparison function type.