/* * Copyright (C) Igor Sysoev * Copyright (C) NGINX, Inc. */ #include /* * The level hash consists of hierarchical levels of arrays of pointers. * The pointers may point to another level, a bucket, or NULL. * The levels and buckets must be allocated in manner alike posix_memalign() * to bookkeep additional information in pointer low bits. * * A level is an array of pointers. Its size is a power of 2. Levels * may be different sizes, but on the same level the sizes are the same. * Level sizes are specified by number of bits per level in lvlhsh->shift * array. A hash may have up to 7 levels. There are two predefined * shift arrays given by the first two shift array values: * * 1) [0, 0]: [4, 4, 4, 4, 4, 4, 4] on a 64-bit platform or * [5, 5, 5, 5, 5, 5, 0] on a 32-bit platform, * so default size of levels is 128 bytes. * * 2) [0, 10]: [10, 4, 4, 4, 4, 4, 0] on a 64-bit platform or * [10, 5, 5, 5, 5, 0, 0] on a 32-bit platform, * so default size of levels is 128 bytes on all levels except * the first level. The first level is 8K or 4K on 64-bit or 32-bit * platforms respectively. * * All buckets in a hash are the same size which is a power of 2. * A bucket contains several entries stored and tested sequentially. * The bucket size should be one or two CPU cache line size, a minimum * allowed size is 32 bytes. A default 128-byte bucket contains 10 64-bit * entries or 15 32-bit entries. Each entry consists of pointer to value * data and 32-bit key. If an entry value pointer is NULL, the entry is free. * On a 64-bit platform entry value pointers are no aligned, therefore they * are accessed as two 32-bit integers. The rest trailing space in a bucket * is used as pointer to next bucket and this pointer is always aligned. * Although the level hash allows to store a lot of values in a bucket chain, * this is non optimal way. The large data set should be stored using * several levels. */ #define nxt_lvlhsh_is_bucket(p) \ ((uintptr_t) (p) & 1) #define nxt_lvlhsh_count_inc(n) \ n = (void *) ((uintptr_t) (n) + 2) #define nxt_lvlhsh_count_dec(n) \ n = (void *) ((uintptr_t) (n) - 2) #define nxt_lvlhsh_level_size(proto, nlvl) \ ((uintptr_t) 1 << proto->shift[nlvl]) #define nxt_lvlhsh_level(lvl, mask) \ (void **) ((uintptr_t) lvl & (~mask << 2)) #define nxt_lvlhsh_level_entries(lvl, mask) \ ((uintptr_t) lvl & (mask << 1)) #define nxt_lvlhsh_store_bucket(slot, bkt) \ slot = (void **) ((uintptr_t) bkt | 2 | 1) #define nxt_lvlhsh_bucket_size(proto) \ proto->bucket_size #define nxt_lvlhsh_bucket(proto, bkt) \ (uint32_t *) ((uintptr_t) bkt & ~(uintptr_t) proto->bucket_mask) #define nxt_lvlhsh_bucket_entries(proto, bkt) \ (((uintptr_t) bkt & (uintptr_t) proto->bucket_mask) >> 1) #define nxt_lvlhsh_bucket_end(proto, bkt) \ &bkt[proto->bucket_end] #define nxt_lvlhsh_free_entry(e) \ (!(nxt_lvlhsh_valid_entry(e))) #define nxt_lvlhsh_next_bucket(proto, bkt) \ ((void **) &bkt[proto->bucket_end]) #if (NXT_64BIT) #define nxt_lvlhsh_valid_entry(e) \ (((e)[0] | (e)[1]) != 0) #define nxt_lvlhsh_entry_value(e) \ (void *) (((uintptr_t) (e)[1] << 32) + (e)[0]) #define nxt_lvlhsh_set_entry_value(e, n) \ (e)[0] = (uint32_t) (uintptr_t) n; \ (e)[1] = (uint32_t) ((uintptr_t) n >> 32) #define nxt_lvlhsh_entry_key(e) \ (e)[2] #define nxt_lvlhsh_set_entry_key(e, n) \ (e)[2] = n #else #define nxt_lvlhsh_valid_entry(e) \ ((e)[0] != 0) #define nxt_lvlhsh_entry_value(e) \ (void *) (e)[0] #define nxt_lvlhsh_set_entry_value(e, n) \ (e)[0] = (uint32_t) n #define nxt_lvlhsh_entry_key(e) \ (e)[1] #define nxt_lvlhsh_set_entry_key(e, n) \ (e)[1] = n #endif #define NXT_LVLHSH_BUCKET_DONE ((void *) -1) typedef struct { const nxt_lvlhsh_proto_t *proto; void *pool; uint32_t retrieve; /* 1 bit */ } nxt_lvlhsh_peek_t; static nxt_int_t nxt_lvlhsh_level_find(nxt_lvlhsh_query_t *lhq, void **lvl, uint32_t key, nxt_uint_t nlvl); static nxt_int_t nxt_lvlhsh_bucket_find(nxt_lvlhsh_query_t *lhq, void **bkt); static nxt_int_t nxt_lvlhsh_new_bucket(nxt_lvlhsh_query_t *lhq, void **slot); static nxt_int_t nxt_lvlhsh_level_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_uint_t nlvl); static nxt_int_t nxt_lvlhsh_bucket_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_int_t nlvl); static nxt_int_t nxt_lvlhsh_convert_bucket_to_level(nxt_lvlhsh_query_t *lhq, void **slot, nxt_uint_t nlvl, uint32_t *bucket); static nxt_int_t nxt_lvlhsh_level_convertion_insert(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key, nxt_uint_t nlvl); static nxt_int_t nxt_lvlhsh_bucket_convertion_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_int_t nlvl); static nxt_int_t nxt_lvlhsh_free_level(nxt_lvlhsh_query_t *lhq, void **level, nxt_uint_t size); static nxt_int_t nxt_lvlhsh_level_delete(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_uint_t nlvl); static nxt_int_t nxt_lvlhsh_bucket_delete(nxt_lvlhsh_query_t *lhq, void **bkt); static void *nxt_lvlhsh_level_each(nxt_lvlhsh_each_t *lhe, void **level, nxt_uint_t nlvl, nxt_uint_t shift); static void *nxt_lvlhsh_bucket_each(nxt_lvlhsh_each_t *lhe); static void *nxt_lvlhsh_level_peek(nxt_lvlhsh_peek_t *peek, void **level, nxt_uint_t nlvl); static void *nxt_lvlhsh_bucket_peek(nxt_lvlhsh_peek_t *peek, void **bkt); nxt_int_t nxt_lvlhsh_find(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq) { void *slot; slot = lh->slot; if (nxt_fast_path(slot != NULL)) { if (nxt_lvlhsh_is_bucket(slot)) { return nxt_lvlhsh_bucket_find(lhq, slot); } return nxt_lvlhsh_level_find(lhq, slot, lhq->key_hash, 0); } return NXT_DECLINED; } static nxt_int_t nxt_lvlhsh_level_find(nxt_lvlhsh_query_t *lhq, void **lvl, uint32_t key, nxt_uint_t nlvl) { void **slot; uintptr_t mask; nxt_uint_t shift; shift = lhq->proto->shift[nlvl]; mask = ((uintptr_t) 1 << shift) - 1; lvl = nxt_lvlhsh_level(lvl, mask); slot = lvl[key & mask]; if (slot != NULL) { if (nxt_lvlhsh_is_bucket(slot)) { return nxt_lvlhsh_bucket_find(lhq, slot); } return nxt_lvlhsh_level_find(lhq, slot, key >> shift, nlvl + 1); } return NXT_DECLINED; } static nxt_int_t nxt_lvlhsh_bucket_find(nxt_lvlhsh_query_t *lhq, void **bkt) { void *value; uint32_t *bucket, *e; nxt_uint_t n; do { bucket = nxt_lvlhsh_bucket(lhq->proto, bkt); n = nxt_lvlhsh_bucket_entries(lhq->proto, bkt); e = bucket; do { if (nxt_lvlhsh_valid_entry(e)) { n--; if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) { value = nxt_lvlhsh_entry_value(e); if (lhq->proto->test(lhq, value) == NXT_OK) { lhq->value = value; return NXT_OK; } } } e += NXT_LVLHSH_ENTRY_SIZE; } while (n != 0); bkt = *nxt_lvlhsh_next_bucket(lhq->proto, bucket); } while (bkt != NULL); return NXT_DECLINED; } nxt_int_t nxt_lvlhsh_insert(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq) { uint32_t key; if (nxt_fast_path(lh->slot != NULL)) { key = lhq->key_hash; if (nxt_lvlhsh_is_bucket(lh->slot)) { return nxt_lvlhsh_bucket_insert(lhq, &lh->slot, key, -1); } return nxt_lvlhsh_level_insert(lhq, &lh->slot, key, 0); } return nxt_lvlhsh_new_bucket(lhq, &lh->slot); } static nxt_int_t nxt_lvlhsh_new_bucket(nxt_lvlhsh_query_t *lhq, void **slot) { uint32_t *bucket; bucket = lhq->proto->alloc(lhq->pool, nxt_lvlhsh_bucket_size(lhq->proto)); if (nxt_fast_path(bucket != NULL)) { nxt_lvlhsh_set_entry_value(bucket, lhq->value); nxt_lvlhsh_set_entry_key(bucket, lhq->key_hash); *nxt_lvlhsh_next_bucket(lhq->proto, bucket) = NULL; nxt_lvlhsh_store_bucket(*slot, bucket); return NXT_OK; } return NXT_ERROR; } static nxt_int_t nxt_lvlhsh_level_insert(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key, nxt_uint_t nlvl) { void **slot, **lvl; nxt_int_t ret; uintptr_t mask; nxt_uint_t shift; shift = lhq->proto->shift[nlvl]; mask = ((uintptr_t) 1 << shift) - 1; lvl = nxt_lvlhsh_level(*parent, mask); slot = &lvl[key & mask]; if (*slot != NULL) { key >>= shift; if (nxt_lvlhsh_is_bucket(*slot)) { return nxt_lvlhsh_bucket_insert(lhq, slot, key, nlvl); } return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl + 1); } ret = nxt_lvlhsh_new_bucket(lhq, slot); if (nxt_fast_path(ret == NXT_OK)) { nxt_lvlhsh_count_inc(*parent); } return ret; } static nxt_int_t nxt_lvlhsh_bucket_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_int_t nlvl) { void **bkt, **vacant_bucket, *value; uint32_t *bucket, *e, *vacant_entry; nxt_int_t ret; uintptr_t n; const void *new_value; const nxt_lvlhsh_proto_t *proto; bkt = slot; vacant_entry = NULL; vacant_bucket = NULL; proto = lhq->proto; /* Search for duplicate entry in bucket chain. */ do { bucket = nxt_lvlhsh_bucket(proto, *bkt); n = nxt_lvlhsh_bucket_entries(proto, *bkt); e = bucket; do { if (nxt_lvlhsh_valid_entry(e)) { if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) { value = nxt_lvlhsh_entry_value(e); if (proto->test(lhq, value) == NXT_OK) { new_value = lhq->value; lhq->value = value; if (lhq->replace) { nxt_lvlhsh_set_entry_value(e, new_value); return NXT_OK; } return NXT_DECLINED; } } n--; } else { /* * Save a hole vacant position in bucket * and continue to search for duplicate entry. */ if (vacant_entry == NULL) { vacant_entry = e; vacant_bucket = bkt; } } e += NXT_LVLHSH_ENTRY_SIZE; } while (n != 0); if (e < nxt_lvlhsh_bucket_end(proto, bucket)) { /* * Save a vacant position on incomplete bucket's end * and continue to search for duplicate entry. */ if (vacant_entry == NULL) { vacant_entry = e; vacant_bucket = bkt; } } bkt = nxt_lvlhsh_next_bucket(proto, bucket); } while (*bkt != NULL); if (vacant_entry != NULL) { nxt_lvlhsh_set_entry_value(vacant_entry, lhq->value); nxt_lvlhsh_set_entry_key(vacant_entry, lhq->key_hash); nxt_lvlhsh_count_inc(*vacant_bucket); return NXT_OK; } /* All buckets are full. */ nlvl++; if (nxt_fast_path(proto->shift[nlvl] != 0)) { ret = nxt_lvlhsh_convert_bucket_to_level(lhq, slot, nlvl, bucket); if (nxt_fast_path(ret == NXT_OK)) { return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl); } return ret; } /* The last allowed level, only buckets may be allocated here. */ return nxt_lvlhsh_new_bucket(lhq, bkt); } static nxt_int_t nxt_lvlhsh_convert_bucket_to_level(nxt_lvlhsh_query_t *lhq, void **slot, nxt_uint_t nlvl, uint32_t *bucket) { void *lvl, **level; uint32_t *e, *end, key; nxt_int_t ret; nxt_uint_t i, shift, size; nxt_lvlhsh_query_t q; const nxt_lvlhsh_proto_t *proto; proto = lhq->proto; size = nxt_lvlhsh_level_size(proto, nlvl); lvl = proto->alloc(lhq->pool, size * (sizeof(void *))); if (nxt_slow_path(lvl == NULL)) { return NXT_ERROR; } nxt_memzero(lvl, size * (sizeof(void *))); level = lvl; shift = 0; for (i = 0; i < nlvl; i++) { /* * Using SIMD operations in this trivial loop with maximum * 8 iterations may increase code size by 170 bytes. */ nxt_pragma_loop_disable_vectorization; shift += proto->shift[i]; } end = nxt_lvlhsh_bucket_end(proto, bucket); for (e = bucket; e < end; e += NXT_LVLHSH_ENTRY_SIZE) { q.proto = proto; q.pool = lhq->pool; q.value = nxt_lvlhsh_entry_value(e); key = nxt_lvlhsh_entry_key(e); q.key_hash = key; ret = nxt_lvlhsh_level_convertion_insert(&q, &lvl, key >> shift, nlvl); if (nxt_slow_path(ret != NXT_OK)) { return nxt_lvlhsh_free_level(lhq, level, size); } } *slot = lvl; proto->free(lhq->pool, bucket); return NXT_OK; } static nxt_int_t nxt_lvlhsh_level_convertion_insert(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key, nxt_uint_t nlvl) { void **slot, **lvl; nxt_int_t ret; uintptr_t mask; nxt_uint_t shift; shift = lhq->proto->shift[nlvl]; mask = ((uintptr_t) 1 << shift) - 1; lvl = nxt_lvlhsh_level(*parent, mask); slot = &lvl[key & mask]; if (*slot == NULL) { ret = nxt_lvlhsh_new_bucket(lhq, slot); if (nxt_fast_path(ret == NXT_OK)) { nxt_lvlhsh_count_inc(*parent); } return ret; } /* Only backets can be here. */ return nxt_lvlhsh_bucket_convertion_insert(lhq, slot, key >> shift, nlvl); } /* * The special bucket insertion procedure is required because during * convertion lhq->key contains garbage values and the test function * cannot be called. Besides, the procedure can be simpler because * a new entry is inserted just after occupied entries. */ static nxt_int_t nxt_lvlhsh_bucket_convertion_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key, nxt_int_t nlvl) { void **bkt; uint32_t *bucket, *e; nxt_int_t ret; uintptr_t n; const nxt_lvlhsh_proto_t *proto; bkt = slot; proto = lhq->proto; do { bucket = nxt_lvlhsh_bucket(proto, *bkt); n = nxt_lvlhsh_bucket_entries(proto, *bkt); e = bucket + n * NXT_LVLHSH_ENTRY_SIZE; if (nxt_fast_path(e < nxt_lvlhsh_bucket_end(proto, bucket))) { nxt_lvlhsh_set_entry_value(e, lhq->value); nxt_lvlhsh_set_entry_key(e, lhq->key_hash); nxt_lvlhsh_count_inc(*bkt); return NXT_OK; } bkt = nxt_lvlhsh_next_bucket(proto, bucket); } while (*bkt != NULL); /* All buckets are full. */ nlvl++; if (nxt_fast_path(proto->shift[nlvl] != 0)) { ret = nxt_lvlhsh_convert_bucket_to_level(lhq, slot, nlvl, bucket); if (nxt_fast_path(ret == NXT_OK)) { return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl); } return ret; } /* The last allowed level, only buckets may be allocated here. */ return nxt_lvlhsh_new_bucket(lhq, bkt); } static nxt_int_t nxt_lvlhsh_free_level(nxt_lvlhsh_query_t *lhq, void **level, nxt_uint_t size) { nxt_uint_t i; const nxt_lvlhsh_proto_t *proto; proto = lhq->proto; for (i = 0; i < size; i++) { if (level[i] != NULL) { /* * Chained buckets are not possible here, since even * in the worst case one bucket cannot be converted * in two chained buckets but remains the same bucket. */ proto->free(lhq->pool, nxt_lvlhsh_bucket(proto, level[i])); } } proto->free(lhq->pool, level); return NXT_ERROR; } nxt_int_t nxt_lvlhsh_delete(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq) { if (nxt_fast_path(lh->slot != NULL)) { if (nxt_lvlhsh_is_bucket(lh->slot)) { return nxt_lvlhsh_bucket_delete(lhq, &lh->slot); } return nxt_lvlhsh_level_delete(lhq, &lh->slot, lhq->key_hash, 0); } return NXT_DECLINED; } static nxt_int_t nxt_lvlhsh_level_delete(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key, nxt_uint_t nlvl) { void **slot, **lvl; uintptr_t mask; nxt_int_t ret; nxt_uint_t shift; shift = lhq->proto->shift[nlvl]; mask = ((uintptr_t) 1 << shift) - 1; lvl = nxt_lvlhsh_level(*parent, mask); slot = &lvl[key & mask]; if (*slot != NULL) { if (nxt_lvlhsh_is_bucket(*slot)) { ret = nxt_lvlhsh_bucket_delete(lhq, slot); } else { key >>= shift; ret = nxt_lvlhsh_level_delete(lhq, slot, key, nlvl + 1); } if (*slot == NULL) { nxt_lvlhsh_count_dec(*parent); if (nxt_lvlhsh_level_entries(*parent, mask) == 0) { *parent = NULL; lhq->proto->free(lhq->pool, lvl); } } return ret; } return NXT_DECLINED; } static nxt_int_t nxt_lvlhsh_bucket_delete(nxt_lvlhsh_query_t *lhq, void **bkt) { void *value; uint32_t *bucket, *e; uintptr_t n; const nxt_lvlhsh_proto_t *proto; proto = lhq->proto; do { bucket = nxt_lvlhsh_bucket(proto, *bkt); n = nxt_lvlhsh_bucket_entries(proto, *bkt); e = bucket; do { if (nxt_lvlhsh_valid_entry(e)) { if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) { value = nxt_lvlhsh_entry_value(e); if (proto->test(lhq, value) == NXT_OK) { if (nxt_lvlhsh_bucket_entries(proto, *bkt) == 1) { *bkt = *nxt_lvlhsh_next_bucket(proto, bucket); proto->free(lhq->pool, bucket); } else { nxt_lvlhsh_count_dec(*bkt); nxt_lvlhsh_set_entry_value(e, NULL); } lhq->value = value; return NXT_OK; } } n--; } e += NXT_LVLHSH_ENTRY_SIZE; } while (n != 0); bkt = nxt_lvlhsh_next_bucket(proto, bucket); } while (*bkt != NULL); return NXT_DECLINED; } void * nxt_lvlhsh_each(nxt_lvlhsh_t *lh, nxt_lvlhsh_each_t *lhe) { void **slot; if (lhe->bucket == NXT_LVLHSH_BUCKET_DONE) { slot = lh->slot; if (nxt_lvlhsh_is_bucket(slot)) { return NULL; } } else { if (nxt_slow_path(lhe->bucket == NULL)) { /* The first iteration only. */ slot = lh->slot; if (slot == NULL) { return NULL; } if (!nxt_lvlhsh_is_bucket(slot)) { lhe->current = 0; goto level; } lhe->bucket = nxt_lvlhsh_bucket(lhe->proto, slot); lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, slot); lhe->entry = 0; } return nxt_lvlhsh_bucket_each(lhe); } level: return nxt_lvlhsh_level_each(lhe, slot, 0, 0); } static void * nxt_lvlhsh_level_each(nxt_lvlhsh_each_t *lhe, void **level, nxt_uint_t nlvl, nxt_uint_t shift) { void **slot, *value; uintptr_t mask; nxt_uint_t n, level_shift; level_shift = lhe->proto->shift[nlvl]; mask = ((uintptr_t) 1 << level_shift) - 1; level = nxt_lvlhsh_level(level, mask); do { n = (lhe->current >> shift) & mask; slot = level[n]; if (slot != NULL) { if (nxt_lvlhsh_is_bucket(slot)) { if (lhe->bucket != NXT_LVLHSH_BUCKET_DONE) { lhe->bucket = nxt_lvlhsh_bucket(lhe->proto, slot); lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, slot); lhe->entry = 0; return nxt_lvlhsh_bucket_each(lhe); } lhe->bucket = NULL; } else { value = nxt_lvlhsh_level_each(lhe, slot, nlvl + 1, shift + level_shift); if (value != NULL) { return value; } } } lhe->current &= ~(mask << shift); n = ((n + 1) & mask) << shift; lhe->current |= n; } while (n != 0); return NULL; } static nxt_noinline void * nxt_lvlhsh_bucket_each(nxt_lvlhsh_each_t *lhe) { void *value, **next; uint32_t *bucket; /* At least one valid entry must present here. */ do { bucket = &lhe->bucket[lhe->entry]; lhe->entry += NXT_LVLHSH_ENTRY_SIZE; } while (nxt_lvlhsh_free_entry(bucket)); value = nxt_lvlhsh_entry_value(bucket); lhe->entries--; if (lhe->entries == 0) { next = *nxt_lvlhsh_next_bucket(lhe->proto, lhe->bucket); lhe->bucket = (next == NULL) ? NXT_LVLHSH_BUCKET_DONE : nxt_lvlhsh_bucket(lhe->proto, next); lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, next); lhe->entry = 0; } return value; } void * nxt_lvlhsh_peek(nxt_lvlhsh_t *lh, const nxt_lvlhsh_proto_t *proto) { void **slot; nxt_lvlhsh_peek_t peek; slot = lh->slot; if (slot != NULL) { peek.proto = proto; peek.retrieve = 0; if (nxt_lvlhsh_is_bucket(slot)) { return nxt_lvlhsh_bucket_peek(&peek, &lh->slot); } return nxt_lvlhsh_level_peek(&peek, &lh->slot, 0); } return NULL; } static void * nxt_lvlhsh_level_peek(nxt_lvlhsh_peek_t *peek, void **parent, nxt_uint_t nlvl) { void **slot, **level, *value; uintptr_t mask; nxt_uint_t n, shift; shift = peek->proto->shift[nlvl]; mask = ((uintptr_t) 1 << shift) - 1; level = nxt_lvlhsh_level(*parent, mask); n = 0; /* At least one valid level slot must present here. */ for ( ;; ) { slot = &level[n]; if (*slot != NULL) { if (nxt_lvlhsh_is_bucket(*slot)) { value = nxt_lvlhsh_bucket_peek(peek, slot); } else { value = nxt_lvlhsh_level_peek(peek, slot, nlvl + 1); } /* * Checking peek->retrieve is not required here because * there can not be empty slots during peeking. */ if (*slot == NULL) { nxt_lvlhsh_count_dec(*parent); if (nxt_lvlhsh_level_entries(*parent, mask) == 0) { *parent = NULL; peek->proto->free(peek->pool, level); } } return value; } n++; } } static nxt_noinline void * nxt_lvlhsh_bucket_peek(nxt_lvlhsh_peek_t *peek, void **bkt) { void *value; uint32_t *bucket, *entry; const nxt_lvlhsh_proto_t *proto; bucket = nxt_lvlhsh_bucket(peek->proto, *bkt); /* At least one valid entry must present here. */ for (entry = bucket; nxt_lvlhsh_free_entry(entry); entry += NXT_LVLHSH_ENTRY_SIZE) { /* void */ } value = nxt_lvlhsh_entry_value(entry); if (peek->retrieve) { proto = peek->proto; if (nxt_lvlhsh_bucket_entries(proto, *bkt) == 1) { *bkt = *nxt_lvlhsh_next_bucket(proto, bucket); proto->free(peek->pool, bucket); } else { nxt_lvlhsh_count_dec(*bkt); nxt_lvlhsh_set_entry_value(entry, NULL); } } return value; } void * nxt_lvlhsh_retrieve(nxt_lvlhsh_t *lh, const nxt_lvlhsh_proto_t *proto, void *pool) { void **slot; nxt_lvlhsh_peek_t peek; slot = lh->slot; if (slot != NULL) { peek.proto = proto; peek.pool = pool; peek.retrieve = 1; if (nxt_lvlhsh_is_bucket(slot)) { return nxt_lvlhsh_bucket_peek(&peek, &lh->slot); } return nxt_lvlhsh_level_peek(&peek, &lh->slot, 0); } return NULL; }