#if defined(GUF_DICT_IMPL_STATIC) #define GUF_DICT_KWRDS static #else #define GUF_DICT_KWRDS #endif #ifndef GUF_DICT_H #define GUF_DICT_H #include "guf_common.h" #include "guf_alloc.h" #include "guf_hash.h" #endif #ifndef GUF_DICT_KEY_T #error "Undefined container template GUF_DICT_KEY_T" #endif #ifndef GUF_DICT_KEY_HASH #error "Undefined container template GUF_DICT_KEY_HASH" #endif #if !defined(GUF_DICT_KEY_T_EQ) && !defined(GUF_DICT_KEY_T_IS_INTEGRAL_TYPE) #error "Undefined container template GUF_DICT_KEY_T_EQ" #endif #ifndef GUF_DICT_VAL_T #define GUF_DICT_IS_SET #endif #if defined(GUF_DICT_32_BIT_HASH) #define GUF_DICT_HASH_T uint32_t #define GUF_DICT_HASH_T_MAX UINT32_MAX #elif defined(GUF_DICT_64_BIT_HASH) #define GUF_DICT_HASH_T uint64_t #define GUF_DICT_HASH_T_MAX UINT64_MAX #else #define GUF_DICT_HASH_T guf_hash_size_t #define GUF_DICT_HASH_T_MAX GUF_HASH_MAX #endif #if defined (GUF_DICT_64_BIT_IDX) #define GUF_DICT_KV_META_T uint64_t /* Store a 16-bit hash-fragment in the upper 16-bits of kv_meta -> (2^48 - 1 is IDX_NULL, 2^48 - 2 is IDX_TOMBSTONE, 2^48 - 3 is the largest actual idx, i.e. the max amount of actual kv_elems the dict could hold is 2^48 - 2 = 281,474,976,710,654). */ #define GUF_DICT_KV_META_HASHFRAG_MASK UINT64_C(0xffff000000000000) #define GUF_DICT_KV_META_IDX_MASK (~UINT64_C(0xffff000000000000)) #if GUF_DICT_HASH_T_MAX == UINT64_MAX #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK ) #elif GUF_DICT_HASH_T_MAX == UINT32_MAX #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (((uint64_t)(HASH)) << 32) & GUF_DICT_KV_META_HASHFRAG_MASK ) #else #error "guf_dict: invalid hash size (should not happen)" #endif #else #define GUF_DICT_KV_META_T uint32_t /* Store a 7-bit hash-fragment in the upper 7-bits of kv_meta -> (2^25 - 1 is IDX_NULL, 2^25 - 2 is IDX_TOMBSTONE, 2^25 - 3 is the largest actual idx, i.e. the max amount of actual kv_elems the dict could hold is 2^25 - 2 = 33,554,430). */ #define GUF_DICT_KV_META_HASHFRAG_MASK UINT32_C(0xfe000000) #define GUF_DICT_KV_META_IDX_MASK (~UINT32_C(0xfe000000)) #if GUF_DICT_HASH_T_MAX == UINT64_MAX #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( ((uint32_t)((HASH) >> 32)) & GUF_DICT_KV_META_HASHFRAG_MASK ) #elif GUF_DICT_HASH_T_MAX == UINT32_MAX #define GUF_DICT_HASH_T_GET_HASHFRAG(HASH) ( (HASH) & GUF_DICT_KV_META_HASHFRAG_MASK ) #else #error "guf_dict: invalid hash size (should not happen)" #endif #endif #define GUF_DICT_KV_META_IDX_NULL GUF_DICT_KV_META_IDX_MASK #define GUF_DICT_KV_META_IDX_TOMBSTONE (GUF_DICT_KV_META_IDX_NULL - 1) #define GUF_DICT_KV_META_IDX_MAX (GUF_DICT_KV_META_IDX_TOMBSTONE - 1) #define GUF_DICT_META_GET_IDX(META) ( (META) & GUF_DICT_KV_META_IDX_MASK ) #define GUF_DICT_META_GET_HASHFRAG(META) ( (META) & GUF_DICT_KV_META_HASHFRAG_MASK ) #define GUF_DICT_META_IS_NULL(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_NULL ) #define GUF_DICT_META_IS_TOMBSTONE(META) ( GUF_DICT_META_GET_IDX(META) == GUF_DICT_KV_META_IDX_TOMBSTONE ) #ifndef GUF_DICT_NAME #define GUF_DICT_NAME GUF_CAT(dict_, GUF_CAT(GUF_DICT_KEY_T, GUF_CAT(_to_, GUF_DICT_VAL_T))) #endif #ifndef GUF_DICT_KV_NAME #define GUF_DICT_KV_NAME GUF_CAT(GUF_DICT_NAME, _kv) #endif #define GUF_DICT_KV_DBUF GUF_CAT(GUF_DICT_KV_NAME, _dbuf) // - GUF_T_COPY: cpy function with signature GUF_T *copy(GUF_T *dst, const GUF_T *src, void *ctx) (default: copy by value) // - GUF_T_MOVE: move function with signature GUF_T *move(GUF_T *dst, GUF_T *src, void *ctx) (default: undefined) // - GUF_T_FREE: free function with signature void free(GUF_T *a, void *ctx) (default: undefined) // - GUF_T_EQ: equality function with signature bool eq(const GUF_T *a, const GUF_T *a) (default: undefined, or equality by value if GUF_T_IS_INTEGRAL_TYPE is defined) #ifndef GUF_DICT_IMPL typedef struct GUF_DICT_KV_NAME { GUF_DICT_KEY_T key; #ifdef GUF_DICT_VAL_T GUF_DICT_VAL_T val; #endif } GUF_DICT_KV_NAME; #define GUF_T GUF_DICT_KV_NAME #define GUF_DBUF_NAME GUF_DICT_KV_DBUF #define GUF_DBUF_ONLY_TYPES #include "guf_dbuf.h" typedef struct GUF_DICT_NAME { GUF_DICT_KV_DBUF kv_elems; // The actual key-value elements (contiguous in memory) GUF_DICT_KV_META_T *kv_indices; // Indices into the kv_elems dbuf. ptrdiff_t kv_indices_cap, num_tombstones; ptrdiff_t max_probelen; // Stores the worst-case probe-length (for performance measurement) } GUF_DICT_NAME; typedef GUF_CAT(GUF_DICT_KV_DBUF, _iter) GUF_CAT(GUF_DICT_NAME, _iter); #endif GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err); GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc); GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx); GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err); GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt); GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err); GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt); GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); #ifdef GUF_DICT_VAL_T GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); #endif GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key); GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht); GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht); /* Iterator functions */ GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht); GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx); GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it); #if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE)) GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle_val); GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle_val); #endif #if defined(GUF_DICT_VAL_T) GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *)); #endif GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht); GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht); GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void); // #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */ #if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC) #include "guf_assert.h" #include "guf_math.h" GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _debug_valid_size)(const GUF_DICT_NAME *ht) { ptrdiff_t cnt = 0; for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { if (!GUF_DICT_META_IS_NULL(ht->kv_indices[i]) && !GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[i])) { ++cnt; } } return cnt == ht->kv_elems.size; } static inline void GUF_CAT(GUF_DICT_KV_NAME, _free)(GUF_DICT_KV_NAME *kv, void *ctx) { (void)ctx; #ifdef GUF_DICT_KEY_T_FREE GUF_DICT_KEY_T_FREE(&kv->key, NULL); #endif #ifdef GUF_DICT_VAL_T_FREE GUF_DICT_VAL_T_FREE(&kv->val, NULL); #endif #if !defined(GUF_DICT_KEY_T_FREE) && !defined(GUF_DICT_VAL_T_FREE) (void)kv; #endif } #define GUF_T GUF_DICT_KV_NAME #define GUF_T_FREE GUF_CAT(GUF_DICT_KV_NAME, _free) #define GUF_DBUF_NAME GUF_DICT_KV_DBUF #define GUF_DBUF_WITHOUT_TYPES #define GUF_DBUF_IMPL_STATIC #include "guf_dbuf.h" GUF_DICT_KWRDS double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht) { if (ht->kv_indices_cap == 0) { return 1; } ptrdiff_t occupied_count = ht->kv_elems.size + ht->num_tombstones; GUF_ASSERT(occupied_count <= ht->kv_indices_cap); return (double)occupied_count / (double)ht->kv_indices_cap; } GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err) { if (!ht || !alloc) { guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in dict_try_init: ht or alloc NULL")); return NULL; } ht->kv_elems = (GUF_DICT_KV_DBUF){0}; GUF_CAT(GUF_DICT_KV_DBUF, _try_init)(&ht->kv_elems, 0, alloc, err); if (err != GUF_ERR_NONE) { return NULL; } ht->kv_indices = NULL; ht->kv_indices_cap = 0; ht->num_tombstones = 0; ht->max_probelen = 0; return ht; } GUF_DICT_KWRDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc) { return GUF_CAT(GUF_DICT_NAME, _try_init)(ht, alloc, NULL); } GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht) { if (!ht) { return false; } bool kv_dbuf_valid = GUF_CAT(GUF_DICT_KV_DBUF, _valid)(&ht->kv_elems); bool kv_meta_buf_valid = (!ht->kv_indices && !ht->kv_indices_cap) || (ht->kv_indices && ht->kv_indices_cap && guf_is_pow2_size_t(ht->kv_indices_cap)); bool count_valid = ht->num_tombstones >= 0 && ht->kv_elems.size >= 0 && (size_t)ht->kv_elems.size <= GUF_DICT_KV_META_IDX_MAX && ((ht->kv_elems.size + ht->num_tombstones) <= ht->kv_indices_cap); return kv_dbuf_valid && kv_meta_buf_valid && count_valid; } GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _max_capacity)(void) { const size_t max_cap_kv_elems = GUF_MIN(GUF_DICT_KV_META_IDX_MAX + 1, GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_NAME)); const size_t max_cap_kv_indices = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T); // Find next power of two (capacities must be powers of two). size_t pow2_cap = 1; const size_t MAX_SIZE_POW2 = SIZE_T_MAX & ~(SIZE_T_MAX >> 1); while ((pow2_cap < MAX_SIZE_POW2) && ((pow2_cap << 1) < max_cap_kv_indices) ) { pow2_cap <<= 1; } GUF_ASSERT(guf_is_pow2_size_t(pow2_cap) && pow2_cap <= max_cap_kv_indices && pow2_cap > 1); return GUF_MIN(GUF_MIN(max_cap_kv_elems, pow2_cap), PTRDIFF_MAX); } GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx) { (void)ctx; GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); guf_allocator *allocator = ht->kv_elems.allocator; if (ht->kv_indices) { allocator->free(ht->kv_indices, ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T), allocator->ctx); ht->kv_indices = NULL; ht->kv_indices_cap = 0; } GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_elems, NULL); ht->num_tombstones = 0; ht->max_probelen = 0; } GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); return ht->kv_elems.size; } static inline size_t GUF_CAT(GUF_DICT_NAME, _probe_offset)(size_t probe_len) { GUF_ASSERT(probe_len > 0); #ifdef GUF_DICT_PROBE_LINEAR return probe_len; // 1, 2, 3, 4, 5, ... #else /* Guaranteed to visit each index once for capacities which are powers of two. cf. https://fgiesen.wordpress.com/2015/02/22/triangular-numbers-mod-2n/ (last-retrieved 2024-07-29) */ return probe_len * (probe_len + 1) / 2; // 1, 3, 6, 10, 15, 21 ... (starting from probe_len == 1) #endif } static size_t GUF_CAT(GUF_DICT_NAME, _find_idx)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key, bool *key_exists) { if (ht->kv_indices_cap <= 0) { *key_exists = false; return SIZE_T_MAX; } const GUF_DICT_HASH_T hash = GUF_DICT_KEY_HASH(key); const GUF_DICT_KV_META_T hash_frag = GUF_DICT_HASH_T_GET_HASHFRAG(hash); #define GUF_MOD_CAP(A) ((size_t)(A) & (size_t)(ht->kv_indices_cap - 1)) /* A % ht->kv_indices_cap (kv_indices_cap must be a power of two > 0) */ size_t idx = GUF_MOD_CAP(hash); const size_t start_idx = idx; size_t first_tombstone_idx = SIZE_T_MAX; size_t probe_len = 0; // size_t seen_occupied = 0; // This allows us to bail out early once we visited every non-null/non-tombstone kv_idx. do { const GUF_DICT_KV_META_T kv_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); const GUF_DICT_KV_META_T kv_hashfrag = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[idx]); if (kv_idx == GUF_DICT_KV_META_IDX_NULL) { // 1.) Empty. if (first_tombstone_idx != SIZE_T_MAX) { idx = first_tombstone_idx; } ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); *key_exists = false; return idx; } else if (kv_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) { // 2.) Tombstone. if (first_tombstone_idx == SIZE_T_MAX) { first_tombstone_idx = idx; } goto probe; } else if (hash_frag == kv_hashfrag && GUF_DICT_KEY_T_EQ(key, &(GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->key))) { // 3.) Key already exists. ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); *key_exists = true; return idx; } else { // 4.) Have to probe due to hash-collision/tombstone. probe: ++probe_len; idx = GUF_MOD_CAP(start_idx + GUF_CAT(GUF_DICT_NAME, _probe_offset)(probe_len)); // NOTE: Add probe_offset to start_idx and not to idx. GUF_ASSERT((ptrdiff_t)probe_len <= (ht->kv_elems.size + ht->num_tombstones)); } } while (idx != start_idx && probe_len < (size_t)ht->kv_indices_cap); *key_exists = false; if (first_tombstone_idx != SIZE_T_MAX) { // Edge case: No empty slots, but found tombstone. ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); GUF_ASSERT(GUF_DICT_META_GET_IDX(ht->kv_indices[first_tombstone_idx]) == GUF_DICT_KV_META_IDX_NULL); return first_tombstone_idx; } else { // Failed to find an idx. return SIZE_T_MAX; } #undef GUF_MOD_CAP } static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, guf_err *err) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); #ifdef GUF_DICT_PROBE_LINEAR const double MAX_LOAD_FAC = 0.6; #else const double MAX_LOAD_FAC = 0.5; #endif const ptrdiff_t KV_META_START_CAP = 32; // Must be a power of two > 0. const ptrdiff_t KV_META_GROWTH_FAC = (ht->kv_indices_cap <= 128) ? 4 : 2; // Must be a power of two > 1. guf_allocator *allocator = ht->kv_elems.allocator; if (ht->kv_indices_cap == 0) { // 1.a) Allocate initial kv-index-buffer. GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(KV_META_START_CAP * sizeof(GUF_DICT_KV_META_T), allocator->ctx); if (new_kv_indices == NULL) { guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_grow: Initial allocation failed")); return; } ht->kv_indices = new_kv_indices; ht->kv_indices_cap = KV_META_START_CAP; for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { new_kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; } } else if (GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) > MAX_LOAD_FAC) { // 1.b) Grow kv-index-buffer if necessary. GUF_ASSERT(ht->kv_indices); GUF_ASSERT((size_t)ht->kv_indices_cap <= GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T)); const ptrdiff_t old_size_bytes = (size_t)ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T); ptrdiff_t new_size_bytes = 0; const size_t MAX_SIZE_BYTES = (size_t)GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T); const size_t new_size_bytes_test = (size_t)old_size_bytes * (size_t)KV_META_GROWTH_FAC; if (guf_mul_is_overflow_size_t(old_size_bytes, KV_META_GROWTH_FAC) || new_size_bytes_test > MAX_SIZE_BYTES) { // Handle overflow. guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New kv_indices_capacity would overflow)")); return; } else { GUF_ASSERT(new_size_bytes_test <= PTRDIFF_MAX); new_size_bytes = (ptrdiff_t)new_size_bytes_test; } GUF_ASSERT_RELEASE(new_size_bytes > old_size_bytes); // TODO: Not sure if alloc and free is better here than realloc (since we do not copy ht->kv_indices anyway.) GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(new_size_bytes, allocator->ctx); if (new_kv_indices == NULL) { guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: re-allocation failed")); return; } allocator->free(ht->kv_indices, old_size_bytes, allocator->ctx); ht->kv_indices = new_kv_indices; ht->kv_indices_cap = ht->kv_indices_cap * KV_META_GROWTH_FAC;; GUF_ASSERT(guf_is_pow2_size_t(ht->kv_indices_cap)); ht->num_tombstones = 0; // ht->max_probelen = 0; for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; } GUF_ASSERT((size_t)ht->kv_elems.size < GUF_DICT_KV_META_IDX_MAX); for (ptrdiff_t kv_idx = 0; kv_idx < ht->kv_elems.size; ++kv_idx) { // Re-insert keys. const GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx); GUF_ASSERT(kv); bool key_exists = false; const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &kv->key, &key_exists); GUF_ASSERT(!key_exists); GUF_ASSERT(new_idx < SIZE_T_MAX && new_idx < (size_t)ht->kv_indices_cap); const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(&kv->key); // TODO: might be expensive... GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)kv_idx) == 0); ht->kv_indices[new_idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)kv_idx; } } guf_err_set_if_not_null(err, GUF_ERR_NONE); GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) <= MAX_LOAD_FAC); } GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); if (!key || !val) { guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in function dict_try_insert: key or val argument is NULL")); return; } if ((size_t)ht->kv_elems.size >= (GUF_DICT_KV_META_IDX_MAX + 1)) { GUF_ASSERT(ht->kv_elems.size == (GUF_DICT_KV_META_IDX_MAX + 1)); guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dict_try_insert: dict has reached its max size 33,554,430 (or 2^48 - 2 for GUF_DICT_64_BIT_IDX)")); return; } // 1.) Grow kv-index-buffer if neccessary (or make the initial allocation.) GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(ht, err); if (err != NULL && *err != GUF_ERR_NONE) { guf_err_set_or_panic(err, *err, GUF_ERR_MSG("in function dict_try_insert: try_grow failed.")); return; } GUF_ASSERT_RELEASE(ht->kv_indices_cap > ht->kv_elems.size); // 2.) Insert new key-value pair. const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key); bool key_exists = false; size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); if (key_exists) { guf_err_set_or_panic(err, GUF_ERR_ALREADY_EXISTS, GUF_ERR_MSG("in function dict_try_insert: Key already exists")); return; } GUF_ASSERT_RELEASE(idx < (size_t)ht->kv_indices_cap); if (GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])) { ht->num_tombstones -= 1; GUF_ASSERT_RELEASE(ht->num_tombstones >= 0); } GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)ht->kv_elems.size) == 0); ht->kv_indices[idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)ht->kv_elems.size; GUF_DICT_KEY_T key_cpy; GUF_DICT_KEY_T *key_cpy_res = NULL; if (key_opt == GUF_CPY_DEEP) { #ifdef GUF_DICT_KEY_T_COPY key_cpy_res = GUF_DICT_KEY_T_COPY(&key_cpy, key); #else GUF_ASSERT_RELEASE(false); #endif } else if (key_opt == GUF_CPY_MOVE) { #ifdef GUF_DICT_KEY_T_MOVE key_cpy_res = GUF_DICT_KEY_T_MOVE(&key_cpy, key); #else GUF_ASSERT_RELEASE(false); #endif } else { key_cpy = *key; key_cpy_res = &key_cpy; } if (!key_cpy_res) { guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy key")); return; } GUF_DICT_VAL_T val_cpy; GUF_DICT_VAL_T *val_cpy_res = NULL; if (val_opt == GUF_CPY_DEEP) { #ifdef GUF_DICT_VAL_T_COPY val_cpy_res = GUF_DICT_KEY_T_COPY(&val_cpy, val); #else GUF_ASSERT_RELEASE(false); #endif } else if (val_opt == GUF_CPY_MOVE) { #ifdef GUF_DICT_VAL_T_MOVE val_cpy_res = GUF_DICT_KEY_T_MOVE(&val_cpy, val); #else GUF_ASSERT_RELEASE(false); #endif } else { val_cpy = *val; val_cpy_res = &val_cpy; } if (!val_cpy_res) { guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Failed to copy value")); if (key_opt == GUF_CPY_DEEP || key_opt == GUF_CPY_MOVE) { #ifdef GUF_DICT_KEY_T_FREE GUF_DICT_KEY_T_FREE(key_cpy_res, NULL); #endif } return; } GUF_DICT_KV_NAME kv = {.key = key_cpy, .val = val_cpy}; GUF_CAT(GUF_DICT_KV_DBUF, _try_push_val)(&ht->kv_elems, kv, err); if (err && *err != GUF_ERR_NONE) { // Insertion failed. GUF_ASSERT(*err != GUF_ERR_IDX_RANGE && *err != GUF_ERR_INVALID_ARG); #ifdef GUF_DICT_KEY_T_FREE GUF_DICT_KEY_T_FREE(&kv.key, NULL); #endif #ifdef GUF_DICT_VAL_T_FREE GUF_DICT_VAL_T_FREE(&kv.val, NULL); #endif } } GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T *key, GUF_DICT_VAL_T *val, guf_cpy_opt key_opt, guf_cpy_opt val_opt) { GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, key, val, key_opt, val_opt, NULL); } GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt, guf_err *err) { GUF_CAT(GUF_DICT_NAME, _try_insert)(ht, &key, &val, key_opt, val_opt, err); } GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key, GUF_DICT_VAL_T val, guf_cpy_opt key_opt, guf_cpy_opt val_opt) { GUF_CAT(GUF_DICT_NAME, _insert)(ht, &key, &val, key_opt, val_opt); } #ifdef GUF_DICT_VAL_T GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); if (!key) { return NULL; } bool key_exists = false; const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); if (!key_exists) { return NULL; } else { GUF_ASSERT(idx != SIZE_T_MAX); GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap); const size_t kv_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); GUF_ASSERT((ptrdiff_t)kv_idx < ht->kv_elems.size); return &GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->val; } } GUF_DICT_KWRDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) { return GUF_CAT(GUF_DICT_NAME, _at)(ht, &key); } #endif GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); if (!key) { return false; } bool key_exists = false; const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); if (key_exists) { GUF_ASSERT(idx != SIZE_T_MAX); GUF_ASSERT(!GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])); GUF_ASSERT(!GUF_DICT_META_IS_NULL(ht->kv_indices[idx])); } (void)idx; return key_exists; } GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) { return GUF_CAT(GUF_DICT_NAME, _contains)(ht, &key); } GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); if (!key || ht->kv_elems.size == 0) { return false; } bool key_exists = false; const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); if (!key_exists) { return false; } GUF_ASSERT(idx < SIZE_T_MAX && (ptrdiff_t)idx < ht->kv_indices_cap); const size_t kv_idx = (size_t)GUF_DICT_META_GET_IDX(ht->kv_indices[idx]); GUF_ASSERT(kv_idx < (size_t)ht->kv_elems.size); ht->kv_indices[idx] = GUF_DICT_KV_META_IDX_TOMBSTONE; ht->num_tombstones += 1; GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx); GUF_ASSERT(kv); GUF_CAT(GUF_DICT_KV_NAME, _free)(kv, NULL); if (ht->kv_elems.size > 1 && (ptrdiff_t)kv_idx != ht->kv_elems.size - 1) { // Switch last kv-elem into the erased position and update its kv-index accordingly. GUF_ASSERT(kv_idx <= GUF_DICT_KV_META_IDX_MAX); // 1.) Switch kv_elem. GUF_DICT_KV_NAME *last_kv = GUF_CAT(GUF_DICT_KV_DBUF, _back)(&ht->kv_elems); GUF_ASSERT(last_kv); GUF_ASSERT(kv != last_kv); *kv = *last_kv; // GUF_ASSERT(!GUF_DICT_KEY_T_EQ(key, &last_kv->key)); // 2.) Update kv_index. bool last_key_exists = false; const size_t last_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &last_kv->key, &last_key_exists); GUF_ASSERT(last_idx != idx); GUF_ASSERT(last_key_exists && (ptrdiff_t)last_idx < ht->kv_indices_cap); GUF_ASSERT(GUF_DICT_META_GET_IDX(ht->kv_indices[last_idx]) == (GUF_DICT_KV_META_T)(ht->kv_elems.size - 1)); ht->kv_indices[last_idx] = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[last_idx]) | (GUF_DICT_KV_META_T)kv_idx; } ht->kv_elems.size -= 1; GUF_ASSERT(ht->kv_elems.size >= 0); GUF_ASSERT(ht->num_tombstones <= ht->kv_indices_cap); // GUF_ASSERT(!GUF_CAT(GUF_DICT_NAME, _contains)(ht, key)); if (ht->kv_elems.size == 0 && ht->num_tombstones > 0) { // Optimisation: We can delete all tombstones here. ptrdiff_t del_tombstone_cnt = 0; for (ptrdiff_t i = 0; i < ht->kv_indices_cap && del_tombstone_cnt < ht->num_tombstones; ++i) { const GUF_DICT_KV_META_T kv_del_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[i]); GUF_ASSERT(GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[i]) == 0); GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE || kv_del_idx == GUF_DICT_KV_META_IDX_NULL); if (kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) { ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; ++del_tombstone_cnt; } else { GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_NULL); } } GUF_ASSERT(del_tombstone_cnt == ht->num_tombstones); ht->num_tombstones = 0; } return true; } GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase_val_arg)(GUF_DICT_NAME *ht, GUF_DICT_KEY_T key) { return GUF_CAT(GUF_DICT_NAME, _erase)(ht, &key); } /* Iterator functions */ GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _begin)(const GUF_DICT_NAME* ht) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _begin)(&ht->kv_elems); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _end)(const GUF_DICT_NAME* ht) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _end)(&ht->kv_elems); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rbegin)(const GUF_DICT_NAME* ht) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rbegin)(&ht->kv_elems); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _rend)(const GUF_DICT_NAME* ht) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _rend)(&ht->kv_elems); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _iter_is_end)(const GUF_DICT_NAME* ht, GUF_CAT(GUF_DICT_NAME, _iter) it) { const bool is_reverse_it = it.base != NULL; const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); return it.ptr == dbuf_end_it.ptr; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_next)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it, ptrdiff_t step) { GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_next)(&ht->kv_elems, it, step); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx) { GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _iter_at_idx)(&ht->kv_elems, idx); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _reverse_iter_at_idx)(const GUF_DICT_NAME *ht, ptrdiff_t idx) { GUF_CAT(GUF_DICT_KV_DBUF, _iter) kv_it = GUF_CAT(GUF_DICT_KV_DBUF, _reverse_iter_at_idx)(&ht->kv_elems, idx); return (GUF_CAT(GUF_DICT_NAME, _iter)){.ptr = kv_it.ptr, .base = kv_it.base}; } GUF_DICT_KWRDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _iter_to_idx)(const GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) it) { return GUF_CAT(GUF_DICT_KV_DBUF, _iter_to_idx)(&ht->kv_elems, it); } #if defined(GUF_DICT_VAL_T) && (defined(GUF_DICT_VAL_T_EQ) || defined(GUF_DICT_VAL_T_IS_INTEGRAL_TYPE)) GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, const GUF_DICT_VAL_T *needle) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_ASSERT_RELEASE(needle); const bool is_reverse_it = begin.base != NULL; GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { return dbuf_end_it; } if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { return dbuf_end_it; } for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) { #ifdef GUF_DICT_VAL_T_EQ if (GUF_DICT_VAL_T_EQ(&(it.ptr->val), needle)) { return it; } #else if (it.ptr->val == *needle) { return it; } #endif } return dbuf_end_it; } GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_val_arg)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, GUF_DICT_VAL_T needle) { return GUF_CAT(GUF_DICT_NAME, _find_val)(ht, begin, end, &needle); } #endif #if defined(GUF_DICT_VAL_T) GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if)(GUF_DICT_NAME *ht, GUF_CAT(GUF_DICT_NAME, _iter) begin, GUF_CAT(GUF_DICT_NAME, _iter) end, bool (*predicate)(const GUF_DICT_VAL_T *)) { GUF_ASSERT_RELEASE(GUF_CAT(GUF_DICT_NAME, _valid)(ht)); GUF_ASSERT_RELEASE(predicate); const bool is_reverse_it = begin.base != NULL; GUF_ASSERT_RELEASE(is_reverse_it == (end.base != NULL)); // begin and end must be the same iterator type. const GUF_CAT(GUF_DICT_NAME, _iter) dbuf_end_it = is_reverse_it ? GUF_CAT(GUF_DICT_NAME, _rend)(ht) : GUF_CAT(GUF_DICT_NAME, _end)(ht); if (!ht->kv_elems.data || !ht->kv_elems.size || (!begin.ptr && !begin.base) || (!end.ptr && !end.base)) { return dbuf_end_it; } if ((begin.ptr == dbuf_end_it.ptr) || (!is_reverse_it && begin.ptr >= end.ptr) || (is_reverse_it && begin.base <= end.base)) { return dbuf_end_it; } for (GUF_CAT(GUF_DICT_NAME, _iter) it = begin; it.ptr != end.ptr && it.ptr != NULL; it = GUF_CAT(GUF_DICT_NAME, _iter_next)(ht, it, 1)) { if (predicate(&(it.ptr->val))) { return it; } } return dbuf_end_it; } #endif #endif /* end GUF_IMPL/GUF_IMPL_STATIC */ #undef GUF_DICT_KV_META_T #undef GUF_DICT_HASH_T #undef GUF_DICT_HASH_T_MAX #undef GUF_DICT_32_BIT_HASH #undef GUF_DICT_64_BIT_HASH #undef GUF_DICT_64_BIT_IDX #undef GUF_DICT_HASH_T_GET_HASHFRAG #undef GUF_DICT_KV_META_HASHFRAG_MASK #undef GUF_DICT_KV_META_IDX_MASK #undef GUF_DICT_KV_META_IDX_NULL #undef GUF_DICT_KV_META_IDX_TOMBSTONE #undef GUF_DICT_KV_META_IDX_MAX #undef GUF_DICT_META_GET_IDX #undef GUF_DICT_META_GET_HASHFRAG #undef GUF_DICT_META_IS_NULL #undef GUF_DICT_META_IS_TOMBSTONE #undef GUF_DICT_NAME #undef GUF_DICT_IS_SET #undef GUF_DICT_PROBE_LINEAR #undef GUF_DICT_PROBE_QUADRATIC #undef GUF_DICT_KEY_T #undef GUF_DICT_KEY_T_IS_INTEGRAL_TYPE #undef GUF_DICT_KEY_T_EQ #undef GUF_DICT_KEY_T_FREE #undef GUF_DICT_KEY_T_CMP #undef GUF_DICT_KEY_T_COPY #undef GUF_DICT_KEY_T_MOVE #undef GUF_DICT_KEY_HASH #undef GUF_DICT_VAL_T #undef GUF_DICT_VAL_T_IS_INTEGRAL_TYPE #undef GUF_DICT_VAL_T_EQ #undef GUF_DICT_VAL_T_FREE #undef GUF_DICT_VAL_T_CMP #undef GUF_DICT_VAL_T_COPY #undef GUF_DICT_VAL_T_MOVE #undef GUF_DICT_KEY_LOOKUP_T #undef GUF_DICT_KEY_TO_LOOKUP_KEY_CONVERT #undef GUF_DICT_KV_NAME #undef GUF_DICT_KV_DBUF #undef GUF_DICT_IMPL_STATIC #undef GUF_DICT_IMPL #undef GUF_DICT_KWRDS