From 213470b477aadde4f34b3f99c80f465d75b4ed86 Mon Sep 17 00:00:00 2001 From: Rhys Perry Date: Tue, 10 Mar 2026 09:43:15 +0000 Subject: [PATCH] util: allow any key for hash tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Acked-by: Marek Olšák Acked-by: Emma Anholt Reviewed-by: Alyssa Rosenzweig Part-of: --- src/compiler/nir/nir_range_analysis.c | 2 - src/util/hash_table.c | 103 +++++++------------------- src/util/hash_table.h | 14 ++-- 3 files changed, 33 insertions(+), 86 deletions(-) diff --git a/src/compiler/nir/nir_range_analysis.c b/src/compiler/nir/nir_range_analysis.c index f8d658d68df..0f6d8d3ec95 100644 --- a/src/compiler/nir/nir_range_analysis.c +++ b/src/compiler/nir/nir_range_analysis.c @@ -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); } diff --git a/src/util/hash_table.c b/src/util/hash_table.c index 54bd50c3cd4..53669a49188 100644 --- a/src/util/hash_table.c +++ b/src/util/hash_table.c @@ -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); + } } } diff --git a/src/util/hash_table.h b/src/util/hash_table.h index c3163d90e76..e4b7bf26386 100644 --- a/src/util/hash_table.h +++ b/src/util/hash_table.h @@ -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