Commit 060193019a for openssl.org

commit 060193019ae75ca4dea1a8c14092502d48ea8500
Author: Neil Horman <nhorman@openssl.org>
Date:   Sun Mar 1 15:18:30 2026 -0500

    Add ability to extract computed hash from hashtable

    One thing we can do to speed up hash table lookups is to cache/reuse
    computed hash values when interrogating a hash table multiple times in
    rapid succession.

    We follow this pattern frequently when using hashtables:

    value = lookup_hash(key)
    if (value == NULL)
     value = new_value()
     insert_to_hash(key, value)

    Note that we use the same key for the lookup and the insert.  So if we
    had a way to preserve the value this key hashed to, we can avoid having
    to do a second hash computation during the lookup.

    These new macros give us that.  The HT_KEY structure now stores the
    computed hash value in the key, which can be extracted and reused by the
    caller with the HT_INIT_KEY_CACHED macro.  When set, the cached hash
    value is used, rather than needing to recompute the hash for any
    subsequent operations

    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    MergeDate: Tue Mar 24 16:23:05 2026
    (Merged from https://github.com/openssl/openssl/pull/30254)

diff --git a/crypto/hashtable/hashtable.c b/crypto/hashtable/hashtable.c
index af800c2cdf..065174feb7 100644
--- a/crypto/hashtable/hashtable.c
+++ b/crypto/hashtable/hashtable.c
@@ -700,12 +700,15 @@ int ossl_ht_insert(HT *h, HT_KEY *key, HT_VALUE *data, HT_VALUE **olddata)
     if (newval == NULL)
         goto out;

+    if (key->cached_hash)
+        hash = key->cached_hash;
+    else
+        hash = key->cached_hash = newval->value.key.cached_hash = h->config.ht_hash_fn(key);
+
     /*
      * we have to take our lock here to prevent other changes
      * to the bucket list
      */
-    hash = h->config.ht_hash_fn(key);
-
     for (i = 0;
         (rc = ossl_ht_insert_locked(h, hash, newval, olddata)) == -1
         && i < 4;
@@ -733,7 +736,10 @@ HT_VALUE *ossl_ht_get(HT *h, HT_KEY *key)
     uint64_t ehash;
     int lockless_reads = h->config.lockless_reads;

-    hash = h->config.ht_hash_fn(key);
+    if (key->cached_hash)
+        hash = key->cached_hash;
+    else
+        hash = key->cached_hash = h->config.ht_hash_fn(key);

     if (!h->config.no_rcu)
         md = ossl_rcu_deref(&h->md);
@@ -792,7 +798,10 @@ int ossl_ht_delete(HT *h, HT_KEY *key)
     if (h->config.lockless_reads)
         return 0;

-    hash = h->config.ht_hash_fn(key);
+    if (key->cached_hash)
+        hash = key->cached_hash;
+    else
+        hash = key->cached_hash = h->config.ht_hash_fn(key);

     neigh_idx = hash & h->md->neighborhood_mask;
     PREFETCH_NEIGHBORHOOD(h->md->neighborhoods[neigh_idx]);
diff --git a/include/internal/hashtable.h b/include/internal/hashtable.h
index 04b2d7f20a..320408bba4 100644
--- a/include/internal/hashtable.h
+++ b/include/internal/hashtable.h
@@ -23,6 +23,7 @@ typedef struct ht_internal_st HT;
  * Represents a key to a hashtable
  */
 typedef struct ht_key_header_st {
+    uint64_t cached_hash;
     size_t keysize;
     size_t bufsize;
     uint8_t *keybuf;
@@ -169,10 +170,22 @@ static ossl_inline ossl_unused int ossl_key_raw_copy(HT_KEY *key, const uint8_t
         (key)->keysize += tmplen;                                                      \
     } while (0)

+#define HT_INIT_KEY_CACHED(key, hash)         \
+    do {                                      \
+        HT_INIT_KEY((key));                   \
+        (key)->key_header.cached_hash = hash; \
+    } while (0)
+
+#define HT_KEY_GET_HASH(key) (key)->key_header.cached_hash
+
 /*
  * Resets a hash table key to a known state
  */
-#define HT_KEY_RESET(key) memset((key)->key_header.keybuf, 0, (key)->key_header.keysize)
+#define HT_KEY_RESET(key)                                               \
+    do {                                                                \
+        memset((key)->key_header.keybuf, 0, (key)->key_header.keysize); \
+        (key)->key_header.cached_hash = 0;                              \
+    } while (0)

 /*
  * Sets a scalar field in a hash table key
diff --git a/test/lhash_test.c b/test/lhash_test.c
index 9f44ffb7ca..eae2999f4e 100644
--- a/test/lhash_test.c
+++ b/test/lhash_test.c
@@ -244,6 +244,7 @@ static int test_int_hashtable(int idx)
     /* insert */
     HT_INIT_KEY(&key);
     for (i = 0; i < n_int_tests; i++) {
+        HT_KEY_RESET(&key);
         HT_SET_KEY_FIELD(&key, mykey, int_tests[i]);
         if (!TEST_int_eq(ossl_ht_test_int_insert(ht, TO_HT_KEY(&key),
                              &int_tests[i], NULL),
@@ -280,6 +281,7 @@ static int test_int_hashtable(int idx)

     /* delete */
     for (i = 0; i < n_dels; i++) {
+        HT_KEY_RESET(&key);
         HT_SET_KEY_FIELD(&key, mykey, dels[i].data);
         todel = ossl_ht_delete(ht, TO_HT_KEY(&key));
         if (dels[i].should_del) {
@@ -439,6 +441,7 @@ static int test_hashtable_stress(int idx)
             goto end;
         }
         *p = 3 * i + 1;
+        HT_KEY_RESET(&key);
         HT_SET_KEY_FIELD(&key, mykey, *p);
         if (!TEST_int_eq(ossl_ht_test_int_insert(h, TO_HT_KEY(&key),
                              p, NULL),
@@ -455,6 +458,7 @@ static int test_hashtable_stress(int idx)
     /* delete or get in a different order */
     for (i = 0; i < n; i++) {
         const int j = (7 * i + 4) % n * 3 + 1;
+        HT_KEY_RESET(&key);
         HT_SET_KEY_FIELD(&key, mykey, j);

         switch (idx % 2) {