details: https://hg.nginx.org/njs/rev/c7d2a7846b0b branches: changeset: 2186:c7d2a7846b0b user: Vadim Zhestikov <v.zhesti...@f5.com> date: Wed Aug 30 12:06:12 2023 -0700 description: Introduced flat hash.
Object property enumeration order is corrected. This fixes #189 issue on Github. diffstat: auto/sources | 2 +- external/njs_shell.c | 4 +- src/njs_array.c | 45 +++- src/njs_array.h | 2 + src/njs_array_buffer.c | 4 +- src/njs_async.c | 4 +- src/njs_buffer.c | 4 +- src/njs_date.c | 4 +- src/njs_encoding.c | 8 +- src/njs_error.c | 40 +- src/njs_flathsh.c | 560 ++++++++++++++++++++++++++++++++++++++++++ src/njs_flathsh.h | 156 +++++++++++ src/njs_function.c | 8 +- src/njs_main.h | 2 +- src/njs_number.c | 4 +- src/njs_object.c | 511 +++++++++++++++++++++++-------------- src/njs_object_prop.c | 1 + src/njs_promise.c | 4 +- src/njs_regexp.c | 4 +- src/njs_string.c | 4 +- src/njs_symbol.c | 4 +- src/njs_typed_array.c | 40 +- src/njs_value.c | 43 ++- src/njs_value.h | 3 +- src/njs_vm.c | 2 +- src/test/lvlhsh_unit_test.c | 2 +- src/test/njs_externals_test.c | 2 +- src/test/njs_unit_test.c | 39 +- 28 files changed, 1215 insertions(+), 291 deletions(-) diffs (truncated from 2123 to 1000 lines): diff -r c0f581b26e84 -r c7d2a7846b0b auto/sources --- a/auto/sources Wed Aug 23 10:09:22 2023 -0700 +++ b/auto/sources Wed Aug 30 12:06:12 2023 -0700 @@ -10,7 +10,7 @@ NJS_LIB_SRCS=" \ src/njs_utf16.c \ src/njs_arr.c \ src/njs_rbtree.c \ - src/njs_lvlhsh.c \ + src/njs_flathsh.c \ src/njs_trace.c \ src/njs_random.c \ src/njs_md5.c \ diff -r c0f581b26e84 -r c7d2a7846b0b external/njs_shell.c --- a/external/njs_shell.c Wed Aug 23 10:09:22 2023 -0700 +++ b/external/njs_shell.c Wed Aug 30 12:06:12 2023 -0700 @@ -11,7 +11,7 @@ #include <njs_arr.h> #include <njs_queue.h> #include <njs_rbtree.h> -#include <njs_lvlhsh.h> +#include <njs_flathsh.h> #include <njs_djb_hash.h> #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) @@ -1636,7 +1636,7 @@ lvlhsh_key_test(njs_lvlhsh_query_t *lhq, static void * lvlhsh_pool_alloc(void *pool, size_t size) { - return njs_mp_align(pool, size, size); + return njs_mp_align(pool, NJS_MAX_ALIGNMENT, size); } diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_array.c --- a/src/njs_array.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_array.c Wed Aug 30 12:06:12 2023 -0700 @@ -609,10 +609,10 @@ njs_array_of(njs_vm_t *vm, njs_value_t * static const njs_object_prop_t njs_array_constructor_properties[] = { + NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("Array"), - NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), NJS_DECLARE_PROP_NATIVE("from", njs_array_from, 1, 0), @@ -1828,6 +1828,47 @@ njs_array_indices_handler(const void *fi } +int +njs_array_indices_handler_nums(const void *first, const void *second, void *ctx) +{ + double num1, num2; + int64_t diff; + const njs_value_t *val1, *val2; + + val1 = first; + val2 = second; + + num1 = njs_string_to_index(val1); + num2 = njs_string_to_index(val2); + + if (!isnan(num1) || !isnan(num2)) { + if (isnan(num1)) { + if (!isnan(num2)) { + return 1; + + } else { + + return 0; + } + } + + if (isnan(num2)) { + return -1; + } + + diff = (int64_t) (num1 - num2); + + if (diff < 0) { + return -1; + } + + return diff != 0; + } + + return 0; +} + + njs_array_t * njs_array_keys(njs_vm_t *vm, njs_value_t *object, njs_bool_t all) { diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_array.h --- a/src/njs_array.h Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_array.h Wed Aug 30 12:06:12 2023 -0700 @@ -36,6 +36,8 @@ njs_int_t njs_array_expand(njs_vm_t *vm, uint32_t append); njs_int_t njs_array_prototype_to_string(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); +int njs_array_indices_handler_nums(const void *first, const void *second, + void *ctx); njs_inline njs_value_t * diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_array_buffer.c --- a/src/njs_array_buffer.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_array_buffer.c Wed Aug 30 12:06:12 2023 -0700 @@ -141,9 +141,9 @@ njs_array_buffer_writable(njs_vm_t *vm, static const njs_object_prop_t njs_array_buffer_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("ArrayBuffer"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("ArrayBuffer"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_async.c --- a/src/njs_async.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_async.c Wed Aug 30 12:06:12 2023 -0700 @@ -163,9 +163,9 @@ njs_async_context_free(njs_vm_t *vm, njs static const njs_object_prop_t njs_async_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("AsyncFunction"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("AsyncFunction"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_buffer.c --- a/src/njs_buffer.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_buffer.c Wed Aug 30 12:06:12 2023 -0700 @@ -2538,10 +2538,10 @@ const njs_object_init_t njs_buffer_prot static const njs_object_prop_t njs_buffer_constructor_properties[] = { + NJS_DECLARE_PROP_LENGTH(0), + NJS_DECLARE_PROP_NAME("Buffer"), - NJS_DECLARE_PROP_LENGTH(0), - NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), NJS_DECLARE_PROP_NATIVE("alloc", njs_buffer_alloc_safe, 0, 1), diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_date.c --- a/src/njs_date.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_date.c Wed Aug 30 12:06:12 2023 -0700 @@ -1109,9 +1109,9 @@ njs_date_number_parse(int64_t *value, co static const njs_object_prop_t njs_date_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("Date"), + NJS_DECLARE_PROP_LENGTH(7), - NJS_DECLARE_PROP_LENGTH(7), + NJS_DECLARE_PROP_NAME("Date"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_encoding.c --- a/src/njs_encoding.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_encoding.c Wed Aug 30 12:06:12 2023 -0700 @@ -256,9 +256,9 @@ const njs_object_init_t njs_text_encode static const njs_object_prop_t njs_text_encoder_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("TextEncoder"), + NJS_DECLARE_PROP_LENGTH(0), - NJS_DECLARE_PROP_LENGTH(0), + NJS_DECLARE_PROP_NAME("TextEncoder"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -593,9 +593,9 @@ const njs_object_init_t njs_text_decode static const njs_object_prop_t njs_text_decoder_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("TextDecoder"), + NJS_DECLARE_PROP_LENGTH(0), - NJS_DECLARE_PROP_LENGTH(0), + NJS_DECLARE_PROP_NAME("TextDecoder"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_error.c --- a/src/njs_error.c Wed Aug 23 10:09:22 2023 -0700 +++ b/src/njs_error.c Wed Aug 30 12:06:12 2023 -0700 @@ -354,9 +354,9 @@ njs_error_constructor(njs_vm_t *vm, njs_ static const njs_object_prop_t njs_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("Error"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("Error"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -370,9 +370,9 @@ const njs_object_init_t njs_error_const static const njs_object_prop_t njs_eval_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("EvalError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("EvalError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -386,9 +386,9 @@ const njs_object_init_t njs_eval_error_ static const njs_object_prop_t njs_internal_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("InternalError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("InternalError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -402,9 +402,9 @@ const njs_object_init_t njs_internal_er static const njs_object_prop_t njs_range_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("RangeError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("RangeError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -418,9 +418,9 @@ const njs_object_init_t njs_range_error static const njs_object_prop_t njs_reference_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("ReferenceError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("ReferenceError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -434,9 +434,9 @@ const njs_object_init_t njs_reference_e static const njs_object_prop_t njs_syntax_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("SyntaxError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("SyntaxError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -450,9 +450,9 @@ const njs_object_init_t njs_syntax_erro static const njs_object_prop_t njs_type_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("TypeError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("TypeError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -466,9 +466,9 @@ const njs_object_init_t njs_type_error_ static const njs_object_prop_t njs_uri_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("URIError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("URIError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -482,9 +482,9 @@ const njs_object_init_t njs_uri_error_c static const njs_object_prop_t njs_aggregate_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("AggregateError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("AggregateError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_object_prototype_create, 0, 0, 0), }; @@ -566,9 +566,9 @@ njs_memory_error_prototype_create(njs_vm static const njs_object_prop_t njs_memory_error_constructor_properties[] = { - NJS_DECLARE_PROP_NAME("MemoryError"), + NJS_DECLARE_PROP_LENGTH(1), - NJS_DECLARE_PROP_LENGTH(1), + NJS_DECLARE_PROP_NAME("MemoryError"), NJS_DECLARE_PROP_HANDLER("prototype", njs_memory_error_prototype_create, 0, 0, 0), diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_flathsh.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/njs_flathsh.c Wed Aug 30 12:06:12 2023 -0700 @@ -0,0 +1,560 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <njs_main.h> + +/* + * Flat hash consists of dynamic DATA STRUCTURE and set of OPERATIONS. + * + * DATA STRUCTURE + * Consists of 3 parts allocated one by one in chunk of memory: + * + * HASH_CELLS array of indices of 1st list element in ELEMENTS array, + * or 0 if list is empty. HASH_CELLS_length is power of 2. + * DESCRIPTOR contains hash_mask (= HASH_CELLS_Length - 1), ELEMENTS_size, + * number of last used element in ELEMENTS, + * number of deleted elements in ELEMENTS; + * ELEMENTS array of: [index of next element, hash_function(KEY), + * link to stored value or NULL if element is not used)]. + * + * PROPERTIES of flat hash + * It is relocatable in memory, preserve insertion order, expand and shrink + * depending on number elements in it, maximum occupancy is 2^32-2 elements. + * + * OPERATIONS + * ADD element by key S with value V + * Prerequisite: be sure if flat hash not contains S. If ELEMENTS has free + * space after last element, then this element is populated by: V, + * hash_function(S), S. Then element is added to correspondent HASH_CELL. + * In case when no free element in ELEMENTS, DATA STRUCTURE is expanded by + * expnad_elts(). It does the following: ELEMENTS_size is increased by + * EXPAND_FACTOR, which value is expected to be > 1. For fast access to + * stored values, HASH_CELLS_size need to be big enough to provide its low + * population: in average less than 1 element per HASH_CELL. So, + * if HASH_CELLS_size < ELEMENTS_size then it will try doubling + * HASH_CELLS_size, until new HASH_CELLS_size >= ELEMENTS_size. Now + * new HASH_CELLS_size and new ELEMENTS_size are both defined. New + * expanded hash is obtained as: + * if HASH_CELLS_size is not increased, then + * reallocate full DATA STRUCTURE, + * else + * create new DATA STRUCTURE and populate it + * by ELEMENTS from old DATA STRUCTURE. + * Replace old DATA STRUCTURE by new one and release old one. + * + * FIND element by key S + * HASH_CELLS is array which contains cells of hash + * table. As entry to the table the following index is used: + * cell_num = hash_function(S) & hash_nask + * hash_function is external and it is not specified here, it is needed to + * be good hash function for Ss, and produce results in range from 0 to + * at least 2^32 - 1; hash_mask is located in DESCRIPTOR, and it is equal + * to HASH_CELLS_size - 1, where HASH_CELLS_size is always power of 2. + * hash cell contains (may be empty) list of hash elements with same + * cell_num. Now run over the list of elements and test if some element + * contains link to S. Test function is external and is not specified here. + * + * DELETE element by key S + * Locate S in ELEMENTS and remove it from elements list. Update number + * of removed elements in hash_decriptor. Mark deleted + * element as not used/deleted. If number of deleted elements is big + * enough, then use shrink_elts(): it removes gaps in ELEMENTS, shrinks if + * required HASH_CELLS, and creates new DATA STRUCTURE. + * + * ENUMERATE all elements in order of insertion + * Returns one by one used elements from ELEMENTS. + */ + + +#define NJS_FLATHSH_ELTS_INITIAL_SIZE 2 +#define NJS_FLATHSH_HASH_INITIAL_SIZE 4 +#define NJS_FLATHSH_ELTS_EXPAND_FACTOR_NUM 3 +#define NJS_FLATHSH_ELTS_EXPAND_FACTOR_DENOM 2 +#define NJS_FLATHSH_ELTS_FRACTION_TO_SHRINK 2 +#define NJS_FLATHSH_ELTS_MINIMUM_TO_SHRINK 8 + + +struct njs_flathsh_descr_s { + uint32_t hash_mask; + uint32_t elts_size; /* allocated properties */ + uint32_t elts_count; /* include deleted properties */ + uint32_t elts_deleted_count; +}; + + +static njs_flathsh_descr_t *njs_flathsh_alloc(njs_flathsh_query_t *fhq, + size_t hash_size, size_t elts_size); +static njs_flathsh_descr_t *njs_expand_elts(njs_flathsh_query_t *fhq, + njs_flathsh_descr_t *h, uint32_t count); + + +njs_inline size_t +njs_flathsh_chunk_size(size_t hash_size, size_t elts_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(njs_flathsh_descr_t) + + elts_size * sizeof(njs_flathsh_elt_t); +} + + +njs_inline void * +njs_flathsh_malloc(njs_flathsh_query_t *fhq, size_t size) +{ + return +#ifdef NJS_FLATHSH_USE_SYSTEM_ALLOCATOR + malloc(size) +#else + fhq->proto->alloc(fhq->pool, size) +#endif + ; +} + + +njs_inline void +njs_flathsh_free(njs_flathsh_query_t *fhq, void *ptr) +{ +#ifdef NJS_FLATHSH_USE_SYSTEM_ALLOCATOR + free(ptr) +#else + fhq->proto->free(fhq->pool, ptr, 0) +#endif + ; +} + + +njs_inline njs_flathsh_descr_t * +njs_flathsh_descr(void *chunk, size_t hash_size) +{ + return (njs_flathsh_descr_t *) ((uint32_t *) chunk + hash_size); +} + + +njs_inline uint32_t * +njs_hash_cells_end(njs_flathsh_descr_t *h) +{ + return (uint32_t *) h; +} + + +njs_inline void * +njs_flathsh_chunk(njs_flathsh_descr_t *h) +{ + return njs_hash_cells_end(h) - ((njs_int_t) h->hash_mask + 1); +} + + +njs_inline njs_flathsh_elt_t * +njs_hash_elts(njs_flathsh_descr_t *h) +{ + return (njs_flathsh_elt_t *) ((char *) h + sizeof(njs_flathsh_descr_t)); +} + + +/* + * Create a new empty flat hash. + */ +njs_flathsh_descr_t * +njs_flathsh_new(njs_flathsh_query_t *fhq) +{ + return njs_flathsh_alloc(fhq, NJS_FLATHSH_HASH_INITIAL_SIZE, + NJS_FLATHSH_ELTS_INITIAL_SIZE); +} + + +static njs_flathsh_descr_t * +njs_flathsh_alloc(njs_flathsh_query_t *fhq, size_t hash_size, size_t elts_size) +{ + void *chunk; + size_t size; + njs_flathsh_descr_t *h; + + njs_assert_msg(hash_size != 0 && (hash_size & (hash_size - 1)) == 0, + "hash_size must be a power of two"); + + size = njs_flathsh_chunk_size(hash_size, elts_size); + + chunk = njs_flathsh_malloc(fhq, size); + if (njs_slow_path(chunk == NULL)) { + return NULL; + } + + h = njs_flathsh_descr(chunk, hash_size); + + njs_memzero(chunk, sizeof(uint32_t) * hash_size); + + h->hash_mask = hash_size - 1; + h->elts_size = elts_size; + h->elts_count = 0; + h->elts_deleted_count = 0; + + return h; +} + + +njs_flathsh_elt_t * +njs_flathsh_add_elt(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) +{ + njs_int_t cell_num; + njs_flathsh_elt_t *elt, *elts; + njs_flathsh_descr_t *h; + + h = fh->slot; + if (njs_slow_path(h == NULL)) { + return NULL; + } + + if (njs_slow_path(h->elts_count >= h->elts_size)) { + h = njs_expand_elts(fhq, fh->slot, h->elts_count + 1); + if (njs_slow_path(h == NULL)) { + return NULL; + } + + fh->slot = h; + } + + elts = njs_hash_elts(h); + elt = &elts[h->elts_count++]; + + elt->value = fhq->value; + elt->key_hash = fhq->key_hash; + + cell_num = fhq->key_hash & h->hash_mask; + elt->next_elt = njs_hash_cells_end(h)[-cell_num - 1]; + njs_hash_cells_end(h)[-cell_num - 1] = h->elts_count; + + return elt; +} + + +static njs_flathsh_descr_t * +njs_expand_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h, + uint32_t count) +{ + void *chunk; + size_t size; + uint32_t new_elts_size, new_hash_size, new_hash_mask, i; + njs_int_t cell_num; + njs_flathsh_elt_t *elt; + njs_flathsh_descr_t *h_src; + + new_elts_size = h->elts_size * NJS_FLATHSH_ELTS_EXPAND_FACTOR_NUM / + NJS_FLATHSH_ELTS_EXPAND_FACTOR_DENOM; + new_elts_size = njs_max(count, new_elts_size); + + new_hash_size = h->hash_mask + 1; + + while (new_hash_size < new_elts_size) { + new_hash_size = 2 * new_hash_size; + } + + if (new_hash_size != (h->hash_mask + 1)) { + + /* Expand both hash table cells and its elts. */ + + h_src = h; + size = njs_flathsh_chunk_size(new_hash_size, new_elts_size); + chunk = njs_flathsh_malloc(fhq, size); + if (njs_slow_path(chunk == NULL)) { + return NULL; + } + + h = njs_flathsh_descr(chunk, new_hash_size); + + memcpy(h, h_src, sizeof(njs_flathsh_descr_t) + + sizeof(njs_flathsh_elt_t) * h_src->elts_size); + + new_hash_mask = new_hash_size - 1; + h->hash_mask = new_hash_mask; + njs_memzero(chunk, sizeof(uint32_t) * new_hash_size); + + for (i = 0, elt = njs_hash_elts(h); i < h->elts_count; i++, elt++) { + if (elt->value != NULL) { + cell_num = elt->key_hash & new_hash_mask; + elt->next_elt = njs_hash_cells_end(h)[-cell_num - 1]; + njs_hash_cells_end(h)[-cell_num - 1] = i + 1; + } + } + + njs_flathsh_free(fhq, njs_flathsh_chunk(h_src)); + + } else { + + size = njs_flathsh_chunk_size(new_hash_size, new_elts_size); + + /* Expand elts only. */ +#ifdef NJS_FLATHSH_USE_SYSTEM_ALLOCATOR + chunk = realloc(njs_flathsh_chunk(h), size); + if (njs_slow_path(chunk == NULL)) { + return NULL; + } + +#else + chunk = fhq->proto->alloc(fhq->pool, size); + if (njs_slow_path(chunk == NULL)) { + return NULL; + } + + memcpy(chunk, njs_flathsh_chunk(h), + njs_flathsh_chunk_size(h->hash_mask + 1, h->elts_size)); + + fhq->proto->free(fhq->pool, njs_flathsh_chunk(h), 0); +#endif + h = njs_flathsh_descr(chunk, new_hash_size); + } + + h->elts_size = new_elts_size; + + return h; +} + + +njs_int_t +njs_flathsh_find(const njs_flathsh_t *fh, njs_flathsh_query_t *fhq) +{ + njs_int_t cell_num, elt_num; + njs_flathsh_elt_t *e, *elts; + njs_flathsh_descr_t *h; + + h = fh->slot; + if (njs_slow_path(h == NULL)) { + return NJS_DECLINED; + } + + cell_num = fhq->key_hash & h->hash_mask; + elt_num = njs_hash_cells_end(h)[-cell_num - 1]; + elts = njs_hash_elts(h); + + while (elt_num != 0) { + e = &elts[elt_num - 1]; + + /* TODO: need to be replaced by atomic test. */ + + if (e->key_hash == fhq->key_hash && + fhq->proto->test(fhq, e->value) == NJS_OK) + { + fhq->value = e->value; + return NJS_OK; + } + + elt_num = e->next_elt; + } + + return NJS_DECLINED; +} + + +njs_int_t +njs_flathsh_insert(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) +{ + void *tmp; + njs_int_t cell_num, elt_num; + njs_flathsh_elt_t *elt, *elts; + njs_flathsh_descr_t *h; + + h = fh->slot; + + if (h == NULL) { + h = njs_flathsh_new(fhq); + if (h == NULL) { + return NJS_ERROR; + } + + fh->slot = h; + } + + cell_num = fhq->key_hash & h->hash_mask; + elt_num = njs_hash_cells_end(h)[-cell_num - 1]; + elts = njs_hash_elts(h); + + while (elt_num != 0) { + elt = &elts[elt_num - 1]; + + /* TODO: need to be replaced by atomic test. */ + + if (elt->key_hash == fhq->key_hash && + fhq->proto->test(fhq, elt->value) == NJS_OK) + { + if (fhq->replace) { + tmp = fhq->value; + fhq->value = elt->value; + elt->value = tmp; + + return NJS_OK; + + } else { + fhq->value = elt->value; + + return NJS_DECLINED; + } + } + + elt_num = elt->next_elt; + } + + elt = njs_flathsh_add_elt(fh, fhq); + if (elt == NULL) { + return NJS_ERROR; + } + + elt->value = fhq->value; + + return NJS_OK; +} + + +static njs_flathsh_descr_t * +njs_shrink_elts(njs_flathsh_query_t *fhq, njs_flathsh_descr_t *h) +{ + void *chunk; + njs_int_t cell_num; + uint32_t i, j, new_hash_size, new_hash_mask, new_elts_size; + njs_flathsh_elt_t *elt, *elt_src; + njs_flathsh_descr_t *h_src; + + new_elts_size = njs_max(NJS_FLATHSH_ELTS_INITIAL_SIZE, + h->elts_count - h->elts_deleted_count); + + njs_assert(new_elts_size <= h->elts_size); + + new_hash_size = h->hash_mask + 1; + while ((new_hash_size / 2) >= new_elts_size) { + new_hash_size = new_hash_size / 2; + } + + new_hash_mask = new_hash_size - 1; + + h_src = h; + chunk = njs_flathsh_malloc(fhq, njs_flathsh_chunk_size(new_hash_size, + new_elts_size)); + if (njs_slow_path(chunk == NULL)) { + return NULL; + } + + h = njs_flathsh_descr(chunk, new_hash_size); + memcpy(h, h_src, sizeof(njs_flathsh_descr_t)); + + njs_memzero(njs_hash_cells_end(h) - new_hash_size, + sizeof(uint32_t) * new_hash_size); + + elt_src = njs_hash_elts(h_src); + for (i = 0, j = 0, elt = njs_hash_elts(h); i < h->elts_count; i++) { + if (elt_src->value != NULL) { + elt->value = elt_src->value; + elt->key_hash = elt_src->key_hash; + + cell_num = elt_src->key_hash & new_hash_mask; + elt->next_elt = njs_hash_cells_end(h)[-cell_num - 1]; + njs_hash_cells_end(h)[-cell_num - 1] = j + 1; + j++; + elt++; + } + + elt_src++; + } + + njs_assert(j == (h->elts_count - h->elts_deleted_count)); + + h->hash_mask = new_hash_mask; + h->elts_size = new_elts_size; + h->elts_deleted_count = 0; + h->elts_count = j; + + njs_flathsh_free(fhq, njs_flathsh_chunk(h_src)); + + return h; +} + + +njs_int_t +njs_flathsh_delete(njs_flathsh_t *fh, njs_flathsh_query_t *fhq) +{ + njs_int_t cell_num, elt_num; + njs_flathsh_elt_t *elt, *elt_prev, *elts; + njs_flathsh_descr_t *h; + + h = fh->slot; + + if (njs_slow_path(h == NULL)) { + return NJS_DECLINED; + } + + cell_num = fhq->key_hash & h->hash_mask; + elt_num = njs_hash_cells_end(h)[-cell_num - 1]; + elts = njs_hash_elts(h); + elt_prev = NULL; + + while (elt_num != 0) { + elt = &elts[elt_num - 1]; + + /* TODO: use atomic comparision. */ + + if (elt->key_hash == fhq->key_hash && + fhq->proto->test(fhq, elt->value) == NJS_OK) + { + fhq->value = elt->value; + + if (elt_prev != NULL) { + elt_prev->next_elt = elt->next_elt; + + } else { + njs_hash_cells_end(h)[-cell_num - 1] = elt->next_elt; + } + + h->elts_deleted_count++; + + elt->value = NULL; + + /* Shrink elts if elts_deleted_count is eligible. */ + + if (h->elts_deleted_count >= NJS_FLATHSH_ELTS_MINIMUM_TO_SHRINK + && h->elts_deleted_count + >= (h->elts_count / NJS_FLATHSH_ELTS_FRACTION_TO_SHRINK)) + { + h = njs_shrink_elts(fhq, h); + if (njs_slow_path(h == NULL)) { + return NJS_ERROR; + } + + fh->slot = h; + } + + if (h->elts_deleted_count == h->elts_count) { + njs_flathsh_free(fhq, njs_flathsh_chunk(h)); + fh->slot = NULL; + } + + return NJS_OK; + } + + elt_prev = elt; + elt_num = elt->next_elt; + } + + return NJS_DECLINED; +} + + +void * +njs_flathsh_each(const njs_flathsh_t *fh, njs_flathsh_each_t *fhe) +{ + void *v; + njs_flathsh_elt_t *elt; + njs_flathsh_descr_t *h; + + h = fh->slot; + if (njs_slow_path(h == NULL)) { + return NULL; + } + + elt = njs_hash_elts(h); + + while (fhe->cp < h->elts_count) { + v = elt[fhe->cp++].value; + if (v != NULL) { + return v; + } + } + + return NULL; +} diff -r c0f581b26e84 -r c7d2a7846b0b src/njs_flathsh.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/njs_flathsh.h Wed Aug 30 12:06:12 2023 -0700 @@ -0,0 +1,156 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_FLATHSH_H_INCLUDED_ +#define _NJS_FLATHSH_H_INCLUDED_ + + +typedef struct { + void *slot; +} njs_flathsh_t; + + +typedef struct { + uint32_t next_elt; + uint32_t key_hash; + void *value; +} njs_flathsh_elt_t; + + +typedef struct njs_flathsh_descr_s njs_flathsh_descr_t; +typedef struct njs_flathsh_query_s njs_flathsh_query_t; + +typedef njs_int_t (*njs_flathsh_test_t)(njs_flathsh_query_t *fhq, void *data); +typedef void *(*njs_flathsh_alloc_t)(void *ctx, size_t size); +typedef void (*njs_flathsh_free_t)(void *ctx, void *p, size_t size); + +typedef struct njs_flathsh_proto_s njs_flathsh_proto_t; + + +struct njs_flathsh_proto_s { + uint32_t not_used; + njs_flathsh_test_t test; + njs_flathsh_alloc_t alloc; + njs_flathsh_free_t free; +}; + + +struct njs_flathsh_query_s { + uint32_t key_hash; + njs_str_t key; + + uint8_t replace; /* 1 bit */ + void *value; + + const njs_flathsh_proto_t *proto; + void *pool; + + /* Opaque data passed for the test function. */ + void *data; +}; + + +#define njs_flathsh_is_empty(fh) \ + ((fh)->slot == NULL) + + +#define njs_flathsh_init(fh) \ + (fh)->slot = NULL + + +#define njs_flathsh_eq(fhl, fhr) \ + ((fhl)->slot == (fhr)->slot) + + +/* + * njs_flathsh_find() finds a hash element. If the element has been + * found then it is stored in the fhq->value and njs_flathsh_find() + * returns NJS_OK. Otherwise NJS_DECLINED is returned. + * + * The required njs_flathsh_query_t fields: key_hash, key, proto. + */ +NJS_EXPORT njs_int_t njs_flathsh_find(const njs_flathsh_t *fh, + njs_flathsh_query_t *fhq); + +/* + * njs_flathsh_insert() adds a hash element. If the element is already + * present in flathsh and the fhq->replace flag is zero, then fhq->value + * is updated with the old element and NJS_DECLINED is returned. + * If the element is already present in flathsh and the fhq->replace flag + * is non-zero, then the old element is replaced with the new element. + * fhq->value is updated with the old element, and NJS_OK is returned. + * If the element is not present in flathsh, then it is inserted and + * NJS_OK is returned. The fhq->value is not changed. + * On memory allocation failure NJS_ERROR is returned. + * + * The required njs_flathsh_query_t fields: key_hash, key, proto, replace, + * value. + * The optional njs_flathsh_query_t fields: pool. + */ +NJS_EXPORT njs_int_t njs_flathsh_insert(njs_flathsh_t *fh, + njs_flathsh_query_t *fhq); + +/* + * njs_flathsh_delete() deletes a hash element. If the element has been + * found then it is removed from flathsh and is stored in the fhq->value, + * and NJS_OK is returned. Otherwise NJS_DECLINED is returned. + * + * The required njs_flathsh_query_t fields: key_hash, key, proto. + * The optional njs_flathsh_query_t fields: pool. + */ +NJS_EXPORT njs_int_t njs_flathsh_delete(njs_flathsh_t *fh, + njs_flathsh_query_t *fhq); + + +typedef struct { + const njs_flathsh_proto_t *proto; + uint32_t key_hash; + uint32_t cp; +} njs_flathsh_each_t; + _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel