util: allow any key for hash tables

We sometimes use this with non-pointer keys.

This removes a footgun at the cost of a larger entry size on 32-bit.

Signed-off-by: Rhys Perry <pendingchaos02@gmail.com>
Acked-by: Marek Olšák <marek.olsak@amd.com>
Acked-by: Emma Anholt <emma@anholt.net>
Reviewed-by: Alyssa Rosenzweig <alyssa.rosenzweig@intel.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40318>
This commit is contained in:
Rhys Perry 2026-03-10 09:43:15 +00:00 committed by Marge Bot
parent d348654a75
commit 213470b477
3 changed files with 33 additions and 86 deletions

View file

@ -2143,7 +2143,6 @@ nir_unsigned_upper_bound(nir_shader *shader, struct hash_table *range_ht,
push_scalar_query(&state, scalar);
_mesa_hash_table_set_deleted_key(range_ht, (void *)(uintptr_t)UINT32_MAX);
return perform_analysis(&state);
}
@ -2547,6 +2546,5 @@ nir_def_num_lsb_zero(struct hash_table *numlsb_ht, nir_scalar def)
push_scalar_query(&state, def);
_mesa_hash_table_set_deleted_key(numlsb_ht, (void *)(uintptr_t)UINT32_MAX);
return perform_analysis(&state);
}

View file

@ -73,8 +73,6 @@ uint_key(unsigned id)
return (void *)(uintptr_t) id;
}
static const uint32_t deleted_key_value;
/**
* From Knuth -- a good choice for hash/rehash values is p, p-2 where
* p and p-2 are both prime. These tables are sized to have an extra 10%
@ -136,30 +134,6 @@ static const struct {
ENTRY(2147483648ul, 2362232233ul, 2362232231ul )
};
ASSERTED static inline bool
key_pointer_is_reserved(const struct hash_table *ht, const void *key)
{
return key == NULL || key == ht->deleted_key;
}
static int
entry_is_free(const struct hash_entry *entry)
{
return entry->key == NULL;
}
static int
entry_is_deleted(const struct hash_table *ht, struct hash_entry *entry)
{
return entry->key == ht->deleted_key;
}
static int
entry_is_present(const struct hash_table *ht, struct hash_entry *entry)
{
return entry->key != NULL && entry->key != ht->deleted_key;
}
void
_mesa_hash_table_init(struct hash_table *ht,
void *mem_ctx,
@ -182,7 +156,6 @@ _mesa_hash_table_init(struct hash_table *ht,
memset(ht->table, 0, sizeof(ht->_initial_storage));
ht->entries = 0;
ht->deleted_entries = 0;
ht->deleted_key = &deleted_key_value;
}
static void
@ -253,16 +226,13 @@ key_u32_equals(const void *a, const void *b)
struct hash_table *
_mesa_hash_table_create_u32_keys(void *mem_ctx)
{
struct hash_table *ht = _mesa_hash_table_create(mem_ctx, key_u32_hash, key_u32_equals);
_mesa_hash_table_set_deleted_key(ht, (void *)(uintptr_t)UINT32_MAX);
return ht;
return _mesa_hash_table_create(mem_ctx, key_u32_hash, key_u32_equals);
}
void
_mesa_hash_table_init_u32_keys(struct hash_table *ht, void *mem_ctx)
{
_mesa_hash_table_init(ht, mem_ctx, key_u32_hash, key_u32_equals);
_mesa_hash_table_set_deleted_key(ht, (void *)(uintptr_t)UINT32_MAX);
}
/* Copy the hash table from src to dst. */
@ -369,10 +339,11 @@ _mesa_hash_table_clear(struct hash_table *ht,
if (delete_function) {
for (entry = ht->table; entry != ht->table + ht->size; entry++) {
if (entry_is_present(ht, entry))
if (entry->present)
delete_function(entry);
entry->key = NULL;
entry->present = false;
entry->deleted = false;
}
ht->entries = 0;
ht->deleted_entries = 0;
@ -380,27 +351,9 @@ _mesa_hash_table_clear(struct hash_table *ht,
hash_table_clear_fast(ht);
}
/** Sets the value of the key pointer used for deleted entries in the table.
*
* The assumption is that usually keys are actual pointers, so we use a
* default value of a pointer to an arbitrary piece of storage in the library.
* But in some cases a consumer wants to store some other sort of value in the
* table, like a uint32_t, in which case that pointer may conflict with one of
* their valid keys. This lets that user select a safe value.
*
* This must be called before any keys are actually deleted from the table.
*/
void
_mesa_hash_table_set_deleted_key(struct hash_table *ht, const void *deleted_key)
{
ht->deleted_key = deleted_key;
}
static struct hash_entry *
hash_table_search(const struct hash_table *ht, uint32_t hash, const void *key)
{
assert(!key_pointer_is_reserved(ht, key));
uint32_t size = ht->size;
uint32_t start_hash_address = util_fast_urem32(hash, size, ht->size_magic);
uint32_t double_hash = 1 + util_fast_urem32(hash, ht->rehash,
@ -410,12 +363,11 @@ hash_table_search(const struct hash_table *ht, uint32_t hash, const void *key)
do {
struct hash_entry *entry = ht->table + hash_address;
if (entry_is_free(entry)) {
if (!entry->present && !entry->deleted) {
return NULL;
} else if (entry_is_present(ht, entry) && entry->hash == hash) {
if (ht->key_equals_function(key, entry->key)) {
return entry;
}
} else if (entry->present && entry->hash == hash &&
ht->key_equals_function(key, entry->key)) {
return entry;
}
hash_address += double_hash;
@ -463,10 +415,11 @@ hash_table_insert_rehash(struct hash_table *ht, uint32_t hash,
do {
struct hash_entry *entry = ht->table + hash_address;
if (likely(entry->key == NULL)) {
if (likely(!entry->present)) {
entry->hash = hash;
entry->key = key;
entry->data = data;
entry->present = true;
return;
}
@ -536,8 +489,6 @@ hash_table_get_entry(struct hash_table *ht, uint32_t hash, const void *key)
{
struct hash_entry *available_entry = NULL;
assert(!key_pointer_is_reserved(ht, key));
if (ht->entries >= ht->max_entries) {
_mesa_hash_table_rehash(ht, ht->size_index + 1);
} else if (ht->deleted_entries + ht->entries >= ht->max_entries) {
@ -552,11 +503,11 @@ hash_table_get_entry(struct hash_table *ht, uint32_t hash, const void *key)
do {
struct hash_entry *entry = ht->table + hash_address;
if (!entry_is_present(ht, entry)) {
if (!entry->present) {
/* Stash the first available entry we find */
if (available_entry == NULL)
available_entry = entry;
if (entry_is_free(entry))
if (!entry->deleted)
break;
}
@ -571,8 +522,7 @@ hash_table_get_entry(struct hash_table *ht, uint32_t hash, const void *key)
* required to avoid memory leaks, perform a search
* before inserting.
*/
if (!entry_is_deleted(ht, entry) &&
entry->hash == hash &&
if (entry->present && entry->hash == hash &&
ht->key_equals_function(key, entry->key))
return entry;
@ -582,7 +532,7 @@ hash_table_get_entry(struct hash_table *ht, uint32_t hash, const void *key)
} while (hash_address != start_hash_address);
if (available_entry) {
if (entry_is_deleted(ht, available_entry))
if (available_entry->deleted)
ht->deleted_entries--;
available_entry->hash = hash;
ht->entries++;
@ -604,6 +554,8 @@ hash_table_insert(struct hash_table *ht, uint32_t hash,
if (entry) {
entry->key = key;
entry->data = data;
entry->present = true;
entry->deleted = false;
}
return entry;
@ -643,7 +595,8 @@ _mesa_hash_table_remove(struct hash_table *ht,
if (!entry)
return;
entry->key = ht->deleted_key;
entry->present = false;
entry->deleted = true;
ht->entries--;
ht->deleted_entries++;
}
@ -673,7 +626,7 @@ _mesa_hash_table_next_entry_unsafe(const struct hash_table *ht, struct hash_entr
else
entry = entry + 1;
if (entry != ht->table + ht->size)
return entry->key ? entry : _mesa_hash_table_next_entry_unsafe(ht, entry);
return entry->present ? entry : _mesa_hash_table_next_entry_unsafe(ht, entry);
return NULL;
}
@ -694,9 +647,8 @@ _mesa_hash_table_next_entry(struct hash_table *ht,
entry = entry + 1;
for (; entry != ht->table + ht->size; entry++) {
if (entry_is_present(ht, entry)) {
if (entry->present)
return entry;
}
}
return NULL;
@ -721,15 +673,13 @@ _mesa_hash_table_random_entry(struct hash_table *ht,
return NULL;
for (entry = ht->table + i; entry != ht->table + ht->size; entry++) {
if (entry_is_present(ht, entry) &&
(!predicate || predicate(entry))) {
if (entry->present && (!predicate || predicate(entry))) {
return entry;
}
}
for (entry = ht->table; entry != ht->table + i; entry++) {
if (entry_is_present(ht, entry) &&
(!predicate || predicate(entry))) {
if (entry->present && (!predicate || predicate(entry))) {
return entry;
}
}
@ -943,8 +893,6 @@ _mesa_hash_table_u64_create(void *mem_ctx)
_mesa_hash_table_set_destructor(&ht->table, _mesa_hash_table_u64_delete_keys);
}
_mesa_hash_table_set_deleted_key(&ht->table, uint_key(DELETED_KEY_VALUE));
return ht;
}
@ -1008,10 +956,13 @@ _mesa_hash_table_u64_insert(struct hash_table_u64 *ht, uint64_t key,
}
entry->data = data;
if (!entry_is_present(&ht->table, entry))
if (!entry->present) {
entry->key = _key;
else
entry->present = true;
entry->deleted = false;
} else {
FREE(_key);
}
}
}

View file

@ -39,6 +39,8 @@ extern "C" {
struct hash_entry {
uint32_t hash;
bool present;
bool deleted;
const void *key;
void *data;
};
@ -49,7 +51,6 @@ struct hash_table {
uint32_t (*key_hash_function)(const void *key);
bool (*key_equals_function)(const void *a, const void *b);
void (*table_destructor)(void *data);
const void *deleted_key;
uint32_t size;
uint32_t rehash;
uint64_t size_magic;
@ -86,7 +87,6 @@ void
_mesa_hash_table_fini(struct hash_table *ht,
void (*delete_function)(struct hash_entry *entry));
/* key == 0 and key == UINT32_MAX are not allowed */
/* It's preferred to use _mesa_hash_table_init_u32_keys instead of this to skip ralloc. */
struct hash_table *
_mesa_hash_table_create_u32_keys(void *mem_ctx);
@ -103,8 +103,6 @@ void _mesa_hash_table_destroy(struct hash_table *ht,
void (*delete_function)(struct hash_entry *entry));
void _mesa_hash_table_clear(struct hash_table *ht,
void (*delete_function)(struct hash_entry *entry));
void _mesa_hash_table_set_deleted_key(struct hash_table *ht,
const void *deleted_key);
static inline uint32_t _mesa_hash_table_num_entries(const struct hash_table *ht)
{
@ -179,10 +177,10 @@ _mesa_hash_table_reserve(struct hash_table *ht, unsigned size);
* This foreach function destroys the table as it iterates.
* It is not safe to use when inserting or removing entries.
*/
#define hash_table_foreach_remove(ht, entry) \
for (struct hash_entry *entry = _mesa_hash_table_next_entry_unsafe(ht, NULL); \
(ht)->entries; \
entry->hash = 0, entry->key = (void*)NULL, entry->data = NULL, \
#define hash_table_foreach_remove(ht, entry) \
for (struct hash_entry *entry = _mesa_hash_table_next_entry_unsafe(ht, NULL); \
(ht)->entries; \
entry->hash = 0, entry->present = false, entry->deleted = false, entry->data = NULL, \
(ht)->entries--, entry = _mesa_hash_table_next_entry_unsafe(ht, entry))
static inline void