diff --git a/CMakeLists.txt b/CMakeLists.txt index 324aece..20ea83b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if (NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/bin) endif () -add_executable(libguf_test ${SOURCES} src/guf_test.c) +add_executable(libguf_test ${SOURCES} src/guf_test.c src/guf_test_dict_impl.c) target_include_directories(libguf_test PRIVATE src) if (TARGET libguf_test) diff --git a/src/guf_assert.h b/src/guf_assert.h index 8f58113..7485597 100644 --- a/src/guf_assert.h +++ b/src/guf_assert.h @@ -16,6 +16,8 @@ typedef enum guf_err { GUF_ERR_INVALID_ARG, GUF_ERR_RUNTIME, GUF_ERR_LOGIC, + GUF_ERR_NOT_FOUND, + GUF_ERR_ALREADY_EXISTS, GUF_ERR_ASSERT_FAIL, GUF_ERR_TYPES_NUM } guf_err; @@ -81,6 +83,7 @@ static inline void guf_err_set_if_not_null(guf_err *err, guf_err err_val) #endif #ifdef GUF_INIT +#undef GUF_INIT static const char *guf_err_type_str[] = { [GUF_ERR_NONE] = "Not an error", [GUF_ERR_UNSPECIFIED] = "Error", @@ -91,6 +94,8 @@ static const char *guf_err_type_str[] = { [GUF_ERR_DIV_BY_ZERO] = "Division by zero", [GUF_ERR_IDX_RANGE] = "Index out of range", [GUF_ERR_INVALID_ARG] = "Invalid argument", + [GUF_ERR_NOT_FOUND] = "Not found error", + [GUF_ERR_ALREADY_EXISTS] = "Already exists error", [GUF_ERR_RUNTIME] = "Runtime error", [GUF_ERR_LOGIC] = "Logic error", [GUF_ERR_ASSERT_FAIL] = "Assertion failed" @@ -98,7 +103,6 @@ static const char *guf_err_type_str[] = { extern void guf_panic(guf_err err, const char *msg) { - GUF_ASSERT(guf_global_panic_handler); if (!guf_global_panic_handler) { fputs("libguf panic (note: guf_global_panic_handler is NULL)\n", stderr); if (msg) { @@ -118,12 +122,16 @@ extern void guf_set_global_panic_handler(guf_panic_handler_fn panic_handler) extern const char *guf_err_to_str(guf_err err) { - GUF_ASSERT(GUF_STATIC_BUF_SIZE(guf_err_type_str) == GUF_ERR_TYPES_NUM); + if (GUF_STATIC_BUF_SIZE(guf_err_type_str) != GUF_ERR_TYPES_NUM) { + puts("Note: size of guf_err_type_str != GUF_ERR_TYPES_NUM"); + } if (err < 0 || err >= GUF_ERR_TYPES_NUM) { return "Invalid error code"; } else { - GUF_ASSERT(err < GUF_STATIC_BUF_SIZE(guf_err_type_str)); + if (err > GUF_STATIC_BUF_SIZE(guf_err_type_str)) { + return "Invalid error string"; + } return guf_err_type_str[err]; } } @@ -146,5 +154,4 @@ extern void guf_panic_handler_default(guf_err err, const char *msg) guf_panic_handler_fn guf_global_panic_handler = guf_panic_handler_default; -#undef GUF_INIT #endif diff --git a/src/guf_cstr.h b/src/guf_cstr.h index d689def..2d5578c 100644 --- a/src/guf_cstr.h +++ b/src/guf_cstr.h @@ -1,7 +1,9 @@ #ifndef GUF_CSTR_H #define GUF_CSTR_H +#include #include "guf_common.h" #include "guf_assert.h" +#include "guf_hash.h" typedef const char* guf_cstr_const; @@ -19,6 +21,13 @@ static inline bool guf_cstr_const_eq(const guf_cstr_const *a, const guf_cstr_con return 0 == strcmp(*a, *b); } +static inline guf_hash_size_t guf_cstr_const_hash(const guf_cstr_const *a) +{ + GUF_ASSERT_RELEASE(a); + GUF_ASSERT_RELEASE(*a); + return guf_hash(*a, strlen(*a), GUF_HASH_INIT); +} + typedef char* guf_cstr_heap; static inline guf_cstr_heap *guf_cstr_heap_copy(guf_cstr_heap *dst, const guf_cstr_heap *src, void *ctx) diff --git a/src/guf_dbuf.h b/src/guf_dbuf.h index d9fe2a8..2e4a85e 100644 --- a/src/guf_dbuf.h +++ b/src/guf_dbuf.h @@ -29,6 +29,23 @@ #define GUF_CNT_NAME GUF_CAT(dbuf_, GUF_T) #endif +#ifdef GUF_ONLY_TYPES + #undef GUF_ONLY_TYPES + typedef struct GUF_CNT_NAME { + GUF_T *data; + ptrdiff_t size, capacity; + guf_allocator *allocator; + #ifdef GUF_CNT_WITH_ELEM_CTX + void *elem_ctx; // NULL by default; is passed as the ctx argument to GUF_T_COPY, GUF_T_MOVE and GUF_T_FREE + #endif + } GUF_CNT_NAME; + + typedef struct GUF_CAT(GUF_CNT_NAME, _iter) { + GUF_T *ptr; + GUF_T *base; // Not NULL For reverse iterators (unless dbuf->size == 0, then ptr and base are NULL for both iterator types) + } GUF_CAT(GUF_CNT_NAME, _iter); +#else + // Used for the first growth if dbuf->capacity is zero. #ifndef GUF_DBUF_INITIAL_CAP #define GUF_DBUF_INITIAL_CAP 8 @@ -42,12 +59,14 @@ #error "Integral types do not need COPY, MOVE, FREE or EQ functions" #endif -#ifdef GUF_IMPL_STATIC +#if defined(GUF_STATIC) || defined(GUF_IMPL_STATIC) #define GUF_FN_KEYWORDS static #else #define GUF_FN_KEYWORDS #endif +#if !defined(GUF_IMPL) + typedef struct GUF_CNT_NAME { GUF_T *data; ptrdiff_t size, capacity; @@ -65,12 +84,13 @@ typedef struct GUF_CNT_NAME { - rend(): base points to begin().ptr and ptr to NULL - reverse iterator for the first element of the container: base points to the second element, ptr points to the first element */ - typedef struct GUF_CAT(GUF_CNT_NAME, _iter) { GUF_T *ptr; GUF_T *base; // Not NULL For reverse iterators (unless dbuf->size == 0, then ptr and base are NULL for both iterator types) } GUF_CAT(GUF_CNT_NAME, _iter); +#endif + GUF_FN_KEYWORDS bool GUF_CAT(GUF_CNT_NAME, _valid)(const GUF_CNT_NAME* dbuf); GUF_FN_KEYWORDS void GUF_CAT(GUF_CNT_NAME, _try_reserve)(GUF_CNT_NAME *dbuf, ptrdiff_t min_capacity, guf_err *err); @@ -982,6 +1002,8 @@ GUF_FN_KEYWORDS GUF_CAT(GUF_CNT_NAME, _iter) GUF_CAT(GUF_CNT_NAME, _find_if)(GUF #endif /* end #ifdef GUF_IMPL */ +#endif /* end GUF_ONLY_TYPES */ + #undef GUF_DBUF_INITIAL_CAP #undef GUF_DBUF_USE_GROWTH_FAC_ONE_POINT_FIVE @@ -998,3 +1020,4 @@ GUF_FN_KEYWORDS GUF_CAT(GUF_CNT_NAME, _iter) GUF_CAT(GUF_CNT_NAME, _find_if)(GUF #undef GUF_FN_KEYWORDS #undef GUF_IMPL #undef GUF_IMPL_STATIC +#undef GUF_STATIC diff --git a/src/guf_dict.h b/src/guf_dict.h index 03cef6d..850252e 100755 --- a/src/guf_dict.h +++ b/src/guf_dict.h @@ -1,108 +1,460 @@ +#ifdef GUF_STATIC + #define GUF_DICT_STATIC + #undef GUF_STATIC +#endif + +#ifdef GUF_IMPL + #define GUF_DICT_IMPL + #undef GUF_IMPL +#endif + +#ifdef GUF_IMPL_STATIC + #define GUF_DICT_IMPL_STATIC + #undef GUF_IMPL_STATIC +#endif + +#if defined(GUF_DICT_STATIC) || defined(GUF_DICT_IMPL_STATIC) + #define GUF_DICT_FN_KEYWORDS static +#else + #define GUF_DICT_FN_KEYWORDS +#endif + #ifndef GUF_DICT_H #define GUF_DICT_H -#include "guf_common.h" -#include "guf_hash.h" + #include "guf_common.h" + #include "guf_alloc.h" + #include "guf_hash.h" -typedef enum guf_dict_probe_type {GUF_DICT_PROBE_LINEAR = 0, GUF_DICT_PROBE_QUADRATIC} guf_dict_probe_type; + #define GUF_DICT_KV_IDX_NULL GUF_HASH_MAX + #define GUF_DICT_KV_IDX_TOMBSTONE (GUF_HASH_MAX - 1) -// ~0.65 in fx10 fixed point (0.65 * 2^10) -#define GUF_DICT_MAX_LOAD_FAC_FX10_DEFAULT 666ul -#define GUF_DICT_PROBE_TYPE_DEFAULT GUF_DICT_PROBE_QUADRATIC + typedef struct guf_dict_kv_meta { + guf_hash_size_t kv_idx; // index into the key-value buffer. TODO: uint32_t? + guf_hash_size_t key_hash; + } guf_dict_kv_meta; -// #define GUF_HASH32_INIT 2166136261ul -// #define GUF_HASH64_INIT 14695981039346656037ull -// uint32_t guf_hash32(const void *data, size_t num_bytes, uint32_t hash); -// uint64_t guf_hash64(const void *data, size_t num_bytes, uint64_t hash); + // #define GUF_T guf_dict_kv_meta + // #define GUF_CNT_NAME guf_dict_kv_meta_dbuf + // #define GUF_ONLY_TYPES + // #include "guf_dbuf.h" +#endif -// #ifdef GUF_USE_32_BIT_HASH -// #define GUF_HASH_INIT GUF_HASH32_INIT -// static inline uint32_t guf_hash(const void *data, size_t num_bytes, uint32_t hash) { -// return guf_hash32(data, num_bytes, hash); -// } -// #define GUF_DICT_HASH_MAX UINT32_MAX -// #else -// #define GUF_HASH_INIT GUF_HASH64_INIT -// static inline uint64_t guf_hash(const void *data, size_t num_bytes, uint64_t hash) { -// return guf_hash64(data, num_bytes, hash); -// } -// #define GUF_DICT_HASH_MAX UINT64_MAX -// #endif +#ifndef GUF_DICT_KEY_T + #error "Undefined container template GUF_DICT_KEY_T" +#endif -typedef struct guf_dict_kv_funcs { - // Only used for keys: - bool (*eq)(const void *key_a, const void *key_b); // Can be NULL for keys and vals. Can be left uninitialised for vals. - guf_hash_size_t (*hash)(const void *key); // Can be NULL for keys and vals. Can be left uninitialised for vals. - - // Used for keys and vals: - void *(*cpy)(void *key_or_val_dst, const void *key_or_val_src); // Can be NULL for keys and vals. Never leave uninitialised. - void *(*move)(void *dst, void *key_or_val_src); // Can be NULL for keys and vals. Never leave uninitialised. - void (*free)(void *key_or_val); // Can be NULL for keys and vals. Never leave uninitialised. - size_t type_size; // Must always be set to sizeof(key_or_val). +#ifndef GUF_DICT_KEY_HASH + #error "Undefined container template GUF_DICT_KEY_HASH" +#endif -} guf_dict_kv_funcs; +#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 -typedef guf_hash_size_t guf_dict_kv_id; +#ifndef GUF_DICT_VAL_T + #define GUF_DICT_IS_SET +#endif -typedef struct guf_dict_kv_status { - guf_dict_kv_id kv_id; - guf_hash_size_t k_hash; -} guf_dict_kv_status; - -typedef struct guf_dict { - guf_dict_kv_status *kv_status; - void *keys, *vals; - guf_dict_kv_funcs key_funcs, val_funcs; - guf_dict_probe_type probe_t; - uint32_t max_load_fac_fx10; - size_t size, capacity_kv_status, capacity_key_val, num_tombstones, max_probelen; -} guf_dict; +#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 -extern const guf_dict GUF_DICT_UNINITIALISED; +#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 -typedef enum guf_dict_insert_opt { - GUF_DICT_CPY_KEY_VAL = 0, - GUF_DICT_MOVE_KEY = 1, - GUF_DICT_MOVE_VAL = 2, -} guf_dict_insert_opt; +#define GUF_DICT_KV_DBUF GUF_CAT(GUF_DICT_KV_NAME, _dbuf) -bool guf_dict_init(guf_dict *ht, size_t start_capacity, const guf_dict_kv_funcs *key_funcs, const guf_dict_kv_funcs *val_funcs); -void guf_dict_free(guf_dict *ht); +// - 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) -bool guf_dict_insert(guf_dict *ht, void *key, void *val); // bool copy_by_value -bool guf_dict_remove(guf_dict *ht, const void *key); -void *guf_dict_get_val(guf_dict *ht, const void *key); -bool guf_dict_contains_key(const guf_dict *ht, const void *key); +#ifndef GUF_DICT_IMPL -uint32_t guf_dict_load_factor_fx10(const guf_dict *ht); -double guf_dict_load_factor_double(const guf_dict *ht); -static inline uint32_t guf_dict_dbl_to_fx10_load_fac(double n) -{ - n = GUF_CLAMP(n, 0, 1); - const uint32_t fx10_scale = 1024; // 2^10 - return n * fx10_scale; -} +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; -typedef struct guf_dict_iter { - guf_dict *ht; - size_t idx; - size_t elems_seen; - const void *key; - void *val; -} guf_dict_iter; +#define GUF_T GUF_DICT_KV_NAME +#define GUF_CNT_NAME GUF_DICT_KV_DBUF +#define GUF_ONLY_TYPES +#include "guf_dbuf.h" -guf_dict_iter guf_dict_iter_begin(guf_dict *ht); -bool guf_dict_iter_is_end(guf_dict_iter *iter); -void guf_dict_iter_advance(guf_dict_iter *iter); - -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_CSTR; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_GUF_STR; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_i32; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_i64; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_u32; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_u64; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_float; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_double; -extern const guf_dict_kv_funcs GUF_DICT_FUNCS_NULL; +typedef struct GUF_DICT_NAME { + guf_dict_kv_meta *kv_meta_buf; + GUF_DICT_KV_DBUF kv_dbuf; + ptrdiff_t kv_meta_buf_cap, num_tombstones; + ptrdiff_t max_probelen; // Debug +} GUF_DICT_NAME; #endif + +GUF_DICT_FN_KEYWORDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _try_init)(GUF_DICT_NAME *ht, guf_allocator *alloc, guf_err *err); +GUF_DICT_FN_KEYWORDS GUF_DICT_NAME *GUF_CAT(GUF_DICT_NAME, _init)(GUF_DICT_NAME *ht, guf_allocator *alloc); + +GUF_DICT_FN_KEYWORDS void GUF_CAT(GUF_DICT_NAME, _free)(GUF_DICT_NAME *ht, void *ctx); + +GUF_DICT_FN_KEYWORDS 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_FN_KEYWORDS 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_FN_KEYWORDS void GUF_CAT(GUF_DICT_NAME, _try_erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key, guf_err *err); +GUF_DICT_FN_KEYWORDS void GUF_CAT(GUF_DICT_NAME, _erase)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_T *key); + +#ifdef GUF_DICT_VAL_T + GUF_DICT_FN_KEYWORDS GUF_DICT_VAL_T *GUF_CAT(GUF_DICT_NAME, _at)(GUF_DICT_NAME *ht, const GUF_DICT_KEY_LOOKUP_T *key); +#endif + +GUF_DICT_FN_KEYWORDS bool GUF_CAT(GUF_DICT_NAME, _contains)(const GUF_DICT_NAME *ht, const GUF_DICT_KEY_LOOKUP_T *key); + +GUF_DICT_FN_KEYWORDS ptrdiff_t GUF_CAT(GUF_DICT_NAME, _size)(const GUF_DICT_NAME *ht); + + +// #define GUF_DICT_IMPL /* DEBUGGGGGGGGG */ + +#if defined(GUF_DICT_IMPL) || defined(GUF_DICT_IMPL_STATIC) + +#include "guf_assert.h" + +// #define GUF_T guf_dict_kv_meta +// #define GUF_CNT_NAME guf_dict_kv_meta_dbuf +// #define GUF_STATIC +// #define GUF_IMPL +// #include "guf_dbuf.h" + +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_CNT_NAME GUF_DICT_KV_DBUF +#ifdef GUF_DICT_STATIC + #define GUF_STATIC +#endif +#define GUF_IMPL +#include "guf_dbuf.h" + +static inline double GUF_CAT(GUF_DICT_NAME, _load_factor)(const GUF_DICT_NAME *ht) +{ + if (ht->kv_meta_buf_cap == 0) { + return 1; + } + ptrdiff_t occupied_count = ht->kv_dbuf.size + ht->num_tombstones; + return (double)occupied_count / (double)ht->kv_meta_buf_cap; +} + + +GUF_DICT_FN_KEYWORDS 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_dbuf = (GUF_DICT_KV_DBUF){0}; + GUF_CAT(GUF_DICT_KV_DBUF, _try_init)(&ht->kv_dbuf, 0, alloc, err); + if (err != GUF_ERR_NONE) { + return NULL; + } + + ht->kv_meta_buf = NULL; + ht->kv_meta_buf_cap = 0; + + ht->num_tombstones = 0; + ht->max_probelen = 0; + return ht; +} + +GUF_DICT_FN_KEYWORDS 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_FN_KEYWORDS 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_dbuf); + bool kv_meta_buf_valid = (!ht->kv_meta_buf && !ht->kv_meta_buf_cap) || (ht->kv_meta_buf && ht->kv_meta_buf_cap); + return kv_dbuf_valid && kv_meta_buf_valid; +} + +GUF_DICT_FN_KEYWORDS 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_dbuf.allocator; + + if (ht->kv_meta_buf) { + allocator->free(ht->kv_meta_buf, ht->kv_meta_buf_cap * sizeof(guf_dict_kv_meta), allocator->ctx); + ht->kv_meta_buf = NULL; + ht->kv_meta_buf_cap = 0; + } + + GUF_CAT(GUF_DICT_KV_DBUF, _free)(&ht->kv_dbuf, NULL); + + ht->num_tombstones = 0; + ht->max_probelen = 0; +} + + +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 1; + #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, ... (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, bool find_first_free) +{ + if (ht->kv_meta_buf_cap == 0) { + return SIZE_MAX; + } + const guf_hash_size_t hash = GUF_DICT_KEY_HASH(key); + size_t idx = hash % ht->kv_meta_buf_cap; + const size_t start_idx = idx; + size_t probe_len = 1; + size_t first_tombstone_idx = SIZE_MAX; + do { + // printf("idx : %zu %td\n", idx, ht->kv_meta_buf_cap); + if (ht->kv_meta_buf[idx].kv_idx == GUF_DICT_KV_IDX_NULL) { // 1.) Empty. + if (first_tombstone_idx != SIZE_MAX) { + idx = first_tombstone_idx; + } + ht->max_probelen = GUF_MAX((ptrdiff_t)probe_len, ht->max_probelen); + GUF_ASSERT((ht->kv_meta_buf[idx].kv_idx == GUF_DICT_KV_IDX_NULL) || (ht->kv_meta_buf[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE)); + *key_exists = false; + return idx; + } else if (ht->kv_meta_buf[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { // 2.) Tombstone. + if (first_tombstone_idx == SIZE_MAX) { + first_tombstone_idx = idx; + } + if (find_first_free) { + goto end; + } else { + goto probe; + } + } else if (hash == ht->kv_meta_buf[idx].key_hash && GUF_DICT_KEY_T_EQ(key, &GUF_CAT(GUF_DICT_KV_DBUF, _at)(&ht->kv_dbuf, ht->kv_meta_buf[idx].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 (idx is already occupied, but not by the key). + probe: + idx = (idx + GUF_CAT(GUF_DICT_NAME, _probe_offset)(probe_len)) % ht->kv_meta_buf_cap; + ++probe_len; + GUF_ASSERT_RELEASE(probe_len < UINT32_MAX); + } + } while (idx != start_idx); + + end: + if (first_tombstone_idx != SIZE_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_meta_buf[first_tombstone_idx].kv_idx == GUF_DICT_KV_IDX_NULL ); + *key_exists = false; + return first_tombstone_idx; + } + *key_exists = false; + return SIZE_MAX; // Failed to find an idx. +} + +GUF_DICT_FN_KEYWORDS 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)); + + const ptrdiff_t KV_META_START_CAP = 64; + const ptrdiff_t KV_META_GROWTH_FAC = 2; + + guf_allocator *allocator = ht->kv_dbuf.allocator; + + if (ht->kv_meta_buf_cap == 0) { + guf_dict_kv_meta *kv_meta_buf = allocator->alloc(KV_META_START_CAP * sizeof(guf_dict_kv_meta), allocator->ctx); + if (kv_meta_buf == NULL) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: Initial allocation failed")); + return; + } + ht->kv_meta_buf = kv_meta_buf; + ht->kv_meta_buf_cap = KV_META_START_CAP; + for (ptrdiff_t i = 0; i < ht->kv_meta_buf_cap; ++i) { + kv_meta_buf[i].key_hash = 0; + kv_meta_buf[i].kv_idx = GUF_DICT_KV_IDX_NULL; + } + } else if (GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) > 0.6) { + + const ptrdiff_t old_size = ht->kv_meta_buf_cap * sizeof(guf_dict_kv_meta); + ptrdiff_t new_size = 0; + if (!guf_size_calc_safe(old_size, KV_META_GROWTH_FAC, &new_size)) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: New capacity would overflow)")); + return; + } + guf_dict_kv_meta *kv_meta_buf = allocator->alloc(new_size, allocator->ctx); + if (kv_meta_buf == NULL) { + guf_err_set_or_panic(err, GUF_ERR_ALLOC_FAIL, GUF_ERR_MSG("in function dict_try_insert: allocation failed")); + return; + } + + const ptrdiff_t new_kv_meta_cap = ht->kv_meta_buf_cap * KV_META_GROWTH_FAC; + for (ptrdiff_t i = 0; i < new_kv_meta_cap; ++i) { + kv_meta_buf[i].key_hash = 0; + kv_meta_buf[i].kv_idx = GUF_DICT_KV_IDX_NULL; + } + + guf_dict_kv_meta *old_kv_meta = ht->kv_meta_buf; + ptrdiff_t old_kv_meta_cap = ht->kv_meta_buf_cap; + + ht->kv_meta_buf = kv_meta_buf; + ht->kv_meta_buf_cap = new_kv_meta_cap; + ht->num_tombstones = 0; + + ptrdiff_t cnt = 0; + for (ptrdiff_t i = 0; i < old_kv_meta_cap; ++i) { // Insert into new buffer. + if (old_kv_meta[i].kv_idx != GUF_DICT_KV_IDX_NULL && old_kv_meta[i].kv_idx != GUF_DICT_KV_IDX_TOMBSTONE) { + bool key_exists = false; + size_t new_idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists, false); + GUF_ASSERT(!key_exists); + GUF_ASSERT(new_idx < SIZE_MAX); + ht->kv_meta_buf[new_idx] = old_kv_meta[i]; + ++cnt; + } + } + GUF_ASSERT(cnt == ht->kv_dbuf.size); + + allocator->free(old_kv_meta, old_size, allocator->ctx); + + GUF_ASSERT(GUF_CAT(GUF_DICT_NAME, _load_factor)(ht) < 0.6); + } + + GUF_ASSERT(ht->kv_meta_buf_cap > ht->kv_dbuf.size); + + bool key_exists = false; + size_t idx = GUF_CAT(GUF_DICT_NAME, _find_idx)(ht, key, &key_exists, false); + 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(idx < (size_t)ht->kv_meta_buf_cap); + + if (ht->kv_meta_buf[idx].kv_idx == GUF_DICT_KV_IDX_TOMBSTONE) { + ht->num_tombstones -= 1; + GUF_ASSERT_RELEASE(ht->num_tombstones >= 0); + } + + ht->kv_meta_buf[idx].key_hash = GUF_DICT_KEY_HASH(key); + ht->kv_meta_buf[idx].kv_idx = ht->kv_dbuf.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_dbuf, kv, err); +} + +#endif /* end GUF_IMPL/GUF_IMPL_STATIC */ + +#undef GUF_DICT_NAME +#undef GUF_DICT_IS_SET +#undef GUF_DICT_PROBE_LINEAR + +#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_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_STATIC +#undef GUF_DICT_FN_KEYWORDS diff --git a/src/guf_hash.h b/src/guf_hash.h index 827def9..8b0dd3a 100644 --- a/src/guf_hash.h +++ b/src/guf_hash.h @@ -8,7 +8,7 @@ cf. http://www.isthe.com/chongo/tech/comp/fnv/ (last retrieved: 2023-11-30) */ -#ifdef GUF_IMPL_STATIC +#if defined(GUF_IMPL_STATIC) || defined(GUF_STATIC) #define GUF_FN_KEYWORDS static #else #define GUF_FN_KEYWORDS @@ -39,6 +39,7 @@ GUF_FN_KEYWORDS uint64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint6 #endif #if defined(GUF_IMPL) || defined(GUF_IMPL_STATIC) + #include "guf_assert.h" GUF_FN_KEYWORDS uint32_t guf_hash32(const void *data, ptrdiff_t num_bytes, uint32_t hash) @@ -72,3 +73,4 @@ GUF_FN_KEYWORDS uint64_t guf_hash64(const void *data, ptrdiff_t num_bytes, uint6 #endif /* endif GUF_IMPL/GUF_IMPL_STATIC */ #undef GUF_FN_KEYWORDS +#undef GUF_STATIC diff --git a/src/guf_init.h b/src/guf_init.h index f795a2a..644fc40 100644 --- a/src/guf_init.h +++ b/src/guf_init.h @@ -5,6 +5,9 @@ #define GUF_INIT #include "guf_assert.h" +#define GUF_IMPL +#include "guf_hash.h" + // static inline bool guf_init(void) // { // static bool guf_is_init = false; diff --git a/src/guf_math.h b/src/guf_math.h index 1b05f85..3c727e8 100644 --- a/src/guf_math.h +++ b/src/guf_math.h @@ -53,7 +53,7 @@ static inline uint32_t guf_uabs_i32(int32_t x) {if (x >= 0) {return x;} else static inline uint64_t guf_uabs_i64(int64_t x) {if (x >= 0) {return x;} else if (x == INT64_MIN) {return (uint64_t)INT64_MAX + 1;} else {return -x;}} static inline unsigned char guf_absdiff_char(char a, char b) {return a > b ? (unsigned char)a - (unsigned char)b : (unsigned char)b - (unsigned char)a;} -static inline unsigned guf_absdiff_int(int a, int b) {return a > b ? (unsigned)a - (unsigned)b : (unsigned)b - (unsigned)a;} +static inline unsigned guf_absdiff_int(int a, int b) {return a > b ? (unsigned)a - (unsigned)b : (unsigned)b - (unsigned)a;} static inline uint8_t guf_absdiff_i8(int8_t a, int8_t b) {return a > b ? (uint8_t)a - (uint8_t)b : (uint8_t)b - (uint8_t)a;} static inline uint16_t guf_absdiff_i16(int16_t a, int16_t b) {return a > b ? (uint16_t)a - (uint16_t)b : (uint16_t)b - (uint16_t)a;} static inline uint32_t guf_absdiff_i32(int32_t a, int32_t b) {return a > b ? (uint32_t)a - (uint32_t)b : (uint32_t)b - (uint32_t)a;} diff --git a/src/guf_rand.h b/src/guf_rand.h index 23ab1b3..e501664 100644 --- a/src/guf_rand.h +++ b/src/guf_rand.h @@ -1,7 +1,7 @@ #ifndef GUF_RAND_H #define GUF_RAND_H -#ifdef GUF_IMPL_STATIC +#if defined(GUF_IMPL_STATIC) || defined(GUF_STATIC) #define GUF_FN_KEYWORDS static #else #define GUF_FN_KEYWORDS @@ -77,6 +77,7 @@ GUF_FN_KEYWORDS void guf_randstate_init(guf_randstate *state, uint64_t seed) for (size_t i = 0; i < GUF_STATIC_BUF_SIZE(state->s); ++i) { state->s[i] = (uint32_t)(guf_rand_splitmix64(&seed) >> 32); } + if (!state->s[0] && !state->s[1] && !state->s[2] && !state->s[3]) { // State must not be only zeroes: state->s[0] = 0x9e3779b9; // arbitrary constant != 0 seed = 0x9e3779b97f4a7c15; @@ -406,4 +407,6 @@ GUF_FN_KEYWORDS float guf_rand_normal_sample_one_f32(guf_randstate *state, float #undef GUF_IMPL_STATIC #endif /* endif GUF_IMPL/GUF_IMPL_STATIC */ +#undef GUF_STATIC #undef GUF_FN_KEYWORDS +#undef GUF_RAND_32_BIT diff --git a/src/guf_sort.h b/src/guf_sort.h index 435ef7c..f3cae55 100644 --- a/src/guf_sort.h +++ b/src/guf_sort.h @@ -15,7 +15,7 @@ typedef enum guf_sort_opt { #define GUF_FN_NAME_PREFIX GUF_CAT(GUF_T, _arr) #endif -#ifdef GUF_IMPL_STATIC +#if defined(GUF_IMPL_STATIC) || defined(GUF_STATIC) #define GUF_FN_KEYWORDS static #else #define GUF_FN_KEYWORDS @@ -182,6 +182,7 @@ GUF_FN_KEYWORDS bool GUF_CAT(GUF_FN_NAME_PREFIX, _is_sorted)(GUF_T *arr, ptrdiff #undef GUF_IMPL_STATIC #endif /* end #ifdef GUF_IMPL */ +#undef GUF_STATIC #undef GUF_FN_KEYWORDS #undef GUF_T #undef GUF_FN_NAME_PREFIX diff --git a/src/guf_test.c b/src/guf_test.c index d0b2b9a..ed30068 100644 --- a/src/guf_test.c +++ b/src/guf_test.c @@ -3,7 +3,7 @@ #include #include -#include "guf_init.h" /* Must be included once (sets up the global panic handler) */ +#include "guf_init.h" /* Must be included once */ #include "guf_alloc_libc.h" #include "guf_cstr.h" @@ -43,12 +43,25 @@ #define GUF_IMPL_STATIC #include "guf_dbuf.h" -#define GUF_RAND_32_BIT #define GUF_IMPL_STATIC #include "guf_rand.h" +#include "guf_test_dict_impl.h" + int main(void) { + dict_cstr_int ht; + dict_cstr_int_try_init(&ht, &guf_allocator_libc, NULL); + + dict_cstr_int_kv kv = {.key = "Hello", .val = 42}; + dict_cstr_int_try_insert(&ht, &kv.key, &kv.val, GUF_CPY_VALUE, GUF_CPY_VALUE, NULL); + + kv = (dict_cstr_int_kv){.key = "World", .val = 64}; + dict_cstr_int_try_insert(&ht, &kv.key, &kv.val, GUF_CPY_VALUE, GUF_CPY_VALUE, NULL); + + dict_cstr_int_free(&ht, NULL); + + printf("libguf test: compiled with C %ld\n", __STDC_VERSION__); guf_allocator test_allocator = guf_allocator_libc; guf_libc_alloc_ctx test_allocator_ctx = {.alloc_type_id = 0, .thread_id = 0, .zero_init = true}; diff --git a/src/guf_test_dict_impl.c b/src/guf_test_dict_impl.c new file mode 100644 index 0000000..0c6260c --- /dev/null +++ b/src/guf_test_dict_impl.c @@ -0,0 +1,9 @@ +#include "guf_test_dict_impl.h" + +#define GUF_DICT_KEY_T guf_cstr_const +#define GUF_DICT_KEY_T_EQ guf_cstr_const_eq +#define GUF_DICT_KEY_HASH guf_cstr_const_hash +#define GUF_DICT_VAL_T int +#define GUF_DICT_NAME dict_cstr_int +#define GUF_IMPL +#include "guf_dict.h" diff --git a/src/guf_test_dict_impl.h b/src/guf_test_dict_impl.h new file mode 100644 index 0000000..47ed821 --- /dev/null +++ b/src/guf_test_dict_impl.h @@ -0,0 +1,14 @@ +#ifndef GUF_DICT_IMPL_H +#define GUF_DICT_IMPL_H + +#include "guf_common.h" +#include "guf_cstr.h" + +#define GUF_DICT_KEY_T guf_cstr_const +#define GUF_DICT_KEY_HASH guf_cstr_const_hash +#define GUF_DICT_KEY_T_EQ guf_cstr_const_eq +#define GUF_DICT_VAL_T int +#define GUF_DICT_NAME dict_cstr_int +#include "guf_dict.h" + +#endif diff --git a/src/guf_utils.h b/src/guf_utils.h index b13fc47..97258ed 100644 --- a/src/guf_utils.h +++ b/src/guf_utils.h @@ -1,11 +1,18 @@ #ifndef GUF_UTILS_H #define GUF_UTILS_H +#include "guf_assert.h" -static inline bool guf_system_is_big_endian(void) +static inline bool guf_platform_is_big_endian(void) { unsigned i = 1; const char *bytes = (const char*)&i; return bytes[0] != 1; } +static inline int guf_platform_native_word_bits(void) +{ + const int bits = sizeof(void*) * CHAR_BIT; + return bits; +} + #endif