diff --git a/src/guf_dict.h b/src/guf_dict.h index ffd22b1..3d51d72 100755 --- a/src/guf_dict.h +++ b/src/guf_dict.h @@ -9,21 +9,6 @@ #include "guf_common.h" #include "guf_alloc.h" #include "guf_hash.h" - - typedef struct guf_dict_kv_meta_32 { - uint32_t kv_idx; // Index into the kv_elems dbuf. - uint32_t key_hash; - } guf_dict_kv_meta_32; - - typedef struct guf_dict_kv_meta_64 { - uint64_t kv_idx; // Index into the kv_elems dbuf. - uint64_t key_hash; - } guf_dict_kv_meta_64; - - typedef struct guf_dict_kv_meta { - guf_hash_size_t kv_idx; // Index into the kv_elems dbuf. - guf_hash_size_t key_hash; - } guf_dict_kv_meta; #endif #ifndef GUF_DICT_KEY_T @@ -42,42 +27,62 @@ #define GUF_DICT_IS_SET #endif -#if defined(GUF_DICT_32_BIT) - #define GUF_DICT_KV_IDX_T uint32_t - #define GUF_DICT_KV_META_T guf_dict_kv_meta_32 - #define GUF_DICT_KV_IDX_NULL UINT32_MAX -#elif defined(GUF_DICT_64_BIT) - #define GUF_DICT_KV_IDX_T uint64_t - #define GUF_DICT_KV_META_T guf_dict_kv_meta_64 - #define GUF_DICT_KV_IDX_NULL UINT64_MAX +#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_KV_IDX_T guf_hash_size_t - #define GUF_DICT_KV_META_T guf_dict_kv_meta - #define GUF_DICT_KV_IDX_NULL GUF_HASH_MAX + #define GUF_DICT_HASH_T guf_hash_size_t + #define GUF_DICT_HASH_T_MAX GUF_HASH_MAX #endif -#define GUF_DICT_KV_IDX_TOMBSTONE (GUF_DICT_KV_IDX_NULL - 1) +#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 PTRDIFF_MAX <= SIZE_T_MAX - #define GUF_DICT_MAX_PTR PTRDIFF_MAX + #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_HASH_T_HASHFRAG_MASK ) + #else + #error "guf_dict: invalid hash size (should not happen)" + #endif #else - #define GUF_DICT_MAX_PTR SIZE_T_MAX + #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 -#if (GUF_DICT_KV_IDX_TOMBSTONE - 1) <= GUF_DICT_MAX_PTR - #define GUF_DICT_KV_IDX_T_MAX (GUF_DICT_KV_IDX_TOMBSTONE - 1) -#else - #define GUF_DICT_KV_IDX_T_MAX GUF_DICT_MAX_PTR -#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_KEY_LOOKUP_T -// #define GUF_DICT_KEY_LOOKUP_T GUF_DICT_KEY_T -// #else -// // GUF_DICT_KEY_LOOKUP_T convert(const GUF_DICT_KEY_T *key) -// #ifndef GUF_DICT_KEY_TO_LOOKUP_KEY_CONVERT -// #error "GUF_DICT_KEY_TO_LOOKUP_KEY_CONVis must be defined since GUF_DICT_KEY_LOOKUP_T is defined" -// #endif -// #endif #ifndef GUF_DICT_NAME #define GUF_DICT_NAME GUF_CAT(dict_, GUF_CAT(GUF_DICT_KEY_T, GUF_CAT(_to_, GUF_DICT_VAL_T))) @@ -170,15 +175,16 @@ GUF_DICT_KWRDS bool 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 (ht->kv_indices[i].kv_idx != GUF_DICT_KV_IDX_NULL && ht->kv_indices[i].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE) { + if (!GUF_DICT_META_IS_NULL(ht->kv_indices[i]) && !GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[i])) { ++cnt; } } @@ -247,16 +253,25 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *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); - bool count_valid = ht->num_tombstones >= 0 && ht->kv_elems.size >= 0 && ((ht->kv_elems.size + ht->num_tombstones) <= ht->kv_indices_cap); + 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 bool GUF_CAT(GUF_DICT_NAME, _max_capacity)(void) { - const ptrdiff_t max_cap_kv_elems = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_NAME); - const ptrdiff_t max_cap_kv_indices = GUF_ALLOC_MAX_CAPACITY(GUF_DICT_KV_META_T); - return GUF_MIN(max_cap_kv_elems, max_cap_kv_indices); + 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_pow2 = SIZE_T_MAX & ~(SIZE_T_MAX >> 1); + while ((pow2_cap < max_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(max_cap_kv_elems, pow2_cap); } @@ -304,9 +319,10 @@ static size_t GUF_CAT(GUF_DICT_NAME, _find_idx)(GUF_DICT_NAME *ht, const GUF_DIC *key_exists = false; return SIZE_T_MAX; } - const GUF_DICT_KV_IDX_T hash = GUF_DICT_KEY_HASH(key); + 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) + #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; @@ -314,29 +330,28 @@ static size_t GUF_CAT(GUF_DICT_NAME, _find_idx)(GUF_DICT_NAME *ht, const GUF_DIC 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 { - if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_NULL) { // 1.) Empty. + 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); - GUF_ASSERT((ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_NULL) || (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE)); *key_exists = false; return idx; - } else if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { // 2.) Tombstone. + } 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 == ht->kv_indices[idx].key_hash && GUF_DICT_KEY_T_EQ(key, &(GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, ht->kv_indices[idx].kv_idx)->key))) { // 3.) Key already exists. + } 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; - // if (ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_NULL && ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE) { - // ++seen_occupied; // && seen_occupied <= (size_t)ht->kv_elems.size - // } 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)); } @@ -345,7 +360,7 @@ static size_t GUF_CAT(GUF_DICT_NAME, _find_idx)(GUF_DICT_NAME *ht, const GUF_DIC *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(ht->kv_indices[first_tombstone_idx].kv_idx == GUF_DICT_KV_IDX_NULL); + 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; @@ -363,7 +378,7 @@ static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, gu 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 = 2; // Must be a power of two > 1. + 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; @@ -376,30 +391,25 @@ static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, gu 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].kv_idx = GUF_DICT_KV_IDX_NULL; - new_kv_indices[i].key_hash = 0; + 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); - const ptrdiff_t old_size_bytes = ht->kv_indices_cap * sizeof(GUF_DICT_KV_META_T); + 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); // TODO: check - GUF_ASSERT(MAX_SIZE_BYTES % sizeof(GUF_DICT_KV_META_T) == 0 && MAX_SIZE_BYTES <= PTRDIFF_MAX); - - if (guf_mul_is_overflow_size_t(old_size_bytes, KV_META_GROWTH_FAC)) { - new_size_bytes = MAX_SIZE_BYTES; - } else { - const size_t mul = (size_t)old_size_bytes * (size_t)KV_META_GROWTH_FAC; - new_size_bytes = GUF_MIN(mul, MAX_SIZE_BYTES); - } - GUF_ASSERT(new_size_bytes % sizeof(GUF_DICT_KV_META_T) == 0); - - if (new_size_bytes <= old_size_bytes) { // Handle overflow. + 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) { @@ -408,27 +418,26 @@ static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, gu } allocator->free(ht->kv_indices, old_size_bytes, allocator->ctx); ht->kv_indices = new_kv_indices; - const ptrdiff_t new_indices_cap = new_size_bytes / sizeof(GUF_DICT_KV_META_T); // TODO: check - GUF_ASSERT(ht->kv_indices_cap < new_indices_cap); - ht->kv_indices_cap = new_indices_cap; + 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].kv_idx = GUF_DICT_KV_IDX_NULL; - ht->kv_indices[i].key_hash = 0; + ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL; } - GUF_ASSERT(ht->kv_elems.size <= GUF_DICT_KV_IDX_T_MAX); + 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 <= PTRDIFF_MAX); - ht->kv_indices[new_idx].kv_idx = (GUF_DICT_KV_IDX_T)kv_idx; - ht->kv_indices[new_idx].key_hash = GUF_DICT_KEY_HASH(&kv->key); // TODO: might be expensive... + 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; } } @@ -436,7 +445,6 @@ static void GUF_CAT(GUF_DICT_NAME, _try_grow_if_necessary)(GUF_DICT_NAME *ht, gu 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)); @@ -446,8 +454,9 @@ GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_D return; } - if (ht->kv_elems.size >= GUF_DICT_KV_IDX_T_MAX) { - guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("in function dict_try_insert: dict has reached its max size (UINT64_MAX - 2 or UINT32_MAX - 2)")); + 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; } @@ -460,6 +469,7 @@ GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_D 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) { @@ -468,14 +478,13 @@ GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _try_insert)(GUF_DICT_NAME *ht, GUF_D } GUF_ASSERT_RELEASE(idx < (size_t)ht->kv_indices_cap); - if (ht->kv_indices[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { + if (GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx])) { ht->num_tombstones -= 1; GUF_ASSERT_RELEASE(ht->num_tombstones >= 0); } - GUF_ASSERT(ht->kv_elems.size <= GUF_DICT_KV_IDX_T_MAX); - ht->kv_indices[idx].key_hash = GUF_DICT_KEY_HASH(key); - ht->kv_indices[idx].kv_idx = (GUF_DICT_KV_IDX_T)ht->kv_elems.size; + 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; @@ -565,7 +574,6 @@ GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, G 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) { @@ -573,7 +581,7 @@ GUF_DICT_KWRDS void GUF_CAT(GUF_DICT_NAME, _insert_val_arg)(GUF_DICT_NAME *ht, G } else { GUF_ASSERT(idx != SIZE_T_MAX); GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap); - const size_t kv_idx = ht->kv_indices[idx].kv_idx; + 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; } @@ -596,8 +604,8 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _contains)(GUF_DICT_NAME *ht, const G 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(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE); - GUF_ASSERT(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_NULL); + 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; @@ -622,14 +630,12 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_ if (!key_exists) { return false; } - GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap); + GUF_ASSERT(idx < SIZE_T_MAX && (ptrdiff_t)idx < ht->kv_indices_cap); - const size_t kv_idx = ht->kv_indices[idx].kv_idx; + 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].kv_idx = GUF_DICT_KV_IDX_TOMBSTONE; - ht->kv_indices[idx].key_hash = 0; - + 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); @@ -638,12 +644,12 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_ 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. @@ -651,10 +657,8 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_ 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((ptrdiff_t)ht->kv_indices[last_idx].kv_idx == ht->kv_elems.size - 1); - GUF_ASSERT(ht->kv_indices[last_idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE && ht->kv_indices[last_idx].kv_idx != GUF_DICT_KV_IDX_NULL); - GUF_ASSERT(kv_idx <= GUF_DICT_KV_IDX_T_MAX); - ht->kv_indices[last_idx].kv_idx = (GUF_DICT_KV_IDX_T)kv_idx; + 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; @@ -667,13 +671,14 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_ 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) { - GUF_ASSERT(ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE || ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_NULL); - if (ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { - ht->kv_indices[i].kv_idx = GUF_DICT_KV_IDX_NULL; - ht->kv_indices[i].key_hash = 0; + 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(ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_NULL); + GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_NULL); } } GUF_ASSERT(del_tombstone_cnt == ht->num_tombstones); @@ -813,15 +818,21 @@ GUF_DICT_KWRDS GUF_CAT(GUF_DICT_NAME, _iter) GUF_CAT(GUF_DICT_NAME, _find_val_if #endif /* end GUF_IMPL/GUF_IMPL_STATIC */ -#undef GUF_DICT_KV_IDX_NULL -#undef GUF_DICT_KV_IDX_TOMBSTONE -#undef GUF_DICT_32_BIT -#undef GUF_DICT_64_BIT - -#undef GUF_DICT_KV_IDX_T -#undef GUF_DICT_KV_IDX_T_MAX #undef GUF_DICT_KV_META_T -#undef GUF_DICT_MAX_PTR + +#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 diff --git a/src/guf_math.h b/src/guf_math.h index a2e1461..72bd996 100644 --- a/src/guf_math.h +++ b/src/guf_math.h @@ -141,6 +141,12 @@ static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, pt return is_safe; } +// cf. https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 (last-retrieved 2025-03-19) +static inline bool guf_is_pow2_u8(uint8_t x) { return x && !(x & (x - 1)); } +static inline bool guf_is_pow2_u16(uint16_t x) { return x && !(x & (x - 1)); } +static inline bool guf_is_pow2_u32(uint32_t x) { return x && !(x & (x - 1)); } +static inline bool guf_is_pow2_u64(uint64_t x) { return x && !(x & (x - 1)); } +static inline bool guf_is_pow2_size_t(size_t x) { return x && !(x & (x - 1)); } static bool guf_nearly_zero_f32(float x, float eps) {return fabsf(x) <= eps;} static bool guf_nearly_one_f32(float x, float eps) {return fabsf(x - 1) <= eps;} diff --git a/src/test/guf_dict_impl.h b/src/test/guf_dict_impl.h index 27a21fe..865b764 100644 --- a/src/test/guf_dict_impl.h +++ b/src/test/guf_dict_impl.h @@ -25,7 +25,7 @@ static inline guf_hash_size_t int32_hash(const int32_t *a) { - return guf_hash(a, sizeof(int32_t), GUF_HASH_INIT); + return guf_hash(a, sizeof(int32_t), GUF_HASH_INIT); // TODO: byte order... } static inline bool int32_eq(const int32_t *a, const int32_t *b) {