Type: fix Change-Id: If59a66aae658dd35dbcb4987ab00c306b3c6e2e2 Signed-off-by: Damjan Marion <damarion@cisco.com>
1165 lines
25 KiB
C
1165 lines
25 KiB
C
/*
|
|
* Copyright (c) 2015 Cisco and/or its affiliates.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
/*
|
|
Copyright (c) 2001-2005 Eliot Dresselhaus
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining
|
|
a copy of this software and associated documentation files (the
|
|
"Software"), to deal in the Software without restriction, including
|
|
without limitation the rights to use, copy, modify, merge, publish,
|
|
distribute, sublicense, and/or sell copies of the Software, and to
|
|
permit persons to whom the Software is furnished to do so, subject to
|
|
the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be
|
|
included in all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <vppinfra/hash.h>
|
|
#include <vppinfra/error.h>
|
|
#include <vppinfra/mem.h>
|
|
#include <vppinfra/byte_order.h> /* for clib_arch_is_big_endian */
|
|
|
|
always_inline void
|
|
zero_pair (hash_t * h, hash_pair_t * p)
|
|
{
|
|
clib_memset (p, 0, hash_pair_bytes (h));
|
|
}
|
|
|
|
always_inline void
|
|
init_pair (hash_t * h, hash_pair_t * p)
|
|
{
|
|
clib_memset (p->value, ~0, hash_value_bytes (h));
|
|
}
|
|
|
|
always_inline hash_pair_union_t *
|
|
get_pair (void *v, uword i)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *p;
|
|
ASSERT (i < vec_len (v));
|
|
p = v;
|
|
p += i << h->log2_pair_size;
|
|
return (hash_pair_union_t *) p;
|
|
}
|
|
|
|
always_inline void
|
|
set_is_user (void *v, uword i, uword is_user)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
uword i0 = i / BITS (h->is_user[0]);
|
|
uword i1 = (uword) 1 << (i % BITS (h->is_user[0]));
|
|
if (is_user)
|
|
h->is_user[i0] |= i1;
|
|
else
|
|
h->is_user[i0] &= ~i1;
|
|
}
|
|
|
|
static u8 *hash_format_pair_default (u8 * s, va_list * args);
|
|
|
|
#if uword_bits == 64
|
|
|
|
static inline u64
|
|
zap64 (u64 x, word n)
|
|
{
|
|
#define _(n) (((u64) 1 << (u64) (8*(n))) - (u64) 1)
|
|
static u64 masks_little_endian[] = {
|
|
0, _(1), _(2), _(3), _(4), _(5), _(6), _(7),
|
|
};
|
|
static u64 masks_big_endian[] = {
|
|
0, ~_(7), ~_(6), ~_(5), ~_(4), ~_(3), ~_(2), ~_(1),
|
|
};
|
|
#undef _
|
|
if (clib_arch_is_big_endian)
|
|
return x & masks_big_endian[n];
|
|
else
|
|
return x & masks_little_endian[n];
|
|
}
|
|
|
|
/**
|
|
* make address-sanitizer skip this:
|
|
* clib_mem_unaligned + zap64 casts its input as u64, computes a mask
|
|
* according to the input length, and returns the casted maked value.
|
|
* Therefore all the 8 Bytes of the u64 are systematically read, which
|
|
* rightfully causes address-sanitizer to raise an error on smaller inputs.
|
|
*
|
|
* However the invalid Bytes are discarded within zap64(), which is why
|
|
* this can be silenced safely.
|
|
*
|
|
* The above is true *unless* the extra bytes cross a page boundary
|
|
* into unmapped or no-access space, hence the boundary crossing check.
|
|
*/
|
|
static inline u64
|
|
hash_memory64 (void *p, word n_bytes, u64 state)
|
|
{
|
|
u64 *q = p;
|
|
u64 a, b, c, n;
|
|
int page_boundary_crossing;
|
|
u64 start_addr, end_addr;
|
|
union
|
|
{
|
|
u8 as_u8[8];
|
|
u64 as_u64;
|
|
} tmp;
|
|
|
|
/*
|
|
* If the request crosses a 4k boundary, it's not OK to assume
|
|
* that the zap64 game is safe. 4k is the minimum known page size.
|
|
*/
|
|
start_addr = (u64) p;
|
|
end_addr = start_addr + n_bytes + 7;
|
|
page_boundary_crossing = (start_addr >> 12) != (end_addr >> 12);
|
|
|
|
a = b = 0x9e3779b97f4a7c13LL;
|
|
c = state;
|
|
n = n_bytes;
|
|
|
|
while (n >= 3 * sizeof (u64))
|
|
{
|
|
a += clib_mem_unaligned (q + 0, u64);
|
|
b += clib_mem_unaligned (q + 1, u64);
|
|
c += clib_mem_unaligned (q + 2, u64);
|
|
hash_mix64 (a, b, c);
|
|
n -= 3 * sizeof (u64);
|
|
q += 3;
|
|
}
|
|
|
|
c += n_bytes;
|
|
switch (n / sizeof (u64))
|
|
{
|
|
case 2:
|
|
a += clib_mem_unaligned (q + 0, u64);
|
|
b += clib_mem_unaligned (q + 1, u64);
|
|
if (n % sizeof (u64))
|
|
{
|
|
if (PREDICT_TRUE (page_boundary_crossing == 0))
|
|
c +=
|
|
zap64 (CLIB_MEM_OVERFLOW
|
|
(clib_mem_unaligned (q + 2, u64), q + 2, sizeof (u64)),
|
|
n % sizeof (u64)) << 8;
|
|
else
|
|
{
|
|
clib_memcpy_fast (tmp.as_u8, q + 2, n % sizeof (u64));
|
|
c += zap64 (tmp.as_u64, n % sizeof (u64)) << 8;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
a += clib_mem_unaligned (q + 0, u64);
|
|
if (n % sizeof (u64))
|
|
{
|
|
if (PREDICT_TRUE (page_boundary_crossing == 0))
|
|
b +=
|
|
zap64 (CLIB_MEM_OVERFLOW
|
|
(clib_mem_unaligned (q + 1, u64), q + 1, sizeof (u64)),
|
|
n % sizeof (u64));
|
|
else
|
|
{
|
|
clib_memcpy_fast (tmp.as_u8, q + 1, n % sizeof (u64));
|
|
b += zap64 (tmp.as_u64, n % sizeof (u64));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0:
|
|
if (n % sizeof (u64))
|
|
{
|
|
if (PREDICT_TRUE (page_boundary_crossing == 0))
|
|
a +=
|
|
zap64 (CLIB_MEM_OVERFLOW
|
|
(clib_mem_unaligned (q + 0, u64), q + 0, sizeof (u64)),
|
|
n % sizeof (u64));
|
|
else
|
|
{
|
|
clib_memcpy_fast (tmp.as_u8, q, n % sizeof (u64));
|
|
a += zap64 (tmp.as_u64, n % sizeof (u64));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
hash_mix64 (a, b, c);
|
|
|
|
return c;
|
|
}
|
|
|
|
#else /* if uword_bits == 64 */
|
|
|
|
static inline u32
|
|
zap32 (u32 x, word n)
|
|
{
|
|
#define _(n) (((u32) 1 << (u32) (8*(n))) - (u32) 1)
|
|
static u32 masks_little_endian[] = {
|
|
0, _(1), _(2), _(3),
|
|
};
|
|
static u32 masks_big_endian[] = {
|
|
0, ~_(3), ~_(2), ~_(1),
|
|
};
|
|
#undef _
|
|
if (clib_arch_is_big_endian)
|
|
return x & masks_big_endian[n];
|
|
else
|
|
return x & masks_little_endian[n];
|
|
}
|
|
|
|
static inline u32
|
|
hash_memory32 (void *p, word n_bytes, u32 state)
|
|
{
|
|
u32 *q = p;
|
|
u32 a, b, c, n;
|
|
|
|
a = b = 0x9e3779b9;
|
|
c = state;
|
|
n = n_bytes;
|
|
|
|
while (n >= 3 * sizeof (u32))
|
|
{
|
|
a += clib_mem_unaligned (q + 0, u32);
|
|
b += clib_mem_unaligned (q + 1, u32);
|
|
c += clib_mem_unaligned (q + 2, u32);
|
|
hash_mix32 (a, b, c);
|
|
n -= 3 * sizeof (u32);
|
|
q += 3;
|
|
}
|
|
|
|
c += n_bytes;
|
|
switch (n / sizeof (u32))
|
|
{
|
|
case 2:
|
|
a += clib_mem_unaligned (q + 0, u32);
|
|
b += clib_mem_unaligned (q + 1, u32);
|
|
if (n % sizeof (u32))
|
|
c += zap32 (clib_mem_unaligned (q + 2, u32), n % sizeof (u32)) << 8;
|
|
break;
|
|
|
|
case 1:
|
|
a += clib_mem_unaligned (q + 0, u32);
|
|
if (n % sizeof (u32))
|
|
b += zap32 (clib_mem_unaligned (q + 1, u32), n % sizeof (u32));
|
|
break;
|
|
|
|
case 0:
|
|
if (n % sizeof (u32))
|
|
a += zap32 (clib_mem_unaligned (q + 0, u32), n % sizeof (u32));
|
|
break;
|
|
}
|
|
|
|
hash_mix32 (a, b, c);
|
|
|
|
return c;
|
|
}
|
|
#endif
|
|
|
|
__clib_export uword
|
|
hash_memory (void *p, word n_bytes, uword state)
|
|
{
|
|
uword *q = p;
|
|
|
|
#if uword_bits == 64
|
|
return hash_memory64 (q, n_bytes, state);
|
|
#else
|
|
return hash_memory32 (q, n_bytes, state);
|
|
#endif
|
|
}
|
|
|
|
#if uword_bits == 64
|
|
always_inline uword
|
|
hash_uword (uword x)
|
|
{
|
|
u64 a, b, c;
|
|
|
|
a = b = 0x9e3779b97f4a7c13LL;
|
|
c = 0;
|
|
a += x;
|
|
hash_mix64 (a, b, c);
|
|
return c;
|
|
}
|
|
#else
|
|
always_inline uword
|
|
hash_uword (uword x)
|
|
{
|
|
u32 a, b, c;
|
|
|
|
a = b = 0x9e3779b9;
|
|
c = 0;
|
|
a += x;
|
|
hash_mix32 (a, b, c);
|
|
return c;
|
|
}
|
|
#endif
|
|
|
|
/* Call sum function. Hash code will be sum function value
|
|
modulo the prime length of the hash table. */
|
|
always_inline uword
|
|
key_sum (hash_t * h, uword key)
|
|
{
|
|
uword sum;
|
|
switch (pointer_to_uword ((void *) h->key_sum))
|
|
{
|
|
case KEY_FUNC_NONE:
|
|
sum = hash_uword (key);
|
|
break;
|
|
|
|
case KEY_FUNC_POINTER_UWORD:
|
|
sum = hash_uword (*uword_to_pointer (key, uword *));
|
|
break;
|
|
|
|
case KEY_FUNC_POINTER_U32:
|
|
sum = hash_uword (*uword_to_pointer (key, u32 *));
|
|
break;
|
|
|
|
case KEY_FUNC_STRING:
|
|
sum = string_key_sum (h, key);
|
|
break;
|
|
|
|
case KEY_FUNC_MEM:
|
|
sum = mem_key_sum (h, key);
|
|
break;
|
|
|
|
default:
|
|
sum = h->key_sum (h, key);
|
|
break;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
always_inline uword
|
|
key_equal1 (hash_t * h, uword key1, uword key2, uword e)
|
|
{
|
|
switch (pointer_to_uword ((void *) h->key_equal))
|
|
{
|
|
case KEY_FUNC_NONE:
|
|
break;
|
|
|
|
case KEY_FUNC_POINTER_UWORD:
|
|
e =
|
|
*uword_to_pointer (key1, uword *) == *uword_to_pointer (key2,
|
|
uword *);
|
|
break;
|
|
|
|
case KEY_FUNC_POINTER_U32:
|
|
e = *uword_to_pointer (key1, u32 *) == *uword_to_pointer (key2, u32 *);
|
|
break;
|
|
|
|
case KEY_FUNC_STRING:
|
|
e = string_key_equal (h, key1, key2);
|
|
break;
|
|
|
|
case KEY_FUNC_MEM:
|
|
e = mem_key_equal (h, key1, key2);
|
|
break;
|
|
|
|
default:
|
|
e = h->key_equal (h, key1, key2);
|
|
break;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/* Compares two keys: returns 1 if equal, 0 if not. */
|
|
always_inline uword
|
|
key_equal (hash_t * h, uword key1, uword key2)
|
|
{
|
|
uword e = key1 == key2;
|
|
if (CLIB_DEBUG > 0 && key1 == key2)
|
|
ASSERT (key_equal1 (h, key1, key2, e));
|
|
if (!e)
|
|
e = key_equal1 (h, key1, key2, e);
|
|
return e;
|
|
}
|
|
|
|
static hash_pair_union_t *
|
|
get_indirect (void *v, hash_pair_indirect_t * pi, uword key)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *p0, *p1;
|
|
|
|
p0 = p1 = pi->pairs;
|
|
if (h->log2_pair_size > 0)
|
|
p1 = hash_forward (h, p0, indirect_pair_get_len (pi));
|
|
else
|
|
p1 += vec_len (p0);
|
|
|
|
while (p0 < p1)
|
|
{
|
|
if (key_equal (h, p0->key, key))
|
|
return (hash_pair_union_t *) p0;
|
|
p0 = hash_forward1 (h, p0);
|
|
}
|
|
|
|
return (hash_pair_union_t *) 0;
|
|
}
|
|
|
|
static hash_pair_union_t *
|
|
set_indirect_is_user (void *v, uword i, hash_pair_union_t * p, uword key)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *q;
|
|
hash_pair_indirect_t *pi = &p->indirect;
|
|
uword log2_bytes = 0;
|
|
|
|
if (h->log2_pair_size == 0)
|
|
q = vec_new (hash_pair_t, 2);
|
|
else
|
|
{
|
|
log2_bytes = 1 + hash_pair_log2_bytes (h);
|
|
q = clib_mem_alloc (1ULL << log2_bytes);
|
|
}
|
|
clib_memcpy_fast (q, &p->direct, hash_pair_bytes (h));
|
|
|
|
pi->pairs = q;
|
|
if (h->log2_pair_size > 0)
|
|
indirect_pair_set (pi, log2_bytes, 2);
|
|
|
|
set_is_user (v, i, 0);
|
|
|
|
/* First element is used by existing pair, second will be used by caller. */
|
|
q = hash_forward1 (h, q);
|
|
q->key = key;
|
|
init_pair (h, q);
|
|
return (hash_pair_union_t *) q;
|
|
}
|
|
|
|
static hash_pair_union_t *
|
|
set_indirect (void *v, hash_pair_indirect_t * pi, uword key,
|
|
uword * found_key)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *new_pair;
|
|
hash_pair_union_t *q;
|
|
|
|
q = get_indirect (v, pi, key);
|
|
if (q)
|
|
{
|
|
*found_key = 1;
|
|
return q;
|
|
}
|
|
|
|
if (h->log2_pair_size == 0)
|
|
vec_add2 (pi->pairs, new_pair, 1);
|
|
else
|
|
{
|
|
uword len, new_len, log2_bytes;
|
|
|
|
len = indirect_pair_get_len (pi);
|
|
log2_bytes = indirect_pair_get_log2_bytes (pi);
|
|
|
|
new_len = len + 1;
|
|
if (new_len * hash_pair_bytes (h) > (1ULL << log2_bytes))
|
|
{
|
|
pi->pairs = clib_mem_realloc (pi->pairs,
|
|
1ULL << (log2_bytes + 1),
|
|
1ULL << log2_bytes);
|
|
log2_bytes++;
|
|
}
|
|
|
|
indirect_pair_set (pi, log2_bytes, new_len);
|
|
new_pair = pi->pairs + (len << h->log2_pair_size);
|
|
}
|
|
new_pair->key = key;
|
|
init_pair (h, new_pair);
|
|
*found_key = 0;
|
|
return (hash_pair_union_t *) new_pair;
|
|
}
|
|
|
|
static void
|
|
unset_indirect (void *v, uword i, hash_pair_t * q)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_union_t *p = get_pair (v, i);
|
|
hash_pair_t *e;
|
|
hash_pair_indirect_t *pi = &p->indirect;
|
|
uword len, is_vec;
|
|
|
|
is_vec = h->log2_pair_size == 0;
|
|
|
|
ASSERT (!hash_is_user (v, i));
|
|
len = is_vec ? vec_len (pi->pairs) : indirect_pair_get_len (pi);
|
|
e = hash_forward (h, pi->pairs, len - 1);
|
|
ASSERT (q >= pi->pairs && q <= e);
|
|
|
|
/* We have two or fewer pairs and we are delete one pair.
|
|
Make indirect pointer direct and free indirect memory. */
|
|
if (len <= 2)
|
|
{
|
|
hash_pair_t *r = pi->pairs;
|
|
|
|
if (len == 2)
|
|
{
|
|
clib_memcpy_fast (p, q == r ? hash_forward1 (h, r) : r,
|
|
hash_pair_bytes (h));
|
|
set_is_user (v, i, 1);
|
|
}
|
|
else
|
|
zero_pair (h, &p->direct);
|
|
|
|
if (is_vec)
|
|
vec_free (r);
|
|
else if (r)
|
|
clib_mem_free (r);
|
|
}
|
|
else
|
|
{
|
|
/* If deleting a pair we need to keep non-null pairs together. */
|
|
if (q < e)
|
|
clib_memcpy_fast (q, e, hash_pair_bytes (h));
|
|
else
|
|
zero_pair (h, q);
|
|
if (is_vec)
|
|
_vec_len (pi->pairs) -= 1;
|
|
else
|
|
indirect_pair_set (pi, indirect_pair_get_log2_bytes (pi), len - 1);
|
|
}
|
|
}
|
|
|
|
enum lookup_opcode
|
|
{
|
|
GET = 1,
|
|
SET = 2,
|
|
UNSET = 3,
|
|
};
|
|
|
|
static hash_pair_t *
|
|
lookup (void *v, uword key, enum lookup_opcode op,
|
|
void *new_value, void *old_value)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_union_t *p = 0;
|
|
uword found_key = 0;
|
|
uword value_bytes;
|
|
uword i;
|
|
|
|
if (!v)
|
|
return 0;
|
|
|
|
i = key_sum (h, key) & (_vec_len (v) - 1);
|
|
p = get_pair (v, i);
|
|
value_bytes = hash_value_bytes (h);
|
|
|
|
if (hash_is_user (v, i))
|
|
{
|
|
found_key = key_equal (h, p->direct.key, key);
|
|
if (found_key)
|
|
{
|
|
if (op == UNSET)
|
|
{
|
|
set_is_user (v, i, 0);
|
|
if (old_value && value_bytes)
|
|
clib_memcpy_fast (old_value, p->direct.value, value_bytes);
|
|
zero_pair (h, &p->direct);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (op == SET)
|
|
p = set_indirect_is_user (v, i, p, key);
|
|
else
|
|
p = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hash_pair_indirect_t *pi = &p->indirect;
|
|
|
|
if (op == SET)
|
|
{
|
|
if (!pi->pairs)
|
|
{
|
|
p->direct.key = key;
|
|
set_is_user (v, i, 1);
|
|
}
|
|
else
|
|
p = set_indirect (v, pi, key, &found_key);
|
|
}
|
|
else
|
|
{
|
|
p = get_indirect (v, pi, key);
|
|
found_key = p != 0;
|
|
if (found_key && op == UNSET)
|
|
{
|
|
if (old_value && value_bytes)
|
|
clib_memcpy_fast (old_value, &p->direct.value, value_bytes);
|
|
|
|
unset_indirect (v, i, &p->direct);
|
|
|
|
/* Nullify p (since it's just been deleted).
|
|
Otherwise we might be tempted to play with it. */
|
|
p = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (op == SET && p != 0 && value_bytes)
|
|
{
|
|
/* Save away old value for caller. */
|
|
if (old_value && found_key)
|
|
clib_memcpy_fast (old_value, &p->direct.value, value_bytes);
|
|
clib_memcpy_fast (&p->direct.value, new_value, value_bytes);
|
|
}
|
|
|
|
if (op == SET)
|
|
h->elts += !found_key;
|
|
if (op == UNSET)
|
|
h->elts -= found_key;
|
|
|
|
return &p->direct;
|
|
}
|
|
|
|
/* Fetch value of key. */
|
|
__clib_export uword *
|
|
_hash_get (void *v, uword key)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *p;
|
|
|
|
/* Don't even search table if its empty. */
|
|
if (!v || h->elts == 0)
|
|
return 0;
|
|
|
|
p = lookup (v, key, GET, 0, 0);
|
|
if (!p)
|
|
return 0;
|
|
if (h->log2_pair_size == 0)
|
|
return &p->key;
|
|
else
|
|
return &p->value[0];
|
|
}
|
|
|
|
__clib_export hash_pair_t *
|
|
_hash_get_pair (void *v, uword key)
|
|
{
|
|
return lookup (v, key, GET, 0, 0);
|
|
}
|
|
|
|
__clib_export hash_pair_t *
|
|
hash_next (void *v, hash_next_t *hn)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_t *p;
|
|
|
|
while (1)
|
|
{
|
|
if (hn->i == 0 && hn->j == 0)
|
|
{
|
|
/* Save flags. */
|
|
hn->f = h->flags;
|
|
|
|
/* Prevent others from re-sizing hash table. */
|
|
h->flags |=
|
|
(HASH_FLAG_NO_AUTO_GROW
|
|
| HASH_FLAG_NO_AUTO_SHRINK | HASH_FLAG_HASH_NEXT_IN_PROGRESS);
|
|
}
|
|
else if (hn->i >= hash_capacity (v))
|
|
{
|
|
/* Restore flags. */
|
|
h->flags = hn->f;
|
|
clib_memset (hn, 0, sizeof (hn[0]));
|
|
return 0;
|
|
}
|
|
|
|
p = hash_forward (h, v, hn->i);
|
|
if (hash_is_user (v, hn->i))
|
|
{
|
|
hn->i++;
|
|
return p;
|
|
}
|
|
else
|
|
{
|
|
hash_pair_indirect_t *pi = (void *) p;
|
|
uword n;
|
|
|
|
if (h->log2_pair_size > 0)
|
|
n = indirect_pair_get_len (pi);
|
|
else
|
|
n = vec_len (pi->pairs);
|
|
|
|
if (hn->j >= n)
|
|
{
|
|
hn->i++;
|
|
hn->j = 0;
|
|
}
|
|
else
|
|
return hash_forward (h, pi->pairs, hn->j++);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove key from table. */
|
|
__clib_export void *
|
|
_hash_unset (void *v, uword key, void *old_value)
|
|
{
|
|
hash_t *h;
|
|
|
|
if (!v)
|
|
return v;
|
|
|
|
(void) lookup (v, key, UNSET, 0, old_value);
|
|
|
|
h = hash_header (v);
|
|
if (!(h->flags & HASH_FLAG_NO_AUTO_SHRINK))
|
|
{
|
|
/* Resize when 1/4 full. */
|
|
if (h->elts > 32 && 4 * (h->elts + 1) < vec_len (v))
|
|
v = hash_resize (v, vec_len (v) / 2);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
__clib_export void *
|
|
_hash_create (uword elts, hash_t * h_user)
|
|
{
|
|
hash_t *h;
|
|
uword log2_pair_size;
|
|
void *v;
|
|
|
|
/* Size of hash is power of 2 >= ELTS and larger than
|
|
number of bits in is_user bitmap elements. */
|
|
elts = clib_max (elts, BITS (h->is_user[0]));
|
|
elts = 1ULL << max_log2 (elts);
|
|
|
|
log2_pair_size = 1;
|
|
if (h_user)
|
|
log2_pair_size = h_user->log2_pair_size;
|
|
|
|
v = _vec_resize ((void *) 0,
|
|
/* vec len: */ elts,
|
|
/* data bytes: */
|
|
(elts << log2_pair_size) * sizeof (hash_pair_t),
|
|
/* header bytes: */
|
|
sizeof (h[0]) +
|
|
(elts / BITS (h->is_user[0])) * sizeof (h->is_user[0]),
|
|
/* alignment */ sizeof (hash_pair_t));
|
|
h = hash_header (v);
|
|
|
|
if (h_user)
|
|
h[0] = h_user[0];
|
|
|
|
h->log2_pair_size = log2_pair_size;
|
|
h->elts = 0;
|
|
|
|
/* Default flags to never shrinking hash tables.
|
|
Shrinking tables can cause "jackpot" cases. */
|
|
if (!h_user)
|
|
h->flags = HASH_FLAG_NO_AUTO_SHRINK;
|
|
|
|
if (!h->format_pair)
|
|
{
|
|
h->format_pair = hash_format_pair_default;
|
|
h->format_pair_arg = 0;
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
__clib_export void *
|
|
_hash_free (void *v)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
hash_pair_union_t *p;
|
|
uword i;
|
|
|
|
if (!v)
|
|
return v;
|
|
|
|
/* We zero all freed memory in case user would be tempted to use it. */
|
|
for (i = 0; i < hash_capacity (v); i++)
|
|
{
|
|
if (hash_is_user (v, i))
|
|
continue;
|
|
p = get_pair (v, i);
|
|
if (h->log2_pair_size == 0)
|
|
vec_free (p->indirect.pairs);
|
|
else if (p->indirect.pairs)
|
|
clib_mem_free (p->indirect.pairs);
|
|
}
|
|
|
|
vec_free_header (h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *
|
|
hash_resize_internal (void *old, uword new_size, uword free_old)
|
|
{
|
|
void *new;
|
|
hash_pair_t *p;
|
|
|
|
new = 0;
|
|
if (new_size > 0)
|
|
{
|
|
hash_t *h = old ? hash_header (old) : 0;
|
|
new = _hash_create (new_size, h);
|
|
/* *INDENT-OFF* */
|
|
hash_foreach_pair (p, old, {
|
|
new = _hash_set3 (new, p->key, &p->value[0], 0);
|
|
});
|
|
/* *INDENT-ON* */
|
|
}
|
|
|
|
if (free_old)
|
|
hash_free (old);
|
|
return new;
|
|
}
|
|
|
|
void *
|
|
hash_resize (void *old, uword new_size)
|
|
{
|
|
return hash_resize_internal (old, new_size, 1);
|
|
}
|
|
|
|
__clib_export void *
|
|
hash_dup (void *old)
|
|
{
|
|
return hash_resize_internal (old, vec_len (old), 0);
|
|
}
|
|
|
|
__clib_export void *
|
|
_hash_set3 (void *v, uword key, void *value, void *old_value)
|
|
{
|
|
hash_t *h;
|
|
|
|
if (!v)
|
|
v = hash_create (0, sizeof (uword));
|
|
|
|
h = hash_header (v);
|
|
(void) lookup (v, key, SET, value, old_value);
|
|
|
|
if (!(h->flags & HASH_FLAG_NO_AUTO_GROW))
|
|
{
|
|
/* Resize when 3/4 full. */
|
|
if (4 * (h->elts + 1) > 3 * vec_len (v))
|
|
v = hash_resize (v, 2 * vec_len (v));
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
__clib_export uword
|
|
vec_key_sum (hash_t * h, uword key)
|
|
{
|
|
void *v = uword_to_pointer (key, void *);
|
|
return hash_memory (v, vec_len (v) * h->user, 0);
|
|
}
|
|
|
|
__clib_export uword
|
|
vec_key_equal (hash_t * h, uword key1, uword key2)
|
|
{
|
|
void *v1 = uword_to_pointer (key1, void *);
|
|
void *v2 = uword_to_pointer (key2, void *);
|
|
uword l1 = vec_len (v1);
|
|
uword l2 = vec_len (v2);
|
|
return l1 == l2 && 0 == memcmp (v1, v2, l1 * h->user);
|
|
}
|
|
|
|
__clib_export u8 *
|
|
vec_key_format_pair (u8 * s, va_list * args)
|
|
{
|
|
void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
|
|
void *v = va_arg (*args, void *);
|
|
hash_pair_t *p = va_arg (*args, hash_pair_t *);
|
|
hash_t *h = hash_header (v);
|
|
void *u = uword_to_pointer (p->key, void *);
|
|
int i;
|
|
|
|
switch (h->user)
|
|
{
|
|
case 1:
|
|
s = format (s, "%v", u);
|
|
break;
|
|
|
|
case 2:
|
|
{
|
|
u16 *w = u;
|
|
for (i = 0; i < vec_len (w); i++)
|
|
s = format (s, "0x%x, ", w[i]);
|
|
break;
|
|
}
|
|
|
|
case 4:
|
|
{
|
|
u32 *w = u;
|
|
for (i = 0; i < vec_len (w); i++)
|
|
s = format (s, "0x%x, ", w[i]);
|
|
break;
|
|
}
|
|
|
|
case 8:
|
|
{
|
|
u64 *w = u;
|
|
for (i = 0; i < vec_len (w); i++)
|
|
s = format (s, "0x%Lx, ", w[i]);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
s = format (s, "0x%U", format_hex_bytes, u, vec_len (u) * h->user);
|
|
break;
|
|
}
|
|
|
|
if (hash_value_bytes (h) > 0)
|
|
s = format (s, " -> 0x%wx", p->value[0]);
|
|
|
|
return s;
|
|
}
|
|
|
|
__clib_export uword
|
|
mem_key_sum (hash_t * h, uword key)
|
|
{
|
|
uword *v = uword_to_pointer (key, void *);
|
|
return hash_memory (v, h->user, 0);
|
|
}
|
|
|
|
__clib_export uword
|
|
mem_key_equal (hash_t * h, uword key1, uword key2)
|
|
{
|
|
void *v1 = uword_to_pointer (key1, void *);
|
|
void *v2 = uword_to_pointer (key2, void *);
|
|
return v1 && v2 && 0 == memcmp (v1, v2, h->user);
|
|
}
|
|
|
|
uword
|
|
string_key_sum (hash_t * h, uword key)
|
|
{
|
|
char *v = uword_to_pointer (key, char *);
|
|
return hash_memory (v, strlen (v), 0);
|
|
}
|
|
|
|
uword
|
|
string_key_equal (hash_t * h, uword key1, uword key2)
|
|
{
|
|
void *v1 = uword_to_pointer (key1, void *);
|
|
void *v2 = uword_to_pointer (key2, void *);
|
|
return v1 && v2 && 0 == strcmp (v1, v2);
|
|
}
|
|
|
|
u8 *
|
|
string_key_format_pair (u8 * s, va_list * args)
|
|
{
|
|
void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
|
|
void *v = va_arg (*args, void *);
|
|
hash_pair_t *p = va_arg (*args, hash_pair_t *);
|
|
hash_t *h = hash_header (v);
|
|
void *u = uword_to_pointer (p->key, void *);
|
|
|
|
s = format (s, "%s", u);
|
|
|
|
if (hash_value_bytes (h) > 0)
|
|
s =
|
|
format (s, " -> 0x%8U", format_hex_bytes, &p->value[0],
|
|
hash_value_bytes (h));
|
|
|
|
return s;
|
|
}
|
|
|
|
static u8 *
|
|
hash_format_pair_default (u8 * s, va_list * args)
|
|
{
|
|
void *CLIB_UNUSED (user_arg) = va_arg (*args, void *);
|
|
void *v = va_arg (*args, void *);
|
|
hash_pair_t *p = va_arg (*args, hash_pair_t *);
|
|
hash_t *h = hash_header (v);
|
|
|
|
s = format (s, "0x%08x", p->key);
|
|
if (hash_value_bytes (h) > 0)
|
|
s =
|
|
format (s, " -> 0x%8U", format_hex_bytes, &p->value[0],
|
|
hash_value_bytes (h));
|
|
return s;
|
|
}
|
|
|
|
__clib_export uword
|
|
hash_bytes (void *v)
|
|
{
|
|
uword i, bytes;
|
|
hash_t *h = hash_header (v);
|
|
|
|
if (!v)
|
|
return 0;
|
|
|
|
bytes = vec_capacity (v, hash_header_bytes (v));
|
|
|
|
for (i = 0; i < hash_capacity (v); i++)
|
|
{
|
|
if (!hash_is_user (v, i))
|
|
{
|
|
hash_pair_union_t *p = get_pair (v, i);
|
|
if (h->log2_pair_size > 0)
|
|
bytes += 1 << indirect_pair_get_log2_bytes (&p->indirect);
|
|
else
|
|
bytes += vec_capacity (p->indirect.pairs, 0);
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
__clib_export u8 *
|
|
format_hash (u8 *s, va_list *va)
|
|
{
|
|
void *v = va_arg (*va, void *);
|
|
int verbose = va_arg (*va, int);
|
|
hash_pair_t *p;
|
|
hash_t *h = hash_header (v);
|
|
uword i;
|
|
|
|
s = format (s, "hash %p, %wd elts, capacity %wd, %wd bytes used,\n",
|
|
v, hash_elts (v), hash_capacity (v), hash_bytes (v));
|
|
|
|
{
|
|
uword *occupancy = 0;
|
|
|
|
/* Count number of buckets with each occupancy. */
|
|
for (i = 0; i < hash_capacity (v); i++)
|
|
{
|
|
uword j;
|
|
|
|
if (hash_is_user (v, i))
|
|
{
|
|
j = 1;
|
|
}
|
|
else
|
|
{
|
|
hash_pair_union_t *p = get_pair (v, i);
|
|
if (h->log2_pair_size > 0)
|
|
j = indirect_pair_get_len (&p->indirect);
|
|
else
|
|
j = vec_len (p->indirect.pairs);
|
|
}
|
|
|
|
vec_validate (occupancy, j);
|
|
occupancy[j]++;
|
|
}
|
|
|
|
s = format (s, " profile ");
|
|
for (i = 0; i < vec_len (occupancy); i++)
|
|
s = format (s, "%wd%c", occupancy[i],
|
|
i + 1 == vec_len (occupancy) ? '\n' : ' ');
|
|
|
|
s = format (s, " lookup # of compares: ");
|
|
for (i = 1; i < vec_len (occupancy); i++)
|
|
s = format (s, "%wd: .%03d%c", i,
|
|
(1000 * i * occupancy[i]) / hash_elts (v),
|
|
i + 1 == vec_len (occupancy) ? '\n' : ' ');
|
|
|
|
vec_free (occupancy);
|
|
}
|
|
|
|
if (verbose)
|
|
{
|
|
/* *INDENT-OFF* */
|
|
hash_foreach_pair (p, v, {
|
|
s = format (s, " %U\n", h->format_pair, h->format_pair_arg, v, p);
|
|
});
|
|
/* *INDENT-ON* */
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
static uword
|
|
unformat_hash_string_internal (unformat_input_t * input,
|
|
va_list * va, int is_vec)
|
|
{
|
|
uword *hash = va_arg (*va, uword *);
|
|
int *result = va_arg (*va, int *);
|
|
u8 *string = 0;
|
|
uword *p;
|
|
|
|
if (!unformat (input, is_vec ? "%v%_" : "%s%_", &string))
|
|
return 0;
|
|
|
|
p = hash_get_mem (hash, string);
|
|
if (p)
|
|
*result = *p;
|
|
|
|
vec_free (string);
|
|
return p ? 1 : 0;
|
|
}
|
|
|
|
__clib_export uword
|
|
unformat_hash_vec_string (unformat_input_t * input, va_list * va)
|
|
{
|
|
return unformat_hash_string_internal (input, va, /* is_vec */ 1);
|
|
}
|
|
|
|
__clib_export uword
|
|
unformat_hash_string (unformat_input_t * input, va_list * va)
|
|
{
|
|
return unformat_hash_string_internal (input, va, /* is_vec */ 0);
|
|
}
|
|
|
|
__clib_export clib_error_t *
|
|
hash_validate (void *v)
|
|
{
|
|
hash_t *h = hash_header (v);
|
|
uword i, j;
|
|
uword *keys = 0;
|
|
clib_error_t *error = 0;
|
|
|
|
#define CHECK(x) if ((error = ERROR_ASSERT (x))) goto done;
|
|
|
|
for (i = 0; i < hash_capacity (v); i++)
|
|
{
|
|
hash_pair_union_t *pu = get_pair (v, i);
|
|
|
|
if (hash_is_user (v, i))
|
|
{
|
|
CHECK (pu->direct.key != 0);
|
|
vec_add1 (keys, pu->direct.key);
|
|
}
|
|
else
|
|
{
|
|
hash_pair_t *p;
|
|
hash_pair_indirect_t *pi = &pu->indirect;
|
|
uword n;
|
|
|
|
n = h->log2_pair_size > 0
|
|
? indirect_pair_get_len (pi) : vec_len (pi->pairs);
|
|
|
|
for (p = pi->pairs; n-- > 0; p = hash_forward1 (h, p))
|
|
{
|
|
/* Assert key uniqueness. */
|
|
for (j = 0; j < vec_len (keys); j++)
|
|
CHECK (keys[j] != p->key);
|
|
vec_add1 (keys, p->key);
|
|
}
|
|
}
|
|
}
|
|
|
|
CHECK (vec_len (keys) == h->elts);
|
|
|
|
vec_free (keys);
|
|
done:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* fd.io coding-style-patch-verification: ON
|
|
*
|
|
* Local Variables:
|
|
* eval: (c-set-style "gnu")
|
|
* End:
|
|
*/
|