We had a mix of styles for declaring function-like macros:
Style A:
#define \
foo() \
do { \
... \
} while (0)
Style B:
#define foo() \
do { \
... \
} while (0)
We had a similar number of occurences of each style:
$ grep -rnI '^\w*(.*\\' | wc -l
244
$ grep -rn 'define.*(.*)' | wc -l
239
(Those regexes aren't perfect, but a very decent approximation.)
Real examples:
$ find src -type f | xargs sed -n '/^nxt_double_is_zero/,/^$/p'
nxt_double_is_zero(f) \
(fabs(f) <= FLT_EPSILON)
$ find src -type f | xargs sed -n '/define nxt_http_field_set/,/^$/p'
#define nxt_http_field_set(_field, _name, _value) \
do { \
(_field)->name_length = nxt_length(_name); \
(_field)->value_length = nxt_length(_value); \
(_field)->name = (u_char *) _name; \
(_field)->value = (u_char *) _value; \
} while (0)
I'd like to standardize on a single style for them, and IMO,
having the identifier in the same line as #define is a better
option for the following reasons:
- Programmers are used to `#define foo() ...` (readability).
- One less line of code.
- The program for finding them is really simple (see below).
function grep_ngx_func()
{
if (($# != 1)); then
>&2 echo "Usage: ${FUNCNAME[0]} <func>";
return 1;
fi;
find src -type f \
| grep '\.[ch]$' \
| xargs grep -l "$1" \
| sort \
| xargs pcregrep -Mn "(?s)^\$[\w\s*]+?^$1\(.*?^}";
find src -type f \
| grep '\.[ch]$' \
| xargs grep -l "$1" \
| sort \
| xargs pcregrep -Mn "(?s)define $1\(.*?^$" \
| sed -E '1s/^[^:]+:[0-9]+:/&\n\n/';
}
$ grep_ngx_func
Usage: grep_ngx_func <func>
$ grep_ngx_func nxt_http_field_set
src/nxt_http.h:98:
#define nxt_http_field_set(_field, _name, _value) \
do { \
(_field)->name_length = nxt_length(_name); \
(_field)->value_length = nxt_length(_value); \
(_field)->name = (u_char *) _name; \
(_field)->value = (u_char *) _value; \
} while (0)
$ grep_ngx_func nxt_sprintf
src/nxt_sprintf.c:56:
u_char * nxt_cdecl
nxt_sprintf(u_char *buf, u_char *end, const char *fmt, ...)
{
u_char *p;
va_list args;
va_start(args, fmt);
p = nxt_vsprintf(buf, end, fmt, args);
va_end(args);
return p;
}
................
Scripted change:
................
$ find src -type f \
| grep '\.[ch]$' \
| xargs sed -i '/define *\\$/{N;s/ *\\\n/ /;s/ //}'
995 lines
25 KiB
C
995 lines
25 KiB
C
|
|
/*
|
|
* Copyright (C) Igor Sysoev
|
|
* Copyright (C) NGINX, Inc.
|
|
*/
|
|
|
|
#include <nxt_main.h>
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|