| /* |
| * 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) 2006 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/qhash.h> |
| |
| #define QHASH_ALL_VALID ((1 << QHASH_KEYS_PER_BUCKET) - 1) |
| |
| void * |
| _qhash_resize (void *v, uword length, uword elt_bytes) |
| { |
| qhash_t *h; |
| uword l; |
| |
| l = clib_max (max_log2 (length), 2 + QHASH_LOG2_KEYS_PER_BUCKET); |
| |
| /* Round up if less than 1/2 full. */ |
| l += ((f64) length / (f64) (1 << l)) < .5; |
| |
| v = _vec_resize (0, 1 << l, elt_bytes << l, sizeof (h[0]), |
| /* align */ sizeof (uword)); |
| |
| h = qhash_header (v); |
| h->n_elts = 0; |
| h->log2_hash_size = l; |
| h->hash_keys = |
| clib_mem_alloc_aligned_no_fail (sizeof (h->hash_keys[0]) << l, |
| CLIB_CACHE_LINE_BYTES); |
| vec_resize (h->hash_key_valid_bitmap, |
| 1 << (l - QHASH_LOG2_KEYS_PER_BUCKET)); |
| clib_memset (v, ~0, elt_bytes << l); |
| |
| return v; |
| } |
| |
| static u8 min_log2_table[256]; |
| |
| static inline uword |
| qhash_min_log2 (uword x) |
| { |
| ASSERT (is_pow2 (x)); |
| ASSERT (x < 256); |
| return min_log2_table[x]; |
| } |
| |
| static void |
| qhash_min_log2_init () |
| { |
| int i; |
| for (i = 0; i < 256; i++) |
| min_log2_table[i] = min_log2 (i); |
| } |
| |
| always_inline uword |
| qhash_get_valid_elt_mask (qhash_t * h, uword i) |
| { |
| return h->hash_key_valid_bitmap[i / QHASH_KEYS_PER_BUCKET]; |
| } |
| |
| always_inline void |
| qhash_set_valid_elt_mask (qhash_t * h, uword i, uword mask) |
| { |
| h->hash_key_valid_bitmap[i / QHASH_KEYS_PER_BUCKET] = mask; |
| } |
| |
| always_inline uword |
| qhash_search_bucket (uword * hash_keys, uword search_key, uword m) |
| { |
| uword t; |
| #define _(i) ((hash_keys[i] == search_key) << i) |
| t = (_(0) | _(1) | _(2) | _(3)); |
| if (QHASH_KEYS_PER_BUCKET > 4) |
| t |= (_(4) | _(5) | _(6) | _(7)); |
| if (QHASH_KEYS_PER_BUCKET > 8) |
| t |= (_(8) | _(9) | _(10) | _(11) | _(12) | _(13) | _(14) | _(15)); |
| #undef _ |
| return m & t; |
| } |
| |
| /* Lookup multiple keys in the same hash table. */ |
| void |
| qhash_get_multiple (void *v, |
| uword * search_keys, |
| uword n_search_keys, u32 * result_indices) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *k, *hash_keys; |
| uword n_left, bucket_mask; |
| u32 *r; |
| |
| if (!v) |
| { |
| clib_memset (result_indices, ~0, |
| sizeof (result_indices[0]) * n_search_keys); |
| return; |
| } |
| |
| bucket_mask = pow2_mask (h->log2_hash_size) & ~(QHASH_KEYS_PER_BUCKET - 1); |
| |
| k = search_keys; |
| n_left = n_search_keys; |
| hash_keys = h->hash_keys; |
| r = result_indices; |
| |
| while (n_left >= 2) |
| { |
| u32 a0, b0, c0, bi0, valid0, match0; |
| u32 a1, b1, c1, bi1, valid1, match1; |
| uword k0, k1, *h0, *h1; |
| |
| k0 = k[0]; |
| k1 = k[1]; |
| n_left -= 2; |
| k += 2; |
| |
| a0 = a1 = h->hash_seeds[0]; |
| b0 = b1 = h->hash_seeds[1]; |
| c0 = c1 = h->hash_seeds[2]; |
| a0 ^= k0; |
| a1 ^= k1; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| b1 ^= k1 >> 32; |
| #endif |
| |
| hash_mix32_step_1 (a0, b0, c0); |
| hash_mix32_step_1 (a1, b1, c1); |
| hash_mix32_step_2 (a0, b0, c0); |
| hash_mix32_step_2 (a1, b1, c1); |
| hash_mix32_step_3 (a0, b0, c0); |
| hash_mix32_step_3 (a1, b1, c1); |
| |
| bi0 = c0 & bucket_mask; |
| bi1 = c1 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| h1 = hash_keys + bi1; |
| |
| /* Search two buckets. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| valid1 = qhash_get_valid_elt_mask (h, bi1); |
| |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| match1 = qhash_search_bucket (h1, k1, valid1); |
| |
| bi0 += qhash_min_log2 (match0); |
| bi1 += qhash_min_log2 (match1); |
| |
| r[0] = match0 ? bi0 : ~0; |
| r[1] = match1 ? bi1 : ~0; |
| r += 2; |
| |
| /* Full buckets trigger search of overflow hash. */ |
| if (PREDICT_FALSE (!match0 && valid0 == QHASH_ALL_VALID)) |
| { |
| uword *p = hash_get (h->overflow_hash, k0); |
| r[-2] = p ? p[0] : ~0; |
| } |
| |
| /* Full buckets trigger search of overflow hash. */ |
| if (PREDICT_FALSE (!match1 && valid1 == QHASH_ALL_VALID)) |
| { |
| uword *p = hash_get (h->overflow_hash, k1); |
| r[-1] = p ? p[0] : ~0; |
| } |
| } |
| |
| while (n_left >= 1) |
| { |
| u32 a0, b0, c0, bi0, valid0, match0; |
| uword k0, *h0; |
| |
| k0 = k[0]; |
| n_left -= 1; |
| k += 1; |
| |
| a0 = h->hash_seeds[0]; |
| b0 = h->hash_seeds[1]; |
| c0 = h->hash_seeds[2]; |
| a0 ^= k0; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| #endif |
| |
| hash_mix32 (a0, b0, c0); |
| |
| bi0 = c0 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| |
| /* Search one bucket. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| |
| bi0 += qhash_min_log2 (match0); |
| |
| r[0] = match0 ? bi0 : ~0; |
| r += 1; |
| |
| /* Full buckets trigger search of overflow hash. */ |
| if (PREDICT_FALSE (!match0 && valid0 == QHASH_ALL_VALID)) |
| { |
| uword *p = hash_get (h->overflow_hash, k0); |
| r[-1] = p ? p[0] : ~0; |
| } |
| } |
| } |
| |
| /* Lookup multiple keys in the same hash table. |
| Returns index of first matching key. */ |
| u32 |
| qhash_get_first_match (void *v, |
| uword * search_keys, |
| uword n_search_keys, uword * matching_key) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *k, *hash_keys; |
| uword n_left, match_mask, bucket_mask; |
| |
| if (!v) |
| return ~0; |
| |
| match_mask = 0; |
| bucket_mask = pow2_mask (h->log2_hash_size) & ~(QHASH_KEYS_PER_BUCKET - 1); |
| |
| k = search_keys; |
| n_left = n_search_keys; |
| hash_keys = h->hash_keys; |
| while (n_left >= 2) |
| { |
| u32 a0, b0, c0, bi0, valid0; |
| u32 a1, b1, c1, bi1, valid1; |
| uword k0, k1, *h0, *h1; |
| |
| k0 = k[0]; |
| k1 = k[1]; |
| n_left -= 2; |
| k += 2; |
| |
| a0 = a1 = h->hash_seeds[0]; |
| b0 = b1 = h->hash_seeds[1]; |
| c0 = c1 = h->hash_seeds[2]; |
| a0 ^= k0; |
| a1 ^= k1; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| b1 ^= k1 >> 32; |
| #endif |
| |
| hash_mix32_step_1 (a0, b0, c0); |
| hash_mix32_step_1 (a1, b1, c1); |
| hash_mix32_step_2 (a0, b0, c0); |
| hash_mix32_step_2 (a1, b1, c1); |
| hash_mix32_step_3 (a0, b0, c0); |
| hash_mix32_step_3 (a1, b1, c1); |
| |
| bi0 = c0 & bucket_mask; |
| bi1 = c1 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| h1 = hash_keys + bi1; |
| |
| /* Search two buckets. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| valid1 = qhash_get_valid_elt_mask (h, bi1); |
| match_mask = qhash_search_bucket (h0, k0, valid0); |
| match_mask |= (qhash_search_bucket (h1, k1, valid1) |
| << QHASH_KEYS_PER_BUCKET); |
| if (match_mask) |
| { |
| uword bi, is_match1; |
| |
| bi = qhash_min_log2 (match_mask); |
| is_match1 = bi >= QHASH_KEYS_PER_BUCKET; |
| |
| bi += ((is_match1 ? bi1 : bi0) |
| - (is_match1 << QHASH_LOG2_KEYS_PER_BUCKET)); |
| *matching_key = (k - 2 - search_keys) + is_match1; |
| return bi; |
| } |
| |
| /* Full buckets trigger search of overflow hash. */ |
| if (PREDICT_FALSE (valid0 == QHASH_ALL_VALID |
| || valid1 == QHASH_ALL_VALID)) |
| { |
| uword *p = 0; |
| uword ki = k - 2 - search_keys; |
| |
| if (valid0 == QHASH_ALL_VALID) |
| p = hash_get (h->overflow_hash, k0); |
| |
| if (!p && valid1 == QHASH_ALL_VALID) |
| { |
| p = hash_get (h->overflow_hash, k1); |
| ki++; |
| } |
| |
| if (p) |
| { |
| *matching_key = ki; |
| return p[0]; |
| } |
| } |
| } |
| |
| while (n_left >= 1) |
| { |
| u32 a0, b0, c0, bi0, valid0; |
| uword k0, *h0; |
| |
| k0 = k[0]; |
| n_left -= 1; |
| k += 1; |
| |
| a0 = h->hash_seeds[0]; |
| b0 = h->hash_seeds[1]; |
| c0 = h->hash_seeds[2]; |
| a0 ^= k0; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| #endif |
| |
| hash_mix32 (a0, b0, c0); |
| |
| bi0 = c0 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| |
| /* Search one bucket. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| match_mask = qhash_search_bucket (h0, k0, valid0); |
| if (match_mask) |
| { |
| uword bi; |
| bi = bi0 + qhash_min_log2 (match_mask); |
| *matching_key = (k - 1 - search_keys); |
| return bi; |
| } |
| |
| /* Full buckets trigger search of overflow hash. */ |
| if (PREDICT_FALSE (valid0 == QHASH_ALL_VALID)) |
| { |
| uword *p = hash_get (h->overflow_hash, k0); |
| if (p) |
| { |
| *matching_key = (k - 1 - search_keys); |
| return p[0]; |
| } |
| } |
| } |
| |
| return ~0; |
| } |
| |
| static void * |
| qhash_set_overflow (void *v, uword elt_bytes, |
| uword key, uword bi, uword * n_elts, u32 * result) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *p = hash_get (h->overflow_hash, key); |
| uword i; |
| |
| bi /= QHASH_KEYS_PER_BUCKET; |
| |
| if (p) |
| i = p[0]; |
| else |
| { |
| uword l = vec_len (h->overflow_free_indices); |
| if (l > 0) |
| { |
| i = h->overflow_free_indices[l - 1]; |
| _vec_len (h->overflow_free_indices) = l - 1; |
| } |
| else |
| i = (1 << h->log2_hash_size) + hash_elts (h->overflow_hash); |
| hash_set (h->overflow_hash, key, i); |
| vec_validate (h->overflow_counts, bi); |
| h->overflow_counts[bi] += 1; |
| *n_elts += 1; |
| |
| l = vec_len (v); |
| if (i >= l) |
| { |
| uword dl = round_pow2 (1 + i - l, 8); |
| v = _vec_resize (v, dl, (l + dl) * elt_bytes, sizeof (h[0]), |
| /* align */ sizeof (uword)); |
| clib_memset (v + l * elt_bytes, ~0, dl * elt_bytes); |
| } |
| } |
| |
| *result = i; |
| |
| return v; |
| } |
| |
| static uword |
| qhash_unset_overflow (void *v, uword key, uword bi, uword * n_elts) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *p = hash_get (h->overflow_hash, key); |
| uword result; |
| |
| bi /= QHASH_KEYS_PER_BUCKET; |
| |
| if (p) |
| { |
| result = p[0]; |
| hash_unset (h->overflow_hash, key); |
| ASSERT (bi < vec_len (h->overflow_counts)); |
| ASSERT (h->overflow_counts[bi] > 0); |
| ASSERT (*n_elts > 0); |
| vec_add1 (h->overflow_free_indices, result); |
| h->overflow_counts[bi] -= 1; |
| *n_elts -= 1; |
| } |
| else |
| result = ~0; |
| |
| return result; |
| } |
| |
| always_inline uword |
| qhash_find_free (uword i, uword valid_mask) |
| { |
| return first_set (~valid_mask & pow2_mask (QHASH_KEYS_PER_BUCKET)); |
| } |
| |
| void * |
| _qhash_set_multiple (void *v, |
| uword elt_bytes, |
| uword * search_keys, |
| uword n_search_keys, u32 * result_indices) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *k, *hash_keys; |
| uword n_left, n_elts, bucket_mask; |
| u32 *r; |
| |
| if (vec_len (v) < n_search_keys) |
| v = _qhash_resize (v, n_search_keys, elt_bytes); |
| |
| if (qhash_min_log2 (2) != 1) |
| { |
| qhash_min_log2_init (); |
| ASSERT (qhash_min_log2 (2) == 1); |
| } |
| |
| ASSERT (v != 0); |
| |
| bucket_mask = pow2_mask (h->log2_hash_size) & ~(QHASH_KEYS_PER_BUCKET - 1); |
| |
| hash_keys = h->hash_keys; |
| k = search_keys; |
| r = result_indices; |
| n_left = n_search_keys; |
| n_elts = h->n_elts; |
| |
| while (n_left >= 2) |
| { |
| u32 a0, b0, c0, bi0, match0, valid0, free0; |
| u32 a1, b1, c1, bi1, match1, valid1, free1; |
| uword k0, *h0; |
| uword k1, *h1; |
| |
| k0 = k[0]; |
| k1 = k[1]; |
| |
| /* Keys must be unique. */ |
| ASSERT (k0 != k1); |
| |
| n_left -= 2; |
| k += 2; |
| |
| a0 = a1 = h->hash_seeds[0]; |
| b0 = b1 = h->hash_seeds[1]; |
| c0 = c1 = h->hash_seeds[2]; |
| a0 ^= k0; |
| a1 ^= k1; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| b1 ^= k1 >> 32; |
| #endif |
| |
| hash_mix32_step_1 (a0, b0, c0); |
| hash_mix32_step_1 (a1, b1, c1); |
| hash_mix32_step_2 (a0, b0, c0); |
| hash_mix32_step_2 (a1, b1, c1); |
| hash_mix32_step_3 (a0, b0, c0); |
| hash_mix32_step_3 (a1, b1, c1); |
| |
| bi0 = c0 & bucket_mask; |
| bi1 = c1 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| h1 = hash_keys + bi1; |
| |
| /* Search two buckets. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| valid1 = qhash_get_valid_elt_mask (h, bi1); |
| |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| match1 = qhash_search_bucket (h1, k1, valid1); |
| |
| /* Find first free element starting at hash offset into bucket. */ |
| free0 = qhash_find_free (c0 & (QHASH_KEYS_PER_BUCKET - 1), valid0); |
| |
| valid1 = valid1 | (bi0 == bi1 ? free0 : 0); |
| free1 = qhash_find_free (c1 & (QHASH_KEYS_PER_BUCKET - 1), valid1); |
| |
| n_elts += (match0 == 0) + (match1 == 0); |
| |
| match0 = match0 ? match0 : free0; |
| match1 = match1 ? match1 : free1; |
| |
| valid0 |= match0; |
| valid1 |= match1; |
| |
| h0 += qhash_min_log2 (match0); |
| h1 += qhash_min_log2 (match1); |
| |
| if (PREDICT_FALSE (!match0 || !match1)) |
| goto slow_path2; |
| |
| h0[0] = k0; |
| h1[0] = k1; |
| r[0] = h0 - hash_keys; |
| r[1] = h1 - hash_keys; |
| r += 2; |
| qhash_set_valid_elt_mask (h, bi0, valid0); |
| qhash_set_valid_elt_mask (h, bi1, valid1); |
| continue; |
| |
| slow_path2: |
| if (!match0) |
| { |
| n_elts -= 1; |
| v = qhash_set_overflow (v, elt_bytes, k0, bi0, &n_elts, &r[0]); |
| } |
| else |
| { |
| h0[0] = k0; |
| r[0] = h0 - hash_keys; |
| qhash_set_valid_elt_mask (h, bi0, valid0); |
| } |
| if (!match1) |
| { |
| n_elts -= 1; |
| v = qhash_set_overflow (v, elt_bytes, k1, bi1, &n_elts, &r[1]); |
| } |
| else |
| { |
| h1[0] = k1; |
| r[1] = h1 - hash_keys; |
| qhash_set_valid_elt_mask (h, bi1, valid1); |
| } |
| r += 2; |
| } |
| |
| while (n_left >= 1) |
| { |
| u32 a0, b0, c0, bi0, match0, valid0, free0; |
| uword k0, *h0; |
| |
| k0 = k[0]; |
| n_left -= 1; |
| k += 1; |
| |
| a0 = h->hash_seeds[0]; |
| b0 = h->hash_seeds[1]; |
| c0 = h->hash_seeds[2]; |
| a0 ^= k0; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| #endif |
| |
| hash_mix32 (a0, b0, c0); |
| |
| bi0 = c0 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| |
| /* Find first free element starting at hash offset into bucket. */ |
| free0 = qhash_find_free (c0 & (QHASH_KEYS_PER_BUCKET - 1), valid0); |
| |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| |
| n_elts += (match0 == 0); |
| |
| match0 = match0 ? match0 : free0; |
| |
| valid0 |= match0; |
| |
| h0 += qhash_min_log2 (match0); |
| |
| if (PREDICT_FALSE (!match0)) |
| goto slow_path1; |
| |
| h0[0] = k0; |
| r[0] = h0 - hash_keys; |
| r += 1; |
| qhash_set_valid_elt_mask (h, bi0, valid0); |
| continue; |
| |
| slow_path1: |
| n_elts -= 1; |
| v = qhash_set_overflow (v, elt_bytes, k0, bi0, &n_elts, &r[0]); |
| r += 1; |
| } |
| |
| h = qhash_header (v); |
| h->n_elts = n_elts; |
| |
| return v; |
| } |
| |
| static uword |
| unset_slow_path (void *v, uword elt_bytes, |
| uword k0, uword bi0, uword valid0, uword match0, |
| uword * n_elts) |
| { |
| qhash_t *h = qhash_header (v); |
| uword i, j = 0, k, l, t = ~0; |
| hash_pair_t *p, *found; |
| |
| if (!match0) |
| { |
| if (valid0 == QHASH_ALL_VALID) |
| t = qhash_unset_overflow (v, k0, bi0, n_elts); |
| return t; |
| } |
| |
| i = bi0 / QHASH_KEYS_PER_BUCKET; |
| t = bi0 + qhash_min_log2 (match0); |
| |
| if (valid0 == QHASH_ALL_VALID |
| && i < vec_len (h->overflow_counts) && h->overflow_counts[i] > 0) |
| { |
| found = 0; |
| /* *INDENT-OFF* */ |
| hash_foreach_pair (p, h->overflow_hash, ({ |
| j = qhash_hash_mix (h, p->key) / QHASH_KEYS_PER_BUCKET; |
| if (j == i) |
| { |
| found = p; |
| break; |
| } |
| })); |
| /* *INDENT-ON* */ |
| ASSERT (found != 0); |
| ASSERT (j == i); |
| |
| l = found->value[0]; |
| k = found->key; |
| hash_unset3 (h->overflow_hash, k, &j); |
| vec_add1 (h->overflow_free_indices, j); |
| h->overflow_counts[i] -= 1; |
| |
| qhash_set_valid_elt_mask (h, bi0, valid0); |
| |
| h->hash_keys[t] = k; |
| clib_memswap (v + t * elt_bytes, v + l * elt_bytes, elt_bytes); |
| t = l; |
| } |
| else |
| qhash_set_valid_elt_mask (h, bi0, valid0 ^ match0); |
| |
| return t; |
| } |
| |
| void |
| _qhash_unset_multiple (void *v, |
| uword elt_bytes, |
| uword * search_keys, |
| uword n_search_keys, u32 * result_indices) |
| { |
| qhash_t *h = qhash_header (v); |
| uword *k, *hash_keys; |
| uword n_left, n_elts, bucket_mask; |
| u32 *r; |
| |
| if (!v) |
| { |
| uword i; |
| for (i = 0; i < n_search_keys; i++) |
| result_indices[i] = ~0; |
| } |
| |
| bucket_mask = pow2_mask (h->log2_hash_size) & ~(QHASH_KEYS_PER_BUCKET - 1); |
| |
| hash_keys = h->hash_keys; |
| k = search_keys; |
| r = result_indices; |
| n_left = n_search_keys; |
| n_elts = h->n_elts; |
| |
| while (n_left >= 2) |
| { |
| u32 a0, b0, c0, bi0, match0, valid0; |
| u32 a1, b1, c1, bi1, match1, valid1; |
| uword k0, *h0; |
| uword k1, *h1; |
| |
| k0 = k[0]; |
| k1 = k[1]; |
| |
| /* Keys must be unique. */ |
| ASSERT (k0 != k1); |
| |
| n_left -= 2; |
| k += 2; |
| |
| a0 = a1 = h->hash_seeds[0]; |
| b0 = b1 = h->hash_seeds[1]; |
| c0 = c1 = h->hash_seeds[2]; |
| a0 ^= k0; |
| a1 ^= k1; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| b1 ^= k1 >> 32; |
| #endif |
| |
| hash_mix32_step_1 (a0, b0, c0); |
| hash_mix32_step_1 (a1, b1, c1); |
| hash_mix32_step_2 (a0, b0, c0); |
| hash_mix32_step_2 (a1, b1, c1); |
| hash_mix32_step_3 (a0, b0, c0); |
| hash_mix32_step_3 (a1, b1, c1); |
| |
| bi0 = c0 & bucket_mask; |
| bi1 = c1 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| h1 = hash_keys + bi1; |
| |
| /* Search two buckets. */ |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| valid1 = qhash_get_valid_elt_mask (h, bi1); |
| |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| match1 = qhash_search_bucket (h1, k1, valid1); |
| |
| n_elts -= (match0 != 0) + (match1 != 0); |
| |
| if (PREDICT_FALSE (valid0 == QHASH_ALL_VALID |
| || valid1 == QHASH_ALL_VALID)) |
| goto slow_path2; |
| |
| valid0 ^= match0; |
| qhash_set_valid_elt_mask (h, bi0, valid0); |
| |
| valid1 = bi0 == bi1 ? valid0 : valid1; |
| valid1 ^= match1; |
| |
| qhash_set_valid_elt_mask (h, bi1, valid1); |
| |
| r[0] = match0 ? bi0 + qhash_min_log2 (match0) : ~0; |
| r[1] = match1 ? bi1 + qhash_min_log2 (match1) : ~0; |
| r += 2; |
| continue; |
| |
| slow_path2: |
| r[0] = unset_slow_path (v, elt_bytes, k0, bi0, valid0, match0, &n_elts); |
| if (bi0 == bi1) |
| { |
| /* Search again in same bucket to test new overflow element. */ |
| valid1 = qhash_get_valid_elt_mask (h, bi0); |
| if (!match1) |
| { |
| match1 = qhash_search_bucket (h1, k1, valid1); |
| n_elts -= (match1 != 0); |
| } |
| } |
| r[1] = unset_slow_path (v, elt_bytes, k1, bi1, valid1, match1, &n_elts); |
| r += 2; |
| } |
| |
| while (n_left >= 1) |
| { |
| u32 a0, b0, c0, bi0, match0, valid0; |
| uword k0, *h0; |
| |
| k0 = k[0]; |
| n_left -= 1; |
| k += 1; |
| |
| a0 = h->hash_seeds[0]; |
| b0 = h->hash_seeds[1]; |
| c0 = h->hash_seeds[2]; |
| a0 ^= k0; |
| #if uword_bits == 64 |
| b0 ^= k0 >> 32; |
| #endif |
| |
| hash_mix32 (a0, b0, c0); |
| |
| bi0 = c0 & bucket_mask; |
| |
| h0 = hash_keys + bi0; |
| |
| valid0 = qhash_get_valid_elt_mask (h, bi0); |
| |
| match0 = qhash_search_bucket (h0, k0, valid0); |
| n_elts -= (match0 != 0); |
| qhash_set_valid_elt_mask (h, bi0, valid0 ^ match0); |
| |
| r[0] = match0 ? bi0 + qhash_min_log2 (match0) : ~0; |
| r += 1; |
| |
| if (PREDICT_FALSE (valid0 == QHASH_ALL_VALID)) |
| r[-1] = unset_slow_path (v, elt_bytes, k0, bi0, valid0, match0, |
| &n_elts); |
| } |
| |
| h->n_elts = n_elts; |
| } |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |