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