Use single u32/u64 as GUF_DICT_KV_META_T instead of two

This commit is contained in:
jun 2025-03-19 15:23:32 +01:00
parent e535d39e3d
commit 6a7bd2bd97
3 changed files with 138 additions and 121 deletions

View File

@ -9,21 +9,6 @@
#include "guf_common.h" #include "guf_common.h"
#include "guf_alloc.h" #include "guf_alloc.h"
#include "guf_hash.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 #endif
#ifndef GUF_DICT_KEY_T #ifndef GUF_DICT_KEY_T
@ -42,42 +27,62 @@
#define GUF_DICT_IS_SET #define GUF_DICT_IS_SET
#endif #endif
#if defined(GUF_DICT_32_BIT) #if defined(GUF_DICT_32_BIT_HASH)
#define GUF_DICT_KV_IDX_T uint32_t #define GUF_DICT_HASH_T uint32_t
#define GUF_DICT_KV_META_T guf_dict_kv_meta_32 #define GUF_DICT_HASH_T_MAX UINT32_MAX
#define GUF_DICT_KV_IDX_NULL UINT32_MAX #elif defined(GUF_DICT_64_BIT_HASH)
#elif defined(GUF_DICT_64_BIT) #define GUF_DICT_HASH_T uint64_t
#define GUF_DICT_KV_IDX_T uint64_t #define GUF_DICT_HASH_T_MAX UINT64_MAX
#define GUF_DICT_KV_META_T guf_dict_kv_meta_64
#define GUF_DICT_KV_IDX_NULL UINT64_MAX
#else #else
#define GUF_DICT_KV_IDX_T guf_hash_size_t #define GUF_DICT_HASH_T guf_hash_size_t
#define GUF_DICT_KV_META_T guf_dict_kv_meta #define GUF_DICT_HASH_T_MAX GUF_HASH_MAX
#define GUF_DICT_KV_IDX_NULL GUF_HASH_MAX
#endif #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 #if GUF_DICT_HASH_T_MAX == UINT64_MAX
#define GUF_DICT_MAX_PTR PTRDIFF_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 #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 #endif
#if (GUF_DICT_KV_IDX_TOMBSTONE - 1) <= GUF_DICT_MAX_PTR #define GUF_DICT_KV_META_IDX_NULL GUF_DICT_KV_META_IDX_MASK
#define GUF_DICT_KV_IDX_T_MAX (GUF_DICT_KV_IDX_TOMBSTONE - 1) #define GUF_DICT_KV_META_IDX_TOMBSTONE (GUF_DICT_KV_META_IDX_NULL - 1)
#else #define GUF_DICT_KV_META_IDX_MAX (GUF_DICT_KV_META_IDX_TOMBSTONE - 1)
#define GUF_DICT_KV_IDX_T_MAX GUF_DICT_MAX_PTR
#endif #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 #ifndef GUF_DICT_NAME
#define GUF_DICT_NAME GUF_CAT(dict_, GUF_CAT(GUF_DICT_KEY_T, GUF_CAT(_to_, GUF_DICT_VAL_T))) #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 */ // #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */
#if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC) #if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC)
#include "guf_assert.h" #include "guf_assert.h"
#include "guf_math.h" #include "guf_math.h"
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, _debug_valid_size)(const GUF_DICT_NAME *ht)
{ {
ptrdiff_t cnt = 0; ptrdiff_t cnt = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { 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; ++cnt;
} }
} }
@ -247,16 +253,25 @@ GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _valid)(const GUF_DICT_NAME *ht)
return false; return false;
} }
bool kv_dbuf_valid = GUF_CAT(GUF_DICT_KV_DBUF, _valid)(&ht->kv_elems); 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 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 && ((ht->kv_elems.size + ht->num_tombstones) <= 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; return kv_dbuf_valid && kv_meta_buf_valid && count_valid;
} }
GUF_DICT_KWRDS bool GUF_CAT(GUF_DICT_NAME, _max_capacity)(void) 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 size_t max_cap_kv_elems = GUF_MIN(GUF_DICT_KV_META_IDX_MAX + 1, 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_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; *key_exists = false;
return SIZE_T_MAX; 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); size_t idx = GUF_MOD_CAP(hash);
const size_t start_idx = idx; 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 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. // size_t seen_occupied = 0; // This allows us to bail out early once we visited every non-null/non-tombstone kv_idx.
do { 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) { if (first_tombstone_idx != SIZE_T_MAX) {
idx = first_tombstone_idx; idx = first_tombstone_idx;
} }
ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); 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; *key_exists = false;
return idx; 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) { if (first_tombstone_idx == SIZE_T_MAX) {
first_tombstone_idx = idx; first_tombstone_idx = idx;
} }
goto probe; 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); ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen);
*key_exists = true; *key_exists = true;
return idx; return idx;
} else { // 4.) Have to probe due to hash-collision/tombstone. } else { // 4.) Have to probe due to hash-collision/tombstone.
probe: probe:
++probe_len; ++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. 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)); 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; *key_exists = false;
if (first_tombstone_idx != SIZE_T_MAX) { // Edge case: No empty slots, but found tombstone. 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); 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; return first_tombstone_idx;
} else { // Failed to find an idx. } else { // Failed to find an idx.
return SIZE_T_MAX; 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; const double MAX_LOAD_FAC = 0.5;
#endif #endif
const ptrdiff_t KV_META_START_CAP = 32; // Must be a power of two > 0. 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; guf_allocator *allocator = ht->kv_elems.allocator;
@ -376,29 +391,24 @@ 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 = new_kv_indices;
ht->kv_indices_cap = KV_META_START_CAP; ht->kv_indices_cap = KV_META_START_CAP;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { 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] = GUF_DICT_KV_META_IDX_NULL;
new_kv_indices[i].key_hash = 0;
} }
} else if (GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) > MAX_LOAD_FAC) { // 1.b) Grow kv-index-buffer if necessary. } 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(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; 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 const size_t MAX_SIZE_BYTES = (size_t)GUF_ALLOC_MAX_BYTES(GUF_DICT_KV_META_T);
GUF_ASSERT(MAX_SIZE_BYTES % sizeof(GUF_DICT_KV_META_T) == 0 && MAX_SIZE_BYTES <= PTRDIFF_MAX); 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.
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.
guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New kv_indices_capacity would 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; 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.) // 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); GUF_DICT_KV_META_T *new_kv_indices = allocator->alloc(new_size_bytes, allocator->ctx);
@ -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); allocator->free(ht->kv_indices, old_size_bytes, allocator->ctx);
ht->kv_indices = new_kv_indices; ht->kv_indices = new_kv_indices;
const ptrdiff_t new_indices_cap = new_size_bytes / sizeof(GUF_DICT_KV_META_T); // TODO: check ht->kv_indices_cap = ht->kv_indices_cap * KV_META_GROWTH_FAC;;
GUF_ASSERT(ht->kv_indices_cap < new_indices_cap); GUF_ASSERT(guf_is_pow2_size_t(ht->kv_indices_cap));
ht->kv_indices_cap = new_indices_cap;
ht->num_tombstones = 0; ht->num_tombstones = 0;
// ht->max_probelen = 0; // ht->max_probelen = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap; ++i) { 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] = GUF_DICT_KV_META_IDX_NULL;
ht->kv_indices[i].key_hash = 0;
} }
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. 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); const GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx);
GUF_ASSERT(kv); GUF_ASSERT(kv);
bool key_exists = false; bool key_exists = false;
const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &kv->key, &key_exists); const size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, &kv->key, &key_exists);
GUF_ASSERT(!key_exists); GUF_ASSERT(!key_exists);
GUF_ASSERT(new_idx < SIZE_T_MAX && new_idx <= PTRDIFF_MAX); GUF_ASSERT(new_idx < SIZE_T_MAX && new_idx < (size_t)ht->kv_indices_cap);
ht->kv_indices[new_idx].kv_idx = (GUF_DICT_KV_IDX_T)kv_idx; const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(&kv->key); // TODO: might be expensive...
ht->kv_indices[new_idx].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_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_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)); 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; return;
} }
if (ht->kv_elems.size >= GUF_DICT_KV_IDX_T_MAX) { if ((size_t)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 (UINT64_MAX - 2 or UINT32_MAX - 2)")); 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; 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); GUF_ASSERT_RELEASE(ht->kv_indices_cap > ht->kv_elems.size);
// 2.) Insert new key-value pair. // 2.) Insert new key-value pair.
const GUF_DICT_HASH_T key_hash = GUF_DICT_KEY_HASH(key);
bool key_exists = false; bool key_exists = false;
size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (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); 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; ht->num_tombstones -= 1;
GUF_ASSERT_RELEASE(ht->num_tombstones >= 0); GUF_ASSERT_RELEASE(ht->num_tombstones >= 0);
} }
GUF_ASSERT(ht->kv_elems.size <= GUF_DICT_KV_IDX_T_MAX); GUF_ASSERT((GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) & (GUF_DICT_KV_META_T)ht->kv_elems.size) == 0);
ht->kv_indices[idx].key_hash = GUF_DICT_KEY_HASH(key); ht->kv_indices[idx] = GUF_DICT_HASH_T_GET_HASHFRAG(key_hash) | (GUF_DICT_KV_META_T)ht->kv_elems.size;
ht->kv_indices[idx].kv_idx = (GUF_DICT_KV_IDX_T)ht->kv_elems.size;
GUF_DICT_KEY_T key_cpy; GUF_DICT_KEY_T key_cpy;
GUF_DICT_KEY_T *key_cpy_res = NULL; 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) { if (!key) {
return NULL; return NULL;
} }
bool key_exists = false; bool key_exists = false;
const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists); const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (!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 { } else {
GUF_ASSERT(idx != SIZE_T_MAX); GUF_ASSERT(idx != SIZE_T_MAX);
GUF_ASSERT((ptrdiff_t)idx < ht->kv_indices_cap); 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); GUF_ASSERT((ptrdiff_t)kv_idx < ht->kv_elems.size);
return &GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx)->val; 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); const size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists);
if (key_exists) { if (key_exists) {
GUF_ASSERT(idx != SIZE_T_MAX); GUF_ASSERT(idx != SIZE_T_MAX);
GUF_ASSERT(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE); GUF_ASSERT(!GUF_DICT_META_IS_TOMBSTONE(ht->kv_indices[idx]));
GUF_ASSERT(ht->kv_indices[idx].kv_idx != GUF_DICT_KV_IDX_NULL); GUF_ASSERT(!GUF_DICT_META_IS_NULL(ht->kv_indices[idx]));
} }
(void)idx; (void)idx;
return key_exists; 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) { if (!key_exists) {
return false; 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); 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] = GUF_DICT_KV_META_IDX_TOMBSTONE;
ht->kv_indices[idx].key_hash = 0;
ht->num_tombstones += 1; ht->num_tombstones += 1;
GUF_DICT_KV_NAME *kv = GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_elems, kv_idx); 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); 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. 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. // 1.) Switch kv_elem.
GUF_DICT_KV_NAME *last_kv = GUF_CAT(GUF_DICT_KV_DBUF, _back)(&ht->kv_elems); GUF_DICT_KV_NAME *last_kv = GUF_CAT(GUF_DICT_KV_DBUF, _back)(&ht->kv_elems);
GUF_ASSERT(last_kv); GUF_ASSERT(last_kv);
GUF_ASSERT(kv != last_kv); GUF_ASSERT(kv != last_kv);
*kv = *last_kv; *kv = *last_kv;
// GUF_ASSERT(!GUF_DICT_KEY_T_EQ(key, &last_kv->key)); // GUF_ASSERT(!GUF_DICT_KEY_T_EQ(key, &last_kv->key));
// 2.) Update kv_index. // 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); 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_idx != idx);
GUF_ASSERT(last_key_exists && (ptrdiff_t)last_idx < ht->kv_indices_cap); 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(GUF_DICT_META_GET_IDX(ht->kv_indices[last_idx]) == (GUF_DICT_KV_META_T)(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); ht->kv_indices[last_idx] = GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[last_idx]) | (GUF_DICT_KV_META_T)kv_idx;
GUF_ASSERT(kv_idx <= GUF_DICT_KV_IDX_T_MAX);
ht->kv_indices[last_idx].kv_idx = (GUF_DICT_KV_IDX_T)kv_idx;
} }
ht->kv_elems.size -= 1; 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. if (ht->kv_elems.size == 0 && ht->num_tombstones > 0) { // Optimisation: We can delete all tombstones here.
ptrdiff_t del_tombstone_cnt = 0; ptrdiff_t del_tombstone_cnt = 0;
for (ptrdiff_t i = 0; i < ht->kv_indices_cap && del_tombstone_cnt < ht->num_tombstones; ++i) { 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); const GUF_DICT_KV_META_T kv_del_idx = GUF_DICT_META_GET_IDX(ht->kv_indices[i]);
if (ht->kv_indices[i].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { GUF_ASSERT(GUF_DICT_META_GET_HASHFRAG(ht->kv_indices[i]) == 0);
ht->kv_indices[i].kv_idx = GUF_DICT_KV_IDX_NULL; GUF_ASSERT(kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE || kv_del_idx == GUF_DICT_KV_META_IDX_NULL);
ht->kv_indices[i].key_hash = 0; if (kv_del_idx == GUF_DICT_KV_META_IDX_TOMBSTONE) {
ht->kv_indices[i] = GUF_DICT_KV_META_IDX_NULL;
++del_tombstone_cnt; ++del_tombstone_cnt;
} else { } 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); 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 */ #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_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_NAME
#undef GUF_DICT_IS_SET #undef GUF_DICT_IS_SET

View File

@ -141,6 +141,12 @@ static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, pt
return is_safe; 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_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;} static bool guf_nearly_one_f32(float x, float eps) {return fabsf(x - 1) <= eps;}

View File

@ -25,7 +25,7 @@
static inline guf_hash_size_t int32_hash(const int32_t *a) 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) static inline bool int32_eq(const int32_t *a, const int32_t *b)
{ {