| /*****************************************************************************\ |
| * xahash.c - functions used for arbitrary hash table |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| /* |
| * Fixed size xahash_t is designed to have all operations with a cost of O(1). |
| * This requires a good bit of design to achieve the goal. |
| * |
| * Fixed size hash table: xahash_table_t |
| * The fixed count xahash_table_t does 1 xmalloc() call unless there are hash |
| * collisions. Then it does 1 xmalloc() per each insert with a hash collision. |
| * |
| * This is the memory layout of each hash table: |
| * |
| * |----------------------------| |
| * | hash table header | xahash_table_t* and xahash_table_header_t* |
| * | | body of xahash_table_header_t |
| * |----------------------------| |
| * | state blob | _get_state() |
| * | | xahash_table_header_t->state_blob_bytes |
| * |----------------------------| |
| * | |----------------| | _get_fentry(index), fentry_header_t* |
| * | | entry header | | |
| * | |----------------| @ index | _get_fentry_blob(fentry_header_t*) |
| * | | entry blob | | |
| * | |----------------| | _get_fentry(index,depth) + xahash_table_header_t->bytes_per_entry_blob |
| * |----------------------------| |
| * |
| * Each entry fails down into a linked list on a hash collision: |
| * |
| * <-------------- xmalloc() per entry ---> |
| * |----------------| |----------------| |----------------| |
| * | entry header | | entry header | | entry header | |
| * | next*| -> | next*| -> | NULL| |
| * |----------------| |----------------| |----------------| |
| * | entry blob | | entry blob | | entry blob | |
| * |----------------| |----------------| |----------------| |
| * |
| * Hash collisions are slow and break the O(1) design and should be avoided. |
| * |
| */ |
| |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/read_config.h" |
| #include "src/common/timers.h" |
| #include "src/common/xahash.h" |
| #include "src/common/xassert.h" |
| #include "src/common/xmalloc.h" |
| |
| /* Macro to only include the code inside of ONLY_DEBUG if !NDEBUG */ |
| #ifndef NDEBUG |
| #define ONLY_DEBUG(...) __VA_ARGS__ |
| #else |
| #define ONLY_DEBUG(...) |
| #endif |
| |
| #define HASH_TABLE_MAGIC 0x131e1aff |
| #define HASH_FENTRY_MAGIC(index) (((uint32_t) 0xffff0000) ^ ((uint32_t) index)) |
| |
| typedef enum { |
| HT_FLAG_STATE_MASK = 0xff, |
| HT_FLAG_INVALID = 0, |
| HT_FLAG_FIXED_SIZE = SLURM_BIT(0), /* hash table is expected to be fixed and use linked list of entries for hash collisions */ |
| HT_FLAG_DYNAMIC_SIZE = SLURM_BIT(1), /* hash table is expected to resize as needed */ |
| HT_FLAG_INVALID_MAX, |
| } hash_flags_t; |
| |
| typedef struct { |
| ONLY_DEBUG(int magic); /* HASH_TABLE_MAGIC */ |
| hash_flags_t flags; |
| xahash_func_t hash_func; |
| xahash_match_func_t match_func; |
| const char *match_func_string; |
| xahash_on_insert_func_t on_insert_func; |
| const char *on_insert_func_string; |
| xahash_on_free_func_t on_free_func; |
| const char *on_free_func_string; |
| size_t state_blob_bytes; |
| size_t bytes_per_entry_blob; |
| union { |
| struct { |
| size_t count; |
| } fixed; |
| } type; |
| } xahash_table_header_t; |
| |
| typedef enum { |
| FENTRY_FLAG_INVALID = 0, |
| FENTRY_FLAG_UNSET = SLURM_BIT(0), /* entry is not populated */ |
| FENTRY_FLAG_SET = SLURM_BIT(1), /* entry is populated */ |
| FENTRY_FLAG_INVALID_MAX |
| } fentry_flags_t; |
| |
| /* Fixed size entry struct */ |
| typedef struct fentry_header_s fentry_header_t; |
| struct fentry_header_s { |
| ONLY_DEBUG(int magic); /* HASH_FENTRY_MAGIC % index */ |
| fentry_flags_t flags; |
| /* next entry in linked list */ |
| fentry_header_t *next; |
| }; |
| |
| static const struct { |
| const char *str; |
| xahash_foreach_control_t value; |
| } foreach_control_strings[] = { |
| { "INVALID", XAHASH_FOREACH_INVALID }, |
| { "CONTINUE", XAHASH_FOREACH_CONT }, |
| { "STOP", XAHASH_FOREACH_STOP }, |
| { "FAIL", XAHASH_FOREACH_FAIL }, |
| { "INVALID", XAHASH_FOREACH_INVALID_MAX }, |
| }; |
| |
| static void *_get_state(const xahash_table_t *ht); |
| static fentry_header_t *_get_fentry(xahash_table_t *ht, |
| xahash_table_header_t *hth, int index); |
| |
| /* Macro place holders for sanity checks */ |
| #define _is_debug() (false) |
| #define DEF_DEBUG_TIMER |
| #define START_DEBUG_TIMER |
| #define END_DEBUG_TIMER |
| #define _check_magic(...) |
| #define _check_fentry_bounds(...) |
| #define _check_fentry_magic(...) |
| |
| static bool _is_fixed(const xahash_table_t *ht, |
| const xahash_table_header_t *hth) |
| { |
| _check_magic(ht); |
| |
| return ((hth->flags & HT_FLAG_STATE_MASK) == HT_FLAG_FIXED_SIZE); |
| } |
| |
| static int _fixed_hash_to_index(const xahash_table_t *ht, |
| const xahash_table_header_t *hth, |
| const xahash_hash_t hash) |
| { |
| xassert(_is_fixed(ht, hth)); |
| |
| return hash % hth->type.fixed.count; |
| } |
| |
| static void *_get_state(const xahash_table_t *ht) |
| { |
| return ((void *) ht) + sizeof(xahash_table_header_t); |
| } |
| |
| static xahash_table_header_t *_get_header(xahash_table_t *ht) |
| { |
| xahash_table_header_t *hth = (void *) ht; |
| |
| xassert(hth->magic == HASH_TABLE_MAGIC); |
| |
| return hth; |
| } |
| |
| static const xahash_table_header_t *_get_header_const(const xahash_table_t *ht) |
| { |
| return _get_header((xahash_table_t *) ht); |
| } |
| |
| /* |
| * Get pointer to entry based on index |
| * WARNING: magic is not checked |
| * WARNING: entry may not be set |
| */ |
| static fentry_header_t *_get_fentry(xahash_table_t *ht, |
| xahash_table_header_t *hth, int index) |
| { |
| fentry_header_t *fe; |
| void *start_ht = ht; |
| void *end_hth = start_ht + sizeof(*hth); |
| void *end_state_blob = end_hth + hth->state_blob_bytes; |
| const size_t bytes_per_fentry = |
| (sizeof(fentry_header_t) + hth->bytes_per_entry_blob); |
| |
| xassert(index >= 0); |
| xassert(index < hth->type.fixed.count); |
| |
| fe = end_state_blob + (bytes_per_fentry * index); |
| |
| _check_fentry_bounds(ht, hth, fe); |
| |
| return fe; |
| } |
| |
| static bool _is_fentry_set(const xahash_table_t *ht, |
| const xahash_table_header_t *hth, |
| const fentry_header_t *fe) |
| { |
| _check_fentry_magic(ht, hth, fe, -1, -1); |
| |
| return (fe->flags & FENTRY_FLAG_SET); |
| } |
| |
| /* WARNING: does not call _check_fentry_magic() */ |
| static void *_get_fentry_blob_ptr(const xahash_table_t *ht, |
| const xahash_table_header_t *hth, |
| const fentry_header_t *fe) |
| { |
| void *ptr; |
| |
| _check_fentry_magic(ht, hth, fe, -1, -1); |
| |
| ptr = ((void *) fe) + sizeof(*fe); |
| |
| /* verify pointer is in bounds of the ht's malloc() */ |
| xassert(ptr); |
| xassert((void *) ptr > (void *) fe); |
| xassert((void *) ptr >= ((void *) fe) + sizeof(*fe)); |
| |
| return ptr; |
| } |
| |
| static void *_get_fentry_blob(const xahash_table_t *ht, |
| const xahash_table_header_t *hth, |
| const fentry_header_t *fe) |
| { |
| void *blob; |
| |
| xassert(fe); |
| _check_fentry_magic(ht, hth, fe, -1, -1); |
| xassert(_is_fentry_set(ht, hth, fe)); |
| |
| if (!fe || !_is_fentry_set(ht, hth, fe)) |
| return NULL; |
| |
| blob = _get_fentry_blob_ptr(ht, hth, fe); |
| |
| xassert(blob); |
| |
| return blob; |
| } |
| |
| static fentry_header_t *_init_fentry(xahash_table_t *ht, |
| xahash_table_header_t *hth, |
| fentry_header_t *fe, bool is_new, |
| const int index, const int depth) |
| { |
| if (is_new) { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] initializing fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe); |
| } else { |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] reinitializing fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe); |
| } |
| |
| *fe = (fentry_header_t) { |
| ONLY_DEBUG(.magic = HASH_FENTRY_MAGIC(index),) |
| .flags = FENTRY_FLAG_UNSET, |
| }; |
| |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| |
| ONLY_DEBUG(memset(_get_fentry_blob_ptr(ht, hth, fe), 0, |
| hth->bytes_per_entry_blob)); |
| |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| |
| return fe; |
| } |
| |
| static xahash_table_t *_new_fixed_table(xahash_func_t hash_func, |
| const char *hash_func_string, |
| xahash_match_func_t match_func, |
| const char *match_func_string, |
| xahash_on_insert_func_t on_insert_func, |
| const char *on_insert_func_string, |
| xahash_on_free_func_t on_free_func, |
| const char *on_free_func_string, |
| const size_t state_bytes, |
| const size_t bytes_per_entry, |
| const size_t fixed_table_size) |
| { |
| size_t bytes; |
| xahash_table_t *ht; |
| xahash_table_header_t *hth; |
| |
| xassert(fixed_table_size > 0); |
| xassert(bytes_per_entry > 0); |
| |
| bytes = sizeof(*hth) + state_bytes + |
| ((sizeof(fentry_header_t) + bytes_per_entry) * |
| fixed_table_size); |
| |
| log_flag(DATA, "%s: initializing fixed xahash_table_t with fixed %zu entries and %zu bytes per entry and %zu state bytes for %zu bytes total. Callbacks: hash_func=%s()@0x%"PRIxPTR" match_func=%s()@0x%"PRIxPTR" on_insert_func=%s()@0x%"PRIxPTR" on_free_func=%s()@0x%"PRIxPTR, |
| __func__, fixed_table_size, bytes_per_entry, state_bytes, bytes, |
| hash_func_string, (uintptr_t) hash_func, match_func_string, |
| (uintptr_t) match_func, on_insert_func_string, |
| (uintptr_t) on_insert_func, on_free_func_string, |
| (uintptr_t) on_free_func); |
| |
| hth = ht = xmalloc_nz(bytes); |
| *hth = (xahash_table_header_t){ |
| ONLY_DEBUG(.magic = HASH_TABLE_MAGIC,) |
| .flags = HT_FLAG_FIXED_SIZE, |
| .hash_func = hash_func, |
| .match_func = match_func, |
| .match_func_string = match_func_string, |
| .on_insert_func = on_insert_func, |
| .on_insert_func_string = on_insert_func_string, |
| .on_free_func = on_free_func, |
| .on_free_func_string = on_free_func_string, |
| .state_blob_bytes = state_bytes, |
| .bytes_per_entry_blob = bytes_per_entry, |
| .type.fixed = { |
| .count = fixed_table_size, |
| } |
| }; |
| |
| for (int i = 0; i < hth->type.fixed.count; i++) |
| (void) _init_fentry(ht, hth, _get_fentry(ht, hth, i), true, i, |
| 0); |
| |
| ONLY_DEBUG(memset(_get_state(ht), 0, state_bytes)); |
| |
| return ht; |
| } |
| |
| extern xahash_table_t *xahash_new_table_funcname( |
| xahash_func_t hash_func, const char *hash_func_string, |
| xahash_match_func_t match_func, const char *match_func_string, |
| xahash_on_insert_func_t on_insert_func, |
| const char *on_insert_func_string, xahash_on_free_func_t on_free_func, |
| const char *on_free_func_string, const size_t state_bytes, |
| const size_t bytes_per_entry, const size_t fixed_table_size) |
| { |
| if (fixed_table_size > 0) |
| return _new_fixed_table(hash_func, hash_func_string, match_func, |
| match_func_string, on_insert_func, |
| on_insert_func_string, on_free_func, |
| on_free_func_string, state_bytes, |
| bytes_per_entry, fixed_table_size); |
| |
| /* TODO: dynamic sizing not implemented yet */ |
| fatal_abort("should never execute"); |
| } |
| |
| static void _free_fentry(xahash_table_t *ht, xahash_table_header_t *hth, |
| int index, int depth, fentry_header_t *fe, |
| fentry_header_t *parent) |
| { |
| fentry_header_t *next = fe->next; |
| |
| if (next) { |
| _check_fentry_magic(ht, hth, fe->next, index, (depth + 1)); |
| xassert(parent != next); |
| xassert(next != fe); |
| } |
| |
| if (hth->on_free_func && _is_fentry_set(ht, hth, fe)) { |
| log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe), |
| hth->bytes_per_entry_blob, |
| "%s: [hashtable@0x%"PRIxPTR"] calling %s()@0x%"PRIxPTR" for fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, hth->on_free_func_string, |
| (uintptr_t) hth->on_free_func, index, depth, |
| (uintptr_t) fe); |
| |
| hth->on_free_func(_get_fentry_blob(ht, hth, fe), |
| _get_state(ht)); |
| } |
| |
| if (parent) { |
| log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe), |
| hth->bytes_per_entry_blob, |
| "%s: [hashtable@0x%"PRIxPTR"] dropping linked fentry[%d][%d]@0x%"PRIxPTR" -> fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, index, (depth - 1), |
| (uintptr_t) parent, index, depth, (uintptr_t) fe); |
| |
| xassert(parent->next == fe); |
| xassert(depth > 0); |
| _check_fentry_magic(ht, hth, parent, index, (depth - 1)); |
| |
| /* unlink fe from list */ |
| parent->next = next; |
| |
| ONLY_DEBUG(fe->magic = ~HASH_FENTRY_MAGIC(index)); |
| xfree(fe); |
| |
| _check_fentry_magic(ht, hth, parent, index, (depth - 1)); |
| if (next) |
| _check_fentry_magic(ht, hth, next, index, depth); |
| } else { |
| xassert(!depth); |
| |
| log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe), |
| hth->bytes_per_entry_blob, |
| "%s: [hashtable@0x%"PRIxPTR"] releasing fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe); |
| |
| (void) _init_fentry(ht, hth, fe, false, index, depth); |
| fe->next = next; |
| } |
| } |
| |
| static void _free_fixed_table_members(xahash_table_t *ht, |
| xahash_table_header_t *hth) |
| { |
| for (int i = 0; i < hth->type.fixed.count; i++) { |
| fentry_header_t *fe = _get_fentry(ht, hth, i); |
| |
| _check_fentry_magic(ht, hth, fe, i, 0); |
| |
| while (fe->next) { |
| _check_fentry_magic(ht, hth, fe, i, 1); |
| _free_fentry(ht, hth, i, 1, fe->next, fe); |
| } |
| |
| _free_fentry(ht, hth, i, 0, fe, NULL); |
| } |
| } |
| |
| extern void xahash_free_table(xahash_table_t *ht) |
| { |
| xahash_table_header_t *hth; |
| |
| _check_magic(ht); |
| if (!ht) |
| return; |
| |
| hth = _get_header(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request free hashtable", |
| __func__, (uintptr_t) ht); |
| |
| if (_is_fixed(ht, hth)) |
| _free_fixed_table_members(ht, hth); |
| |
| ONLY_DEBUG(hth->magic = ~HASH_TABLE_MAGIC); |
| xfree(ht); |
| } |
| |
| extern void *xahash_get_state_ptr(xahash_table_t *ht) |
| { |
| DEF_DEBUG_TIMER; |
| xahash_table_header_t *hth = (void *) ht; |
| void *ptr; |
| |
| START_DEBUG_TIMER; |
| _check_magic(ht); |
| |
| ptr = _get_state(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request table state=0x%"PRIxPTR"[%zu]", |
| __func__, (uintptr_t) ht, (uintptr_t) ptr, |
| hth->state_blob_bytes); |
| |
| END_DEBUG_TIMER; |
| return ptr; |
| } |
| |
| static bool _match_fixed_entry(xahash_table_t *ht, xahash_table_header_t *hth, |
| const xahash_hash_t hash, const void *key, |
| const size_t key_bytes, fentry_header_t *fe, |
| const int index, const int depth) |
| { |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| |
| if (!_is_fentry_set(ht, hth, fe)) { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] skip unset fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe, (uintptr_t) key, key_bytes, |
| hash); |
| return false; |
| } |
| |
| if (hth->match_func(_get_fentry_blob(ht, hth, fe), key, key_bytes, |
| _get_state(ht))) { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] %s()@0x%"PRIxPTR"=true accepted fentry[%d][%d]@0x%"PRIxPTR" == 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, hth->match_func_string, |
| (uintptr_t) hth->match_func, index, depth, |
| (uintptr_t) fe, (uintptr_t) key, key_bytes, hash); |
| return true; |
| } else { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] %s()@0x%"PRIxPTR"=false rejected fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, hth->match_func_string, |
| (uintptr_t) hth->match_func, index, depth, |
| (uintptr_t) fe, (uintptr_t) key, key_bytes, hash); |
| return false; |
| } |
| } |
| |
| static fentry_header_t *_find_fixed_entry(xahash_table_t *ht, |
| xahash_table_header_t *hth, |
| const xahash_hash_t hash, |
| const void *key, |
| const size_t key_bytes) |
| { |
| const int index = _fixed_hash_to_index(ht, hth, hash); |
| fentry_header_t *fe = _get_fentry(ht, hth, index); |
| int depth = 0; |
| |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| |
| do { |
| if (_match_fixed_entry(ht, hth, hash, key, key_bytes, fe, index, |
| depth)) |
| return fe; |
| |
| depth++; |
| } while ((fe = fe->next)); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] unable to find fentry for 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes, |
| hash); |
| |
| return NULL; |
| } |
| |
| static const fentry_header_t *_find_fixed_entry_const( |
| const xahash_table_t *ht, const xahash_table_header_t *hth, |
| const void *key, const size_t key_bytes) |
| { |
| const xahash_hash_t hash = |
| hth->hash_func(key, key_bytes, _get_state(ht)); |
| |
| /* de-constify and then return as const */ |
| |
| return _find_fixed_entry((xahash_table_t *) ht, |
| (xahash_table_header_t *) hth, hash, key, |
| key_bytes); |
| } |
| |
| static void *_find_fixed_entry_blob(const xahash_table_t *ht, |
| const xahash_table_header_t *hth, |
| const void *key, const size_t key_bytes) |
| { |
| const fentry_header_t *fe = |
| _find_fixed_entry_const(ht, hth, key, key_bytes); |
| |
| if (!fe || !_is_fentry_set(ht, hth, fe)) |
| return NULL; |
| |
| return _get_fentry_blob(ht, hth, fe); |
| } |
| |
| extern void *xahash_find_entry(const xahash_table_t *ht, const void *key, |
| const size_t key_bytes) |
| { |
| DEF_DEBUG_TIMER; |
| const xahash_table_header_t *hth; |
| void *ptr = NULL; |
| |
| _check_magic(ht); |
| |
| if (!ht || !key || !key_bytes) |
| return NULL; |
| |
| START_DEBUG_TIMER; |
| hth = _get_header_const(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request find entry for 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes, |
| hth->hash_func(key, key_bytes, _get_state(ht))); |
| |
| if (_is_fixed(ht, hth)) |
| ptr = _find_fixed_entry_blob(ht, hth, key, key_bytes); |
| else |
| fatal_abort("should never execute"); |
| |
| END_DEBUG_TIMER; |
| return ptr; |
| } |
| |
| static fentry_header_t *_append_fentry(xahash_table_t *ht, |
| xahash_table_header_t *hth, |
| fentry_header_t *fe, |
| const xahash_hash_t hash, |
| const int index, int *depth_ptr) |
| { |
| xassert(_is_fentry_set(ht, hth, fe)); |
| |
| /* Find unset entry if one is already in chain */ |
| while (fe) { |
| if (!_is_fentry_set(ht, hth, fe)) { |
| /* found empty entry so return it instead */ |
| return fe; |
| } |
| |
| if (!fe->next) { |
| size_t bytes = sizeof(*fe); |
| bytes += hth->bytes_per_entry_blob; |
| fe->next = xmalloc_nz(bytes); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] new linked fentry[%d][%d]@0x%"PRIxPTR" -> fentry[%d][%d]@0x%"PRIxPTR"=#0x%x", |
| __func__, (uintptr_t) ht, index, *depth_ptr, |
| (uintptr_t) fe, index, (*depth_ptr + 1), |
| (uintptr_t) fe->next, hash); |
| |
| (*depth_ptr)++; |
| return _init_fentry(ht, hth, fe->next, true, index, |
| *depth_ptr); |
| } |
| |
| fe = fe->next; |
| (*depth_ptr)++; |
| } |
| |
| fatal_abort("should never execute"); |
| } |
| |
| static void *_insert_fixed_entry(xahash_table_t *ht, xahash_table_header_t *hth, |
| const void *key, const size_t key_bytes) |
| { |
| int depth = -1, index = -1; |
| const xahash_hash_t hash = |
| hth->hash_func(key, key_bytes, _get_state(ht)); |
| fentry_header_t *fe = _find_fixed_entry(ht, hth, hash, key, key_bytes); |
| |
| if (fe) { |
| xassert(_is_fentry_set(ht, hth, fe)); |
| |
| log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe), |
| hth->bytes_per_entry_blob, |
| "%s: [hashtable@0x%"PRIxPTR"] ignoring duplicate insert on existing fentry@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, (uintptr_t) fe); |
| |
| return _get_fentry_blob(ht, hth, fe); |
| } |
| |
| /* not found: find and place a new entry */ |
| index = _fixed_hash_to_index(ht, hth, hash); |
| depth = 0; |
| fe = _get_fentry(ht, hth, index); |
| _check_fentry_magic(ht, hth, fe, index, depth); |
| |
| if (_is_fentry_set(ht, hth, fe)) { |
| /* need to add to linked list */ |
| fe = _append_fentry(ht, hth, fe, hash, index, &depth); |
| } |
| |
| /* check for clobbering */ |
| xassert(fe->flags == FENTRY_FLAG_UNSET); |
| |
| fe->flags = FENTRY_FLAG_SET; |
| |
| if (hth->on_insert_func) { |
| hth->on_insert_func(_get_fentry_blob(ht, hth, fe), key, |
| key_bytes, _get_state(ht)); |
| |
| log_flag_hex(DATA, _get_fentry_blob_ptr(ht, hth, fe), |
| hth->bytes_per_entry_blob, |
| "%s: [hashtable@0x%"PRIxPTR"] inserted after %s()@0x%"PRIxPTR" for fentry[%d][%d]@0x%"PRIxPTR"=#0x%x", |
| __func__, (uintptr_t) ht, |
| hth->on_insert_func_string, |
| (uintptr_t) hth->on_insert_func, index, depth, |
| (uintptr_t) fe, hash); |
| } else { |
| log_flag(DATA, |
| "%s: [hashtable@0x%" PRIxPTR |
| "] inserted fentry[%d][%d]@0x%" PRIxPTR "=#0x%x", |
| __func__, (uintptr_t) ht, index, depth, (uintptr_t) fe, |
| hash); |
| } |
| |
| return _get_fentry_blob(ht, hth, fe); |
| } |
| |
| extern void *xahash_insert_entry(xahash_table_t *ht, const void *key, |
| const size_t key_bytes) |
| { |
| DEF_DEBUG_TIMER; |
| xahash_table_header_t *hth; |
| void *ptr = NULL; |
| |
| xassert(key); |
| xassert(key_bytes > 0); |
| _check_magic(ht); |
| if (!ht || !key || !key_bytes) |
| return NULL; |
| |
| START_DEBUG_TIMER; |
| hth = _get_header(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request insert entry for 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes, |
| hth->hash_func(key, key_bytes, _get_state(ht))); |
| |
| if (_is_fixed(ht, hth)) |
| ptr = _insert_fixed_entry(ht, hth, key, key_bytes); |
| else |
| fatal_abort("should never execute"); |
| |
| END_DEBUG_TIMER; |
| |
| return ptr; |
| } |
| |
| static bool _find_and_free_fentry(xahash_table_t *ht, |
| xahash_table_header_t *hth, const void *key, |
| const size_t key_bytes) |
| { |
| const xahash_hash_t hash = |
| hth->hash_func(key, key_bytes, _get_state(ht)); |
| const int index = _fixed_hash_to_index(ht, hth, hash); |
| fentry_header_t *fe = _get_fentry(ht, hth, index); |
| fentry_header_t *parent = NULL; |
| int depth = 0; |
| |
| _check_fentry_magic(ht, hth, fe, index, -1); |
| |
| while (fe) { |
| if (_is_fentry_set(ht, hth, fe)) { |
| if (hth->match_func(_get_fentry_blob(ht, hth, fe), key, |
| key_bytes, _get_state(ht))) { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] matched fentry[%d][%d]@0x%"PRIxPTR" == 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe, (uintptr_t) key, |
| key_bytes, hash); |
| break; |
| } else { |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] match_func rejected fentry[%d][%d]@0x%"PRIxPTR" != 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, index, depth, |
| (uintptr_t) fe, (uintptr_t) key, |
| key_bytes, hash); |
| } |
| } |
| |
| parent = fe; |
| fe = fe->next; |
| depth++; |
| } |
| |
| if (!fe) |
| return false; |
| |
| _free_fentry(ht, hth, index, depth, fe, parent); |
| return true; |
| } |
| |
| extern bool xahash_free_entry(xahash_table_t *ht, const void *key, |
| const size_t key_bytes) |
| { |
| DEF_DEBUG_TIMER; |
| xahash_table_header_t *hth; |
| bool rc = false; |
| |
| _check_magic(ht); |
| xassert(key); |
| xassert(key_bytes > 0); |
| if (!ht || !key || !key_bytes) |
| return false; |
| |
| START_DEBUG_TIMER; |
| hth = _get_header(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request free entry for 0x%"PRIxPTR"[%zu]=#0x%x", |
| __func__, (uintptr_t) ht, (uintptr_t) key, key_bytes, |
| hth->hash_func(key, key_bytes, _get_state(ht))); |
| |
| if (_is_fixed(ht, hth)) { |
| rc = _find_and_free_fentry(ht, hth, key, key_bytes); |
| } else { |
| fatal_abort("should never execute"); |
| } |
| |
| END_DEBUG_TIMER; |
| return rc; |
| } |
| |
| static const char *_foreach_control_string(xahash_foreach_control_t control) |
| { |
| for (int i = 0; i < ARRAY_SIZE(foreach_control_strings); i++) |
| if (foreach_control_strings[i].value == control) |
| return foreach_control_strings[i].str; |
| |
| fatal_abort("should never execute"); |
| } |
| |
| static xahash_foreach_control_t _foreach_fentry_entry( |
| xahash_table_t *ht, xahash_table_header_t *hth, |
| xahash_foreach_func_t callback, const char *callback_string, void *arg, |
| fentry_header_t *fe, const int index, const int depth) |
| { |
| xahash_foreach_control_t rc; |
| |
| rc = callback(_get_fentry_blob(ht, hth, fe), _get_state(ht), arg); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] called after %s()@0x%"PRIxPTR"=%s for fentry[%d][%d]@0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, callback_string, |
| (uintptr_t) callback, _foreach_control_string(rc), index, |
| depth, (uintptr_t) fe); |
| |
| return rc; |
| } |
| |
| static int _foreach_fentry(xahash_table_t *ht, xahash_table_header_t *hth, |
| xahash_foreach_func_t callback, |
| const char *callback_string, void *arg) |
| { |
| uint32_t count = 0; |
| |
| for (uint32_t i = 0; i < hth->type.fixed.count; i++) { |
| uint32_t depth = 0; |
| fentry_header_t *fe = _get_fentry(ht, hth, i); |
| |
| do { |
| xahash_foreach_control_t rc; |
| |
| if (!_is_fentry_set(ht, hth, fe)) |
| continue; |
| |
| count++; |
| |
| rc = _foreach_fentry_entry(ht, hth, callback, |
| callback_string, arg, fe, i, |
| depth); |
| |
| xassert(rc > XAHASH_FOREACH_INVALID); |
| xassert(rc < XAHASH_FOREACH_INVALID_MAX); |
| |
| switch (rc) { |
| case XAHASH_FOREACH_CONT: |
| /* do nothing */ |
| break; |
| case XAHASH_FOREACH_STOP: |
| return count; |
| case XAHASH_FOREACH_FAIL: |
| return (count * -1); |
| case XAHASH_FOREACH_INVALID: |
| case XAHASH_FOREACH_INVALID_MAX: |
| fatal_abort("should never execute"); |
| } |
| } while (depth++, (fe = fe->next)); |
| } |
| |
| return count; |
| } |
| |
| extern int xahash_foreach_entry_funcname(xahash_table_t *ht, |
| xahash_foreach_func_t callback, |
| const char *callback_string, void *arg) |
| { |
| DEF_DEBUG_TIMER; |
| xahash_table_header_t *hth; |
| int rc = 0; |
| |
| _check_magic(ht); |
| if (!ht) |
| return rc; |
| |
| START_DEBUG_TIMER; |
| hth = _get_header(ht); |
| |
| log_flag(DATA, "%s: [hashtable@0x%"PRIxPTR"] request foreach func:%s()@0x%"PRIxPTR" arg:0x%"PRIxPTR, |
| __func__, (uintptr_t) ht, callback_string, |
| (uintptr_t) callback, (uintptr_t) arg); |
| |
| if (_is_fixed(ht, hth)) |
| rc = _foreach_fentry(ht, hth, callback, callback_string, arg); |
| else |
| fatal_abort("should never execute"); |
| |
| END_DEBUG_TIMER; |
| return rc; |
| } |