SHT Hash Table
SHT Hash Table
Loading...
Searching...
No Matches
sht.c
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
2
3/*
4 *
5 * SHT - hash table with "Robin Hood" probing
6 *
7 * Copyright 2025 Ian Pilcher <arequipeno@gmail.com>
8 *
9 */
10
11
12/**
13 * @file
14 * SHT library implementation.
15 */
16
17
18#include "sht.h"
19
20#include <assert.h>
21#include <limits.h>
22#include <stdbit.h>
23#include <stdckdint.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27
28
29/**
30 * @internal
31 * @brief
32 * Default initial capacity.
33 */
34#define SHT_DEF_CAPCITY 6
35
36/**
37 * @internal
38 * @brief
39 * Default load factor threshold.
40 */
41#define SHT_DEF_LFT 85
42
43/**
44 * @internal
45 * @brief
46 * Default maximum PSL.
47 */
48#define SHT_DEF_PSL_LIMIT UINT8_C(127)
49
50/**
51 * @internal
52 * @brief
53 * Maximum table size (16,777,216).
54 */
55#define SHT_MAX_TSIZE (UINT32_C(1) << 24)
56
57/**
58 * @internal
59 * @brief
60 * Maximum number of read-only iterators on a table.
61 */
62#define SHT_MAX_ITERS UINT16_C(0x7fff)
63
64/**
65 * @private
66 * Hash table bucket structure ("SHT bucket").
67 */
68union sht_bckt {
69 struct {
70 uint32_t hash:24; /**< Hash (low 24 bits). */
71 uint32_t psl:7; /**< Probe sequence length. */
72 uint32_t empty:1; /**< Is this bucket empty? */
73 };
74 uint32_t all; /**< All 32 bits. */
75};
76
77/**
78 * @private
79 * A hash table.
80 */
81struct sht_ht {
82 //
83 // Arrays are allocated when table is initialized or resized.
84 //
85 union sht_bckt *buckets; /**< Array of SHT buckets. */
86 uint8_t *entries; /**< Array of entries. */
87 //
88 // The next 9 members don't change once the table is initialized.
89 //
90 sht_hashfn_t hashfn; /**< Hash function. */
91 void *hash_ctx; /**< Context for hash function. */
92 sht_eqfn_t eqfn; /**< Equality function. */
93 void *eq_ctx; /**< Context for equality function. */
94 sht_freefn_t freefn; /**< Entry resource free function. */
95 void *free_ctx; /**< Context for free function. */
96 uint32_t esize; /**< Size of each entry in the table. */
97 uint32_t ealign; /**< Alignment of table entries. */
98 uint32_t lft; /**< Load factor threshold * 100. */
99 uint8_t psl_limit; /**< Maximum allowed PSL. */
100 //
101 // The next 3 members change whenever the arrays are (re)allocated.
102 //
103 uint32_t tsize; /**< Number of buckets (table size). */
104 uint32_t mask; /**< Hash -> index bitmask. */
105 uint32_t thold; /**< Expansion threshold. */
106 //
107 // These 6 members change as entries are added and removed.
108 //
109 uint32_t count; /**< Number of occupied buckets. */
110 uint32_t psl_sum; /**< Sum of all PSLs. */
111 uint32_t max_psl_ct; /**< Number of entries with max PSL. */
112 enum sht_err err; /**< Last error. */
113 uint8_t peak_psl; /**< Largest PSL in table. */
114 //
115 // Iterator reference count or r/w lock status
116 //
117 uint16_t iter_lock; /**< Iterator lock. */
118};
119
120/**
121 * @private
122 * Hash table iterator.
123 */
124struct sht_iter {
125 struct sht_ht *ht; /**< Table. */
126 int32_t last; /**< Position of last entry returned. */
127 enum sht_err err; /**< Last error. */
128 enum sht_iter_type type; /**< Type of iterator (ro/rw). */
129};
130
131/**
132 * Default critical error printing function.
133 *
134 * @param msg The error message.
135 *
136 * @see sht_abort()
137 */
138static void sht_err_print(const char *msg)
139{
140 fprintf(stderr, "Fatal SHT error: %s\n", msg);
141}
142
143/**
144 * @private
145 */
146void (*sht_abort_print)(const char *msg) = sht_err_print;
147
148/**
149 * Print an error message and abort the program.
150 *
151 * @param msg The error message.
152 */
153[[noreturn]]
154static void sht_abort(const char *msg)
155{
156 sht_abort_print(msg);
157 abort();
158}
159
160/**
161 * Get the error code of a table's last error.
162 *
163 * The value returned by this function is only valid after a previous function
164 * call indicated an error.
165 *
166 * @param ht The hash table.
167 *
168 * @returns A code that describes the error.
169 */
170enum sht_err sht_get_err(const struct sht_ht *ht)
171{
172 return ht->err;
173}
174
175/**
176 * Get the description for an error code.
177 *
178 * **NOTE:** This function will abort the calling program if an invalid value of
179 * @p err is specified.
180 *
181 * @param err The error code. Must in the range `SHT_ERR_OK` to
182 * `SHT_ERR_COUNT - 1`.
183 *
184 * @returns A string describing the error code.
185 */
186const char *sht_msg(enum sht_err err)
187{
188 static const char *err_msgs[] = {
189 [SHT_ERR_OK] = "No error",
190 [SHT_ERR_ALLOC] = "Memory allocation failed",
191 [SHT_ERR_BAD_ESIZE] = "Entry size too large (> 16KiB)",
192 [SHT_ERR_TOOBIG] = "Requested table size too large",
193 [SHT_ERR_BAD_HASH] = "Too many hash collisions",
194 [SHT_ERR_ITER_LOCK] = "Can't acquire iterator lock",
195 [SHT_ERR_ITER_COUNT] = "Table has too many iterators",
196 [SHT_ERR_ITER_NO_LAST] = "Iterator at beginning or end",
197 };
198
199 /* Ensure that we have a message for every code. */
200 static_assert(sizeof err_msgs / sizeof err_msgs[0] == SHT_ERR_COUNT);
201
202 if (err >= SHT_ERR_COUNT)
203 sht_abort("sht_msg: Invalid error code");
204
205 return err_msgs[err];
206}
207
208/**
209 * Get a description of a table's last error.
210 *
211 * The value returned by this function is only valid after a previous function
212 * call indicated an error.
213 *
214 * @param ht The hash table.
215 *
216 * @returns A string describing the error.
217 */
218const char *sht_get_msg(const struct sht_ht *ht)
219{
220 return sht_msg(ht->err);
221}
222
223/**
224 * Check that a `nonnull` function pointer really isn't `NULL`.
225 *
226 * `sht.h` declares sht_new_() with the following function attribute &mdash;
227 * `[[gnu::nonnull(1, 2)]]`. This tells the compiler (GCC and Clang) that its
228 * `hashfn` and `eqfn` arguments should not be `NULL`, and causes them to issue
229 * a warning when they determine that a `NULL` pointer is being used for one of
230 * these arguments.
231 *
232 * Unfortunately, it also allows the compiler to assume that the value of these
233 * arguments will **never** be `NULL`, even though it is not actually enforced.
234 * Because of this assumption, explicit checks for `NULL` values will usually be
235 * optimized away.
236 *
237 * This function endeavors to "force" the compiler to check that a function
238 * pointer value is not `NULL`, even when its been told that it can't be.
239 *
240 * @param p The pointer to be checked.
241 * @param msg Error message if pointer is `NULL`.
242 *
243 * @see sht_abort()
244 */
245#ifdef __clang__
246[[clang::optnone]]
247#else
248[[gnu::optimize("-fno-delete-null-pointer-checks")]]
249#endif
250static void sht_assert_nonnull(void (*p)(void), const char *msg)
251{
252 void (*volatile vp)(void) = p;
253
254 if (vp == nullptr)
255 sht_abort(msg);
256}
257
258/**
259 * Create a new hash table (call via SHT_NEW()).
260 *
261 * > **NOTE**
262 * >
263 * > Do not call this function directly. Use SHT_NEW().
264 *
265 * A table returned by this function cannot be used until it has been
266 * initialized.
267 *
268 * @param hashfn Function to be used to compute the hash values of keys.
269 * @param eqfn Function to be used to compare keys for equality.
270 * @param freefn Function to be used to free entry resources. (May be
271 * `NULL`.)
272 * @param esize The size of the entries to be stored in the table.
273 * @param ealign The alignment of the entries to be stored in the table.
274 * @param[out] err Optional output pointer for error reporting.
275 *
276 * @returns On success, a pointer to the new hash table is returned. On
277 * error, `NULL` is returned, and an error code is returned in
278 * @p err (if it is not `NULL`).
279 *
280 * @see SHT_NEW()
281 *
282 */
283struct sht_ht *sht_new_(sht_hashfn_t hashfn, sht_eqfn_t eqfn,
284 sht_freefn_t freefn, size_t esize,
285 size_t ealign, enum sht_err *err)
286{
287 struct sht_ht *ht;
288
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");
297
298 if (esize > SHT_MAX_ESIZE) {
299 err != nullptr && (*err = SHT_ERR_BAD_ESIZE);
300 return nullptr;
301 }
302
303 if ((ht = calloc(1, sizeof *ht)) == nullptr) {
304 err != nullptr && (*err = SHT_ERR_ALLOC);
305 return nullptr;
306 }
307
308 ht->hashfn = hashfn;
309 ht->eqfn = eqfn;
310 ht->freefn = freefn;
311 ht->esize = esize;
312 ht->ealign = ealign;
313 ht->lft = SHT_DEF_LFT;
314 ht->psl_limit = SHT_DEF_PSL_LIMIT;
315
316 return ht;
317}
318
319/**
320 * Set the "context" for a table's hash function.
321 *
322 * Sets the value of the @p context argument for all calls to the table's hash
323 * function.
324 *
325 * > **NOTE**
326 * >
327 * > This function cannot be called after the table has been initialized. (See
328 * > [Abort conditions](index.html#abort-conditions).)
329 *
330 * @param ht The hash table.
331 * @param context The function-specific context.
332 *
333 * @see sht_hashfn_t
334 * @see [Abort conditions](index.html#abort-conditions)
335 */
336void sht_set_hash_ctx(struct sht_ht *ht, void *context)
337{
338 if (ht->tsize != 0)
339 sht_abort("sht_set_hash_ctx: Table already initialized");
340 ht->hash_ctx = context;
341}
342
343/**
344 * Set the "context" for a table's equality function.
345 *
346 * Sets the value of the @p context argument for all calls to the table's
347 * equality function.
348 *
349 * > **NOTE**
350 * >
351 * > This function cannot be called after the table has been initialized. (See
352 * > [Abort conditions](index.html#abort-conditions).)
353 *
354 * @param ht The hash table.
355 * @param context The function-specific context.
356 *
357 * @see sht_eqfn_t
358 * @see [Abort conditions](index.html#abort-conditions)
359 */
360void sht_set_eq_ctx(struct sht_ht *ht, void *context)
361{
362 if (ht->tsize != 0)
363 sht_abort("sht_set_eq_ctx: Table already initialized");
364 ht->eq_ctx = context;
365}
366
367/**
368 * Set the "context" for a table's free function.
369 *
370 * Sets the value of the @p context argument for all calls to the table's free
371 * function.
372 *
373 * > **NOTE**
374 * >
375 * > This function cannot be called after the table has been initialized. (See
376 * > [Abort conditions](index.html#abort-conditions).)
377 *
378 * @param ht The hash table.
379 * @param context Optional function-specific context.
380 *
381 * @see sht_freefn_t
382 * @see [Abort conditions](index.html#abort-conditions)
383 */
384void sht_set_free_ctx(struct sht_ht *ht, void *context)
385{
386 if (ht->tsize != 0)
387 sht_abort("sht_set_freefn: Table already initialized");
388 ht->free_ctx = context;
389}
390
391/**
392 * Set the load factor threshold for a table.
393 *
394 * The load factor threshold (LFT) determines when a table is expanded, in order
395 * to accomodate additional entries. The size of the table is doubled when the
396 * number of entries it contains exceeds a certain percentage of its size. That
397 * percentage is determined by the LFT. Thus, the LFT must be between 1 and
398 * 100, although values much different from the default (85) are unlikely to be
399 * very useful.
400 *
401 * > **NOTE**
402 * >
403 * > This function cannot be called after the table has been initialized, nor
404 * > can it be called with an invalid @p lft value. (See
405 * > [Abort conditions](index.html#abort-conditions).)
406 *
407 * @param ht The hash table.
408 * @param lft The load factor threshold (`1` - `100`).
409 *
410 * @see [Abort conditions](index.html#abort-conditions)
411 */
412void sht_set_lft(struct sht_ht *ht, uint8_t lft)
413{
414 if (ht->tsize != 0)
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");
418 ht->lft = lft;
419}
420
421/**
422 * Set the PSL limit of a table.
423 *
424 * If an entry in the table has a PSL equal to the table's PSL limit, no
425 * further entries will be allowed until 1 or more entries that hash to the same
426 * "ideal" position are removed. (See [Limits and assumptions][1].)
427 *
428 * > **NOTE**
429 * >
430 * > This function cannot be called after the table has been initialized, nor
431 * > can it be called with an invalid @p limit value. (See
432 * > [Abort conditions](index.html#abort-conditions).)
433 *
434 * @param ht The hash table.
435 * @param limit The PSL limit (`1` - `127`).
436 *
437 * @see [Limits and assumptions][1]
438 * @see [Abort conditions](index.html#abort-conditions)
439 *
440 * [1]: index.html#limits-and-assumptions
441 */
442void sht_set_psl_limit(struct sht_ht *ht, uint8_t limit)
443{
444 if (ht->tsize != 0)
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;
449}
450
451/**
452 * Allocate new arrays for a table.
453 *
454 * Allocates memory for a table's `buckets` and `entries` arrays. If allocation
455 * is successful, `ht->tsize`, `ht->mask`, and `ht->thold` are updated for the
456 * new size, and `ht->count`, `ht->psl_sum`, `ht->peak_psl`, and
457 * `ht->max_psl_ct` are reset to 0. If an error occurs, the state of the table
458 * is unchanged.
459 *
460 * @param ht The hash table.
461 * @param tsize The new size (total number of buckets) of the table.
462 *
463 * @returns On success, true (`1`) is returned. On failure, false (`0`) is
464 * returned, and the table's error status is set. (The state of
465 * the table is otherwise unchanged.)
466 */
467static bool sht_alloc_arrays(struct sht_ht *ht, uint32_t tsize)
468{
469 size_t b_size; // size of bucket array
470 size_t e_size; // size of entry array
471 size_t pad; // padding to align entry array
472 size_t size; // total size
473 uint8_t *new;
474
475 static_assert(SHT_MAX_TSIZE == 1 << 24);
476 static_assert(sizeof(union sht_bckt) == 4);
477 static_assert(alignof(union sht_bckt) == 4);
478
479 assert(tsize <= SHT_MAX_TSIZE);
480
481 b_size = tsize * sizeof(union sht_bckt); /* Max result is 2^26 */
482
483 if (ckd_mul(&e_size, tsize, ht->esize)) {
484 ht->err = SHT_ERR_TOOBIG;
485 return 0;
486 }
487
488 pad = (ht->ealign - b_size % ht->ealign) % ht->ealign;
489
490 if (ckd_add(&size, b_size + pad, e_size)) {
491 ht->err = SHT_ERR_TOOBIG;
492 return 0;
493 }
494
495 if ((new = malloc(size)) == nullptr) {
496 ht->err = SHT_ERR_ALLOC;
497 return 0;
498 }
499
500 // Mark all of the newly allocated buckets as empty.
501 memset(new, 0xff, b_size);
502
503 ht->buckets = (union sht_bckt *)(void *)new;
504 ht->entries = new + b_size + pad;
505 ht->tsize = tsize;
506 ht->mask = tsize - 1; // e.g. 0x8000 - 1 = 0x7fff
507 ht->thold = tsize * ht->lft / 100; // 2^24 * 100 < 2^32
508 ht->count = 0;
509 ht->psl_sum = 0;
510 ht->peak_psl = 0;
511 ht->max_psl_ct = 0;
512
513 return 1;
514}
515
516/**
517 * Initialize a hash table.
518 *
519 * @p capacity, along with the table's load factor threshold, is used to
520 * calculate the minimum initial size of the table. Setting an appropriate
521 * initial size will avoid the need to resize the table as it grows (but will
522 * consume unnecessary memory if fewer keys are stored in the table than
523 * expected).
524 *
525 * If @p capacity is `0`, a default initial capacity (currently `6`) is used.
526 *
527 * > **NOTE**
528 * >
529 * > If this function succeeds, it must not be called again on the same table.
530 * > A failed call may be retried, possibly with a lower @p capacity. (See
531 * > [Abort conditions](index.html#abort-conditions).)
532 *
533 * @param ht The hash table to be initialized.
534 * @param capacity The initial capacity of the hash table (or `0`).
535 *
536 * @returns On success, true (`1`) is returned. On failure, false (`0`) is
537 * returned, and the table's error status is set.
538 *
539 * @see [Abort conditions](index.html#abort-conditions)
540 */
541bool sht_init(struct sht_ht *ht, uint32_t capacity)
542{
543 if (ht->tsize != 0)
544 sht_abort("sht_init: Table already initialized");
545
546 // Initial check avoids overflow below (SHT_MAX_TSIZE = 2^24)
547 if (capacity > SHT_MAX_TSIZE) {
548 ht->err = SHT_ERR_TOOBIG;
549 return 0;
550 }
551
552 if (capacity == 0)
553 capacity = SHT_DEF_CAPCITY;
554
555 // Calculate required size at LFT (max result is less than 2^31)
556 capacity = (capacity * 100 + ht->lft - 1) / ht->lft;
557
558 // Find smallest power of 2 that is >= capacity (max result 2^31)
559 capacity = stdc_bit_ceil(capacity);
560
561 // Check final capacity
562 if (capacity > SHT_MAX_TSIZE) {
563 ht->err = SHT_ERR_TOOBIG;
564 return 0;
565 }
566
567 return sht_alloc_arrays(ht, capacity);
568}
569
570/**
571 * Get the number of entries in a table.
572 *
573 * > **NOTE**
574 * >
575 * > This function cannot be called on an unitialized table. (See
576 * > [Abort conditions](index.html#abort-conditions).)
577 *
578 * @param ht The hash table.
579 *
580 * @returns The number of entries in the table.
581 *
582 * @see [Abort conditions](index.html#abort-conditions)
583 */
584uint32_t sht_size(const struct sht_ht *ht)
585{
586 if (ht->tsize == 0)
587 sht_abort("sht_size: Table not initialized");
588 return ht->count;
589}
590
591/**
592 * Determine whether a table is empty.
593 *
594 * > **NOTE**
595 * >
596 * > This function cannot be called on an unitialized table. (See
597 * > [Abort conditions](index.html#abort-conditions).)
598 *
599 * @param ht The hash table.
600 *
601 * @returns True (`1`) if the table is empty; false (`0`) if it has at least
602 * one entry.
603 *
604 * @see [Abort conditions](index.html#abort-conditions)
605 */
606bool sht_empty(const struct sht_ht *ht)
607{
608 if (ht->tsize == 0)
609 sht_abort("sht_empty: Table not initialized");
610 return ht->count == 0;
611}
612
613/**
614 * Get the "peak" probe sequence length (PSL) of a table.
615 *
616 * The peak PSL is the highest PSL value of any entry in the table since the
617 * time that the table was expanded (or created). It provides an indication of
618 * the performance of the table's hash function.
619 *
620 * At the default load factor threshold (85%), PSL values should generally
621 * remain in the single digits, or the low teens at most, unless the hash
622 * function being used is extremely poor.
623 *
624 * @param ht The hash table.
625 */
626uint8_t sht_peak_psl(const struct sht_ht *ht)
627{
628 return ht->peak_psl;
629}
630
631/**
632 * Low level insert function.
633 *
634 * Copies candidate entry & bucket into table and updates table statistics.
635 *
636 * @param ht The hash table.
637 * @param c_entry The entry to be inserted.
638 * @param c_bckt The bucket to be inserted.
639 * @param o_entry Destination for @p c_entry.
640 * @param o_bckt Destination for @p c_bckt.
641 */
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)
646{
647 *o_bckt = *c_bckt;
648 memcpy(o_entry, c_entry, ht->esize);
649
650 ht->count++;
651 ht->psl_sum += c_bckt->psl;
652
653 if (c_bckt->psl > ht->peak_psl)
654 ht->peak_psl = c_bckt->psl;
655
656 if (c_bckt->psl == ht->psl_limit) {
657 ht->max_psl_ct++;
658 assert(ht->max_psl_ct < ht->thold); // should be much lower
659 }
660}
661
662/**
663 * Low level remove function.
664 *
665 * Copies entry & bucket from table to temporary storage and updates table
666 * statistics.
667 *
668 * @param ht The hash table.
669 * @param o_entry The entry to be removed.
670 * @param o_bckt The bucket to be removed.
671 * @param t_entry Destination for @p o_entry.
672 * @param t_bckt Destination for @p o_bckt.
673 */
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)
678{
679 *t_bckt = *o_bckt;
680 memcpy(t_entry, o_entry, ht->esize);
681
682 ht->count--;
683 ht->psl_sum -= o_bckt->psl;
684
685 if (o_bckt->psl == ht->psl_limit) {
686 assert(ht->max_psl_ct > 0);
687 ht->max_psl_ct--;
688 }
689}
690
691/**
692 * Finds or inserts the specified key or entry in the table.
693 *
694 * Implements the core "Robin Hood" probing algorithm, used for lookup and
695 * insertion, as well as populating newly allocated bucket and entry arrays
696 * during rehashing.
697 *
698 * The mode of operation depends on the values of @p key, @p ce, and @p c_uniq.
699 *
700 * | | **key** | **ce** |**c_uniq**|
701 * |------|:--------:|:--------:|:---------|
702 * |search|non-`NULL`| `NULL` | `false` |
703 * |insert|non-`NULL`|non-`NULL`| `false` |
704 * |rehash| `NULL` |non-`NULL`| `true` |
705 *
706 * @param ht The hash table.
707 * @param hash Hash of @p key.
708 * @param key The key to be found or added.
709 * @param ce "Candidate" entry to insert (if not already present).
710 * @param c_uniq Is @p key known to not be present in the table?
711 *
712 * @returns If @p key is already present in the table, its position (index)
713 * is returned.
714 * @returns `-1` indicates that @p key was not present in the table. In
715 * insert mode, this indicates that it has been successfully added.
716 * @returns `-2` is returned only in insert mode. It indicates that @p key
717 * is not present in the table, but the table must be expanded
718 * before it can be added.
719 * @returns The state of the table is unchanged, except when this function
720 * is called in insert mode and returns `-1`.
721 */
722static int32_t sht_probe(struct sht_ht *ht, uint32_t hash, const void *key,
723 const uint8_t *ce, bool c_uniq)
724{
725 uint8_t e_tmp[2][ht->esize]; // temp storage for displaced entries
726 union sht_bckt b_tmp[2]; // temp storage for displaced buckets
727 bool ti; // index for e_tmp and b_tmp
728 union sht_bckt *cb; // candidate bucket
729 union sht_bckt *ob; // current position occupant bucket
730 uint8_t *oe; // current position occupant entry
731 uint32_t p; // current position (index)
732
733 assert( (key != nullptr && ce == nullptr && c_uniq == 0) /* search */
734 || (key != nullptr && ce != nullptr && c_uniq == 0) /* insert */
735 || (key == nullptr && ce != nullptr && c_uniq == 1) /* rehash */
736 );
737
738 cb = b_tmp; // Initial candidate bucket goes in b_tmp[0]
739 cb->hash = hash;
740 cb->psl = 0;
741 cb->empty = 0;
742 ti = 1; // so 1st displacement doesn't overwrite the candidate bucket
743
744 p = hash; // masked to table size in loop
745
746 while (1) {
747
748 p &= ht->mask;
749 ob = ht->buckets + p;
750 oe = ht->entries + p * ht->esize;
751
752 // Empty position?
753 if (ob->empty) {
754 if (ce != nullptr) {
755 if (ht->count == ht->thold) // rehash needed?
756 return -2;
757 sht_set_entry(ht, ce, cb, oe, ob);
758 }
759 return -1;
760 }
761
762 // Found key?
763 if (!c_uniq
764 && cb->all == ob->all
765 && ht->eqfn(key, oe, ht->eq_ctx)
766 ) {
767 return p;
768 }
769
770 // Found later bucket group?
771 if (cb->psl > ob->psl) {
772 // If we're just searching, we're done
773 if (ce == nullptr)
774 return -1;
775 // Only need this check before 1st displacement
776 if (!c_uniq && ht->count == ht->thold)
777 return -2;
778 // Move occupant to temp slot & adjust table stats
779 sht_remove_entry(ht, oe, ob, e_tmp[ti], b_tmp + ti);
780 // Move candidate to current pos'n & adjust table stats
781 sht_set_entry(ht, ce, cb, oe, ob);
782 // Old occupant (in temp slot) is new candidate
783 cb = b_tmp + ti;
784 ce = e_tmp[ti];
785 // Use other temp slot next time (don't overwrite)
786 ti ^= 1;
787 // New candidate was in table; must be unique
788 c_uniq = 1;
789 }
790
791 // https://github.com/ipilcher/sht/blob/main/docs/psl-limits.md
792 assert(cb->psl < ht->psl_limit);
793 cb->psl++;
794 p++;
795 }
796}
797
798
799/**
800 * Doubles the size of the table.
801 *
802 * @param ht The hash table.
803 *
804 * @returns On success, true (`1`) is returned. On failure, false (`0`) is
805 * returned, and the table's error status is set. (The state of
806 * the table is otherwise unchanged.)
807 */
808static bool sht_ht_grow(struct sht_ht *ht)
809{
810 union sht_bckt *b, *old;
811 uint8_t *e;
812 int result;
813 uint32_t i;
814
815 if (ht->tsize == SHT_MAX_TSIZE) {
816 ht->err = SHT_ERR_TOOBIG;
817 return 0;
818 }
819
820 old = ht->buckets; // save to free
821 b = ht->buckets;
822 e = ht->entries;
823
824 if (!sht_alloc_arrays(ht, ht->tsize * 2))
825 return 0;
826
827 for (i = 0; i < ht->tsize / 2; ++i, ++b, e += ht->esize) {
828 if (!b->empty) {
829 result = sht_probe(ht, b->hash, nullptr, e, 1);
830 assert(result == -1);
831 }
832 }
833
834 free(old);
835
836 return 1;
837}
838
839/**
840 * Add an entry to a table.
841 *
842 * If @p key is already present in the table, the behavior depends on the value
843 * of @p replace. If it is true (`1`), the existing entry will be replaced by
844 * @p entry; if it is false (`0`), the existing entry will be left in place.
845 *
846 * @param ht The hash table.
847 * @param key The key of the new entry.
848 * @param entry The new entry.
849 * @param replace How to handle duplicate key.
850 *
851 * @returns If an error occurs, `-1` is returned, the error status of the
852 * table is set, and the state of the table is otherwise unchanged.
853 * On success, `0` is returned if the key was not already present
854 * in the table, and `1` is returned if the key was already
855 * present.
856 *
857 * @see sht_add()
858 * @see sht_set()
859 */
860static int sht_insert(struct sht_ht *ht, const void *key,
861 const void *entry, bool replace)
862{
863 uint32_t hash;
864 int32_t result;
865 uint8_t *current;
866
867 if (ht->tsize == 0)
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)");
871
872 assert(key != nullptr && entry != nullptr);
873
874 if (ht->max_psl_ct != 0) {
875 ht->err = SHT_ERR_BAD_HASH;
876 return -1;
877 }
878
879 hash = ht->hashfn(key, ht->hash_ctx);
880
881 result = sht_probe(ht, hash, key, entry, 0);
882
883 if (result >= 0) {
884 if (replace) {
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);
889 }
890 return 1;
891 }
892
893 if (result == -1)
894 return 0;
895
896 assert(result == -2);
897
898 if (!sht_ht_grow(ht))
899 return -1;
900
901 result = sht_probe(ht, hash, nullptr, entry, 1);
902 assert(result == -1);
903 return 0;
904}
905
906/**
907 * Add an entry to the table, if its key is not already present.
908 *
909 * > **NOTE**
910 * >
911 * > This function cannot be called on an unitialized table or a table that has
912 * > one or more iterators. (See
913 * > [Abort conditions](index.html#abort-conditions).)
914 *
915 * @param ht The hash table.
916 * @param key The key of the new entry.
917 * @param entry The new entry.
918 *
919 * @returns If an error occurs, `-1` is returned, the error status of the
920 * table is set, and the state of the table is otherwise unchanged.
921 * On success, `0` is returned if the key was not already present
922 * in the table, and the new entry has been added; `1` indicates
923 * that the key was already present in the table, and the state of
924 * the table is unchanged.
925 *
926 * @see sht_set()
927 * @see [Abort conditions](index.html#abort-conditions)
928 */
929int sht_add(struct sht_ht *ht, const void *key, const void *entry)
930{
931 return sht_insert(ht, key, entry, 0);
932}
933
934/**
935 * Unconditionally set the value associated with a key.
936 *
937 * > **NOTE**
938 * >
939 * > This function cannot be called on an unitialized table or a table that has
940 * > one or more iterators. (See
941 * > [Abort conditions](index.html#abort-conditions).)
942 *
943 * @param ht The hash table.
944 * @param key The key of the new entry.
945 * @param entry The new entry.
946 *
947 * @returns If an error occurs, `-1` is returned, the error status of the
948 * table is set, and the state of the table is otherwise unchanged.
949 * On success, `0` is returned if the key was not already present
950 * in the table, and the new entry has been added; `1` indicates
951 * that the key was already present in the table, and the new entry
952 * has replaced it.
953 *
954 * @see sht_add()
955 * @see [Abort conditions](index.html#abort-conditions)
956 */
957int sht_set(struct sht_ht *ht, const void *key, const void *entry)
958{
959 return sht_insert(ht, key, entry, 1);
960}
961
962/**
963 * Lookup an entry in a table.
964 *
965 * > **WARNING**
966 * >
967 * > The pointer returned by this function is only valid until the next time the
968 * > table is changed. Structural changes to the table (adding or removing
969 * > keys) can cause other entries to be moved within the table, making pointers
970 * > to those entries invalid.
971 *
972 * > **NOTE**
973 * >
974 * > This function cannot be called on an unitialized table. (See
975 * > [Abort conditions](index.html#abort-conditions).)
976 *
977 * @param ht The hash table.
978 * @param key The key for which the entry is to be retrieved.
979 *
980 * @returns If the the key is present in the table, a pointer to the key's
981 * entry is returned. Otherwise, `NULL` is returned.
982 *
983 * @see [Abort conditions](index.html#abort-conditions)
984 */
985const void *sht_get(struct sht_ht *ht, const void *restrict key)
986{
987 uint32_t hash;
988 int32_t result;
989
990 if (ht->tsize == 0)
991 sht_abort("sht_get: Table not initialized");
992
993 hash = ht->hashfn(key, ht->hash_ctx);
994 result = sht_probe(ht, hash, key, nullptr, 0);
995
996 if (result < 0) {
997 assert(result == -1);
998 return nullptr;
999 }
1000
1001 return ht->entries + result * ht->esize;
1002}
1003
1004/**
1005 * Change the entry at a known position.
1006 *
1007 * @param ht The hash table.
1008 * @param pos The position of the entry to be replaced.
1009 * @param entry The new entry.
1010 * @param[out] out Output buffer for previous entry (or `NULL`). @p out
1011 * may point to the same object as @p entry.
1012 *
1013 * @see sht_change()
1014 */
1015static void sht_change_at(struct sht_ht *ht, uint32_t pos,
1016 const void *entry, void *out)
1017{
1018 uint8_t *e;
1019
1020 e = ht->entries + pos * ht->esize;
1021
1022 if (out == nullptr) {
1023 if (ht->freefn != nullptr)
1024 ht->freefn(e, ht->free_ctx);
1025 memcpy(e, entry, ht->esize);
1026 }
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);
1032 }
1033 else {
1034 memcpy(out, e, ht->esize);
1035 memcpy(e, entry, ht->esize);
1036 }
1037}
1038
1039/**
1040 * Change the entry associated with an existing key.
1041 *
1042 * @param ht The hash table.
1043 * @param key The key for which the value is to be changed.
1044 * @param entry The new entry.
1045 * @param[out] out Output buffer for previous entry (or `NULL`). @p out
1046 * may point to the same object as @p entry.
1047 *
1048 * @returns If the key was present in the table, true (`1`) is returned, the
1049 * entry associated with the key is replaced with the new entry,
1050 * and the previous entry is copied to @p out (if it is not
1051 * `NULL`). Otherwise, false (`0`) is returned (and the contents
1052 * of @p out are unchanged).
1053 *
1054 * @see sht_replace()
1055 * @see sht_swap()
1056 */
1057static bool sht_change(struct sht_ht *ht, const void *key,
1058 const void *entry, void *out)
1059{
1060 uint32_t hash;
1061 int32_t pos;
1062
1063 if (ht->tsize == 0)
1064 sht_abort("sht_replace/sht_swap: Table not initialized");
1065
1066 hash = ht->hashfn(key, ht->hash_ctx);
1067 pos = sht_probe(ht, hash, key, nullptr, 0);
1068
1069 if (pos < 0) {
1070 assert(pos == -1);
1071 return 0;
1072 }
1073
1074 sht_change_at(ht, pos, entry, out);
1075
1076 return 1;
1077}
1078
1079/**
1080 * Replace the entry associated with an existing key.
1081 *
1082 * > **NOTE**
1083 * >
1084 * > This function cannot be called on an unitialized table. (See
1085 * > [Abort conditions](index.html#abort-conditions).)
1086 *
1087 * @param ht The hash table.
1088 * @param key The key for which the value is to be replaced.
1089 * @param entry The new entry for the key.
1090 *
1091 * @returns If the key was present in the table, true (`1`) is returned, and
1092 * the entry associated with the key is replaced with the new
1093 * entry. Otherwise, false (`0`) is returned.
1094 *
1095 * @see [Abort conditions](index.html#abort-conditions)
1096 */
1097bool sht_replace(struct sht_ht *ht, const void *key, const void *entry)
1098{
1099 return sht_change(ht, key, entry, nullptr);
1100}
1101
1102/**
1103 * Exchange an existing entry and a new entry.
1104 *
1105 * > **NOTE**
1106 * >
1107 * > This function cannot be called on an unitialized table. (See
1108 * > [Abort conditions](index.html#abort-conditions).)
1109 *
1110 * @param ht The hash table.
1111 * @param key The key for which the value is to be replaced.
1112 * @param entry The new entry for the key.
1113 * @param out Output buffer for the previous entry. Must be large
1114 * enough to hold an entry. (@p out may point to the same
1115 * object as @p entry.)
1116 *
1117 * @returns If the key was present in the table, true (`1`) is returned, the
1118 * entry associated with the key is replaced with the new entry,
1119 * and the previous entry is copied to @p out. Otherwise, false
1120 * (`0`) is returned, and the contents of @p out are unchanged.
1121 *
1122 * @see [Abort conditions](index.html#abort-conditions)
1123 */
1124bool sht_swap(struct sht_ht *ht, const void *key, const void *entry, void *out)
1125{
1126 return sht_change(ht, key, entry, out);
1127}
1128
1129/**
1130 * Shift a block of entries (and buckets) down by 1 position.
1131 *
1132 * This function does **not** handle wrap-around.
1133 *
1134 * @param ht The hash table.
1135 * @param dest Position to which the block should be moved.
1136 * @param count The number of entries and buckets to be moved.
1137 */
1138static void sht_shift(struct sht_ht *ht, uint32_t dest, uint32_t count)
1139{
1140 uint32_t i;
1141
1142 assert(dest + count < ht->tsize);
1143
1144 // Move entries
1145 memmove(ht->entries + dest * ht->esize,
1146 ht->entries + (dest + 1) * ht->esize,
1147 count * ht->esize);
1148
1149 // Move buckets
1150 memmove(ht->buckets + dest,
1151 ht->buckets + dest + 1,
1152 count * sizeof(union sht_bckt));
1153
1154 // Every shifted entry is now 1 position closer to its ideal position
1155 for (i = dest; i < dest + count; ++i) {
1156
1157 if (ht->buckets[i].psl == ht->psl_limit) {
1158 assert(ht->max_psl_ct > 0);
1159 ht->max_psl_ct--;
1160 }
1161
1162 ht->buckets[i].psl--;
1163 }
1164
1165 // PSL total decreased by 1x number of moved entries
1166 ht->psl_sum -= count;
1167}
1168
1169/**
1170 * Shift the entry at position 0 "down" to the last position in the table.
1171 *
1172 * @param ht The hash table.
1173 */
1174static void sht_shift_wrap(struct sht_ht *ht)
1175{
1176 // ht->mask is also index of last position
1177
1178 // Move entry
1179 memcpy(ht->entries + ht->mask * ht->esize, ht->entries, ht->esize);
1180
1181 // Move bucket
1182 ht->buckets[ht->mask] = ht->buckets[0];
1183
1184 // Entry is now 1 position closer to its ideal position
1185 if (ht->buckets[ht->mask].psl == ht->psl_limit) {
1186 assert(ht->max_psl_ct > 0);
1187 ht->max_psl_ct--;
1188 }
1189
1190 ht->buckets[ht->mask].psl--;
1191 ht->psl_sum--;
1192}
1193
1194/**
1195 * Remove and possibly return an entry at a known position.
1196 *
1197 * @param ht The hash table.
1198 * @param pos The position of the entry to be removed.
1199 * @param[out] out Entry output buffer (or `NULL`).
1200 *
1201 * @see sht_remove()
1202 */
1203static void sht_remove_at(struct sht_ht *ht, uint32_t pos, void *restrict out)
1204{
1205 uint32_t end, next;
1206
1207 // Copy entry to output buffer or free its resources
1208 if (out != nullptr) {
1209 memcpy(out, ht->entries + pos * ht->esize, ht->esize);
1210 }
1211 else if (ht->freefn != nullptr) {
1212 ht->freefn(ht->entries + pos * ht->esize, ht->free_ctx);
1213 }
1214
1215 // Update table stats for removal
1216 ht->psl_sum -= ht->buckets[pos].psl;
1217 ht->count--;
1218
1219 // Find range to shift (if any)
1220 end = pos;
1221 next = (pos + 1) & ht->mask;
1222 while (!ht->buckets[next].empty && ht->buckets[next].psl != 0) {
1223 end = next;
1224 next = (next + 1) & ht->mask;
1225 }
1226
1227 // Do any necessary shifts
1228 if ((uint32_t)pos == end) {
1229 // no shifts needed
1230 }
1231 else if ((uint32_t)pos < end) {
1232 // no wrap-around
1233 sht_shift(ht, pos, end - pos);
1234 }
1235 else {
1236 // Shift entries up to end of table (if any)
1237 if ((uint32_t)pos < ht->mask) // mask is also max index
1238 sht_shift(ht, pos, ht->mask - pos);
1239
1240 // Shift entry from position 0 "down" to the end of table
1241 sht_shift_wrap(ht);
1242
1243 // Shift entries at the beginning of the table
1244 sht_shift(ht, 0, end);
1245 }
1246
1247 // Mark position at end of range as empty
1248 ht->buckets[end].empty = 1;
1249}
1250
1251/**
1252 * Remove and possibly return an entry from the table.
1253 *
1254 * (If @p out is `NULL`, the entry is simply removed from the table.)
1255 *
1256 * @param ht The hash table.
1257 * @param key The key for which the entry is to be retrieved and
1258 * removed.
1259 * @param[out] out Entry output buffer (or `NULL`).
1260 *
1261 * @returns If the key was present in the table, true (`1`) is returned, and
1262 * its entry is stored in @p out (if it is not `NULL`). Otherwise,
1263 * false (`0`) is returned (and the contents of @p out are
1264 * unchanged).
1265 *
1266 * @see sht_pop()
1267 * @see sht_delete()
1268 */
1269static bool sht_remove(struct sht_ht *ht, const void *restrict key,
1270 void *restrict out)
1271{
1272 uint32_t hash;
1273 int32_t pos;
1274
1275 if (ht->tsize == 0)
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)");
1279
1280 // Find the entry
1281 hash = ht->hashfn(key, ht->hash_ctx);
1282 pos = sht_probe(ht, hash, key, nullptr, 0);
1283 if (pos < 0) {
1284 assert(pos == -1);
1285 return 0;
1286 }
1287
1288 sht_remove_at(ht, pos, out);
1289 return 1;
1290}
1291
1292/**
1293 * Remove and return an entry from the table.
1294 *
1295 * > **NOTE**
1296 * >
1297 * > This function cannot be called on an unitialized table or a table that has
1298 * > one or more iterators. (See
1299 * > [Abort conditions](index.html#abort-conditions).)
1300 *
1301 * @param ht The hash table.
1302 * @param key The key for which the entry is to be "popped."
1303 * @param[out] out Entry output buffer. Must be large enough to hold an
1304 * entry.
1305 * @returns If the key was present in the table, true (`1`) is returned, and
1306 * its entry is stored in @p out. Otherwise, false (`0`) is
1307 * returned (and the contents of @p out are unchanged).
1308 *
1309 * @see sht_delete()
1310 * @see [Abort conditions](index.html#abort-conditions)
1311 */
1312bool sht_pop(struct sht_ht *ht, const void *restrict key, void *restrict out)
1313{
1314 return sht_remove(ht, key, out);
1315}
1316
1317/**
1318 * Remove an entry from the table.
1319 *
1320 * > **NOTE**
1321 * >
1322 * > This function cannot be called on an unitialized table or a table that has
1323 * > one or more iterators. (See
1324 * > [Abort conditions](index.html#abort-conditions).)
1325 *
1326 * @param ht The hash table.
1327 * @param key The key for which the entry is to be removed.
1328 *
1329 * @returns If the key was present in the table, true (`1`) is returned.
1330 * Otherwise, false (`0`) is returned.
1331 *
1332 * @see sht_pop()
1333 * @see [Abort conditions](index.html#abort-conditions)
1334 */
1335bool sht_delete(struct sht_ht *ht, const void *restrict key)
1336{
1337 return sht_remove(ht, key, nullptr);
1338}
1339
1340/**
1341 * Free the resources used by a hash table.
1342 *
1343 * > **NOTE**
1344 * >
1345 * > This function cannot be called on a table that has one or more iterators.
1346 * > (See [Abort conditions](index.html#abort-conditions).)
1347 *
1348 * @param ht The hash table.
1349 *
1350 * @see [Abort conditions](index.html#abort-conditions)
1351 */
1352void sht_free(struct sht_ht *ht)
1353{
1354 uint32_t i;
1355 uint8_t *e;
1356 union sht_bckt *b;
1357
1358 if (ht->iter_lock != 0)
1359 sht_abort("sht_free: Table has iterator(s)");
1360
1361 if (ht->freefn != nullptr) {
1362
1363 for (i = 0, b = ht->buckets; i < ht->tsize; ++i, ++b) {
1364
1365 if (!b->empty) {
1366 e = ht->entries + i * ht->esize;
1367 ht->freefn(e, ht->free_ctx);
1368 }
1369 }
1370 }
1371
1372 free(ht->buckets);
1373 free(ht);
1374}
1375
1376/**
1377 * Create a new iterator.
1378 *
1379 * @param ht The hash table.
1380 * @param type The type of the iterator (read-only or read/write).
1381 *
1382 * @returns On success, a pointer to the new iterator is returned. If
1383 * memory allocation fails, `NULL` is returned, and the error
1384 * status of the table is set.
1385 */
1386struct sht_iter *sht_iter_new(struct sht_ht *ht, enum sht_iter_type type)
1387{
1388 struct sht_iter *iter;
1389 uint16_t lock;
1390
1391 if (ht->tsize == 0)
1392 sht_abort("sht_iter_new: Table not initialized");
1393
1394 if (type == SHT_ITER_RO) {
1395
1396 if (ht->iter_lock == UINT16_MAX) {
1397 ht->err = SHT_ERR_ITER_LOCK;
1398 return nullptr;
1399 }
1400
1401 if ((lock = ht->iter_lock + 1) > SHT_MAX_ITERS) {
1402 ht->err = SHT_ERR_ITER_COUNT;
1403 return nullptr;
1404 }
1405 }
1406 else { // SHT_ITER_RW
1407 if (ht->iter_lock != 0) {
1408 ht->err = SHT_ERR_ITER_LOCK;
1409 return nullptr;
1410 }
1411
1412 lock = UINT16_MAX;
1413 }
1414
1415 if ((iter = calloc(1, sizeof *iter)) == nullptr) {
1416 ht->err = SHT_ERR_ALLOC;
1417 return nullptr;
1418 }
1419
1420 iter->ht = ht;
1421 iter->last = -1;
1422 iter->type = type;
1423
1424 ht->iter_lock = lock;
1425
1426 return iter;
1427}
1428
1429/**
1430 * Get the error code of an iterator's last error.
1431 *
1432 * The value returned by this function is only valid after a previous iterator
1433 * function call indicated an error.
1434 *
1435 * @param iter The iterator
1436 *
1437 * @returns A code that describes the error.
1438 */
1439enum sht_err sht_iter_err(const struct sht_iter *iter)
1440{
1441 return iter->err;
1442}
1443
1444/**
1445 * Get a description of an iterator's last error.
1446 *
1447 * The value returned by this function is only valid after a previous iterator
1448 * function call indicated an error.
1449 *
1450 * @param iter The iterator.
1451 *
1452 * @returns A string that describes the error.
1453 */
1454const char *sht_iter_msg(const struct sht_iter *iter)
1455{
1456 return sht_msg(iter->err);
1457}
1458
1459/**
1460 * Get the next entry from an iterator.
1461 *
1462 * @param iter The iterator.
1463 *
1464 * @returns A pointer to the next entry, if any. If no more entries are
1465 * available, `NULL` is returned.
1466 */
1467const void *sht_iter_next(struct sht_iter *iter)
1468{
1469 uint32_t next;
1470 const struct sht_ht *ht;
1471
1472 if (iter->last == INT32_MAX)
1473 return nullptr;
1474
1475 assert(iter->last < (int32_t)SHT_MAX_TSIZE); // 2^24
1476
1477 if (iter->last == -1)
1478 next = 0;
1479 else
1480 next = iter->last + 1;
1481
1482 for (ht = iter->ht; next < ht->tsize; ++next) {
1483
1484 if (!ht->buckets[next].empty) {
1485 iter->last = next;
1486 return ht->entries + next * ht->esize;
1487 }
1488 }
1489
1490 iter->last = INT32_MAX;
1491 return nullptr;
1492}
1493
1494/**
1495 * Remove the last entry returned by a **read/write** iterator.
1496 *
1497 * > **NOTE**
1498 * >
1499 * > This function cannot be called on a read-only iterator. (See
1500 * > [Abort conditions](index.html#abort-conditions).)
1501 *
1502 * @param iter The iterator.
1503 *
1504 * @returns On success, true (`1`) is returned. On error, false (`0`) is
1505 * returned and the error status of the iterator is set.
1506 */
1507bool sht_iter_delete(struct sht_iter *iter)
1508{
1509 if (iter->type != SHT_ITER_RW)
1510 sht_abort("sht_iter_delete: Iterator is read-only");
1511
1512 if (iter->last == -1 || iter->last == INT32_MAX) {
1513 iter->err = SHT_ERR_ITER_NO_LAST;
1514 return 0;
1515 }
1516
1517 assert(iter->last >= 0 && (uint32_t)iter->last < iter->ht->tsize);
1518 assert(!iter->ht->buckets[iter->last].empty);
1519
1520 sht_remove_at(iter->ht, iter->last, nullptr);
1521
1522 // If an entry has been shifted down, return it on next call to
1523 // SHT_ITER_NEXT
1524 iter->last--;
1525
1526 return 1;
1527}
1528
1529/**
1530 * Replace the last entry returned by an iterator.
1531 *
1532 * > **WARNING**
1533 * >
1534 * > The new entry **must** have the same key as the entry being replaced.
1535 * > Replacing an entry with an entry that contains a different key will corrupt
1536 * > the table.
1537 *
1538 * @param iter The iterator.
1539 * @param entry The new entry.
1540 *
1541 * @returns On success, true (`1`) is returned. On error, false (`0`) is
1542 * returned and the error status of the iterator is set.
1543 */
1544bool sht_iter_replace(struct sht_iter *iter, const void *restrict entry)
1545{
1546 if (iter->last == -1 || iter->last == INT32_MAX) {
1547 iter->err = SHT_ERR_ITER_NO_LAST;
1548 return 0;
1549 }
1550
1551 assert(iter->last >= 0 && (uint32_t)iter->last < iter->ht->tsize);
1552 assert(!iter->ht->buckets[iter->last].empty);
1553
1554 sht_change_at(iter->ht, iter->last, entry, nullptr);
1555
1556 return 1;
1557}
1558
1559/**
1560 * Free an iterator.
1561 *
1562 * @param iter The iterator.
1563 */
1564void sht_iter_free(struct sht_iter *iter)
1565{
1566 struct sht_ht *ht;
1567
1568 ht = iter->ht;
1569
1570 if (iter->type == SHT_ITER_RO) {
1571 assert(ht->iter_lock > 0 && ht->iter_lock <= SHT_MAX_ITERS);
1572 ht->iter_lock--;
1573 }
1574 else {
1575 assert(ht->iter_lock == UINT16_MAX);
1576 ht->iter_lock = 0;
1577 }
1578
1579 free(iter);
1580}
const void * sht_get(struct sht_ht *ht, const void *restrict key)
Lookup an entry in a table.
Definition sht.c:985
bool sht_delete(struct sht_ht *ht, const void *restrict key)
Remove an entry from the table.
Definition sht.c:1335
bool sht_replace(struct sht_ht *ht, const void *key, const void *entry)
Replace the entry associated with an existing key.
Definition sht.c:1097
const char * sht_iter_msg(const struct sht_iter *iter)
Get a description of an iterator's last error.
Definition sht.c:1454
uint8_t sht_peak_psl(const struct sht_ht *ht)
Get the "peak" probe sequence length (PSL) of a table.
Definition sht.c:626
const void * sht_iter_next(struct sht_iter *iter)
Get the next entry from an iterator.
Definition sht.c:1467
int sht_set(struct sht_ht *ht, const void *key, const void *entry)
Unconditionally set the value associated with a key.
Definition sht.c:957
void sht_set_hash_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's hash function.
Definition sht.c:336
bool sht_init(struct sht_ht *ht, uint32_t capacity)
Initialize a hash table.
Definition sht.c:541
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.
Definition sht.c:929
bool sht_iter_replace(struct sht_iter *iter, const void *restrict entry)
Replace the last entry returned by an iterator.
Definition sht.c:1544
bool sht_empty(const struct sht_ht *ht)
Determine whether a table is empty.
Definition sht.c:606
bool sht_swap(struct sht_ht *ht, const void *key, const void *entry, void *out)
Exchange an existing entry and a new entry.
Definition sht.c:1124
bool sht_pop(struct sht_ht *ht, const void *restrict key, void *restrict out)
Remove and return an entry from the table.
Definition sht.c:1312
uint32_t sht_size(const struct sht_ht *ht)
Get the number of entries in a table.
Definition sht.c:584
struct sht_iter * sht_iter_new(struct sht_ht *ht, enum sht_iter_type type)
Create a new iterator.
Definition sht.c:1386
bool sht_iter_delete(struct sht_iter *iter)
Remove the last entry returned by a read/write iterator.
Definition sht.c:1507
enum sht_err sht_get_err(const struct sht_ht *ht)
Get the error code of a table's last error.
Definition sht.c:170
void sht_set_lft(struct sht_ht *ht, uint8_t lft)
Set the load factor threshold for a table.
Definition sht.c:412
void sht_set_eq_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's equality function.
Definition sht.c:360
void sht_set_psl_limit(struct sht_ht *ht, uint8_t limit)
Set the PSL limit of a table.
Definition sht.c:442
const char * sht_msg(enum sht_err err)
Get the description for an error code.
Definition sht.c:186
const char * sht_get_msg(const struct sht_ht *ht)
Get a description of a table's last error.
Definition sht.c:218
void sht_set_free_ctx(struct sht_ht *ht, void *context)
Set the "context" for a table's free function.
Definition sht.c:384
enum sht_err sht_iter_err(const struct sht_iter *iter)
Get the error code of an iterator's last error.
Definition sht.c:1439
void sht_iter_free(struct sht_iter *iter)
Free an iterator.
Definition sht.c:1564
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()).
Definition sht.c:283
void sht_free(struct sht_ht *ht)
Free the resources used by a hash table.
Definition sht.c:1352
SHT library API.
sht_err
Error codes.
Definition sht.h:195
@ SHT_ERR_TOOBIG
Requested table size too large.
Definition sht.h:199
@ SHT_ERR_ITER_COUNT
Table has too many iterators.
Definition sht.h:202
@ SHT_ERR_BAD_ESIZE
Entry size too large (> 16KiB).
Definition sht.h:198
@ SHT_ERR_ALLOC
Memory allocation failed.
Definition sht.h:197
@ SHT_ERR_BAD_HASH
Too many hash collisions.
Definition sht.h:200
@ SHT_ERR_ITER_NO_LAST
Iterator at beginning or end.
Definition sht.h:203
@ SHT_ERR_COUNT
(Not an error; used for bounds checks.)
Definition sht.h:207
@ SHT_ERR_ITER_LOCK
Can't acquire iterator lock.
Definition sht.h:201
@ SHT_ERR_OK
No error.
Definition sht.h:196
void(* sht_freefn_t)(const void *restrict entry, void *restrict context)
Free function type.
Definition sht.h:162
void(* sht_abort_print)(const char *msg)
Critical error printing function.
Definition sht.c:146
uint32_t(* sht_hashfn_t)(const void *restrict key, void *restrict context)
Hash function type.
Definition sht.h:103
sht_iter_type
Iterator types.
Definition sht.h:182
@ SHT_ITER_RO
Read-only iterator.
Definition sht.h:183
@ SHT_ITER_RW
Read/write iterator.
Definition sht.h:184
bool(* sht_eqfn_t)(const void *restrict key, const void *restrict entry, void *restrict context)
Equality comparison function type.
Definition sht.h:137