11/*
2- * Copyright (c) 2018-2020 , Andreas Kling <andreas@ladybird.org>
2+ * Copyright (c) 2018-2025 , Andreas Kling <andreas@ladybird.org>
33 * Copyright (c) 2023, Jelle Raaijmakers <jelle@ladybird.org>
44 *
55 * SPDX-License-Identifier: BSD-2-Clause
@@ -123,8 +123,29 @@ class HashTable {
123123 static constexpr size_t grow_at_load_factor_percent = 80 ;
124124 static constexpr size_t grow_capacity_increase_percent = 60 ;
125125
126+ struct StoredHash {
127+ void set ([[maybe_unused]] u32 h)
128+ {
129+ if constexpr (TraitsForT::may_have_slow_equality_check ()) {
130+ hash = h;
131+ }
132+ }
133+ bool check (u32 h)
134+ {
135+ if constexpr (TraitsForT::may_have_slow_equality_check ()) {
136+ // If equality checks may be slow, we always store the hash and compare it first.
137+ return hash == h;
138+ } else {
139+ // If equality checks are fast, we don't store the hash and always return true.
140+ return true ;
141+ }
142+ }
143+ u32 hash;
144+ };
145+
126146 struct Bucket {
127147 BucketState state;
148+ StoredHash hash;
128149 alignas (T) u8 storage[sizeof (T)];
129150 T* slot () { return reinterpret_cast <T*>(storage); }
130151 T const * slot () const { return reinterpret_cast <T const *>(storage); }
@@ -134,6 +155,7 @@ class HashTable {
134155 OrderedBucket* previous;
135156 OrderedBucket* next;
136157 BucketState state;
158+ StoredHash hash;
137159 alignas (T) u8 storage[sizeof (T)];
138160 T* slot () { return reinterpret_cast <T*>(storage); }
139161 T const * slot () const { return reinterpret_cast <T const *>(storage); }
@@ -583,15 +605,15 @@ class HashTable {
583605 if (is_empty ())
584606 return nullptr ;
585607
586- hash %= m_capacity;
608+ size_t bucket_index = hash % m_capacity;
587609 for (;;) {
588- auto * bucket = &m_buckets[hash ];
610+ auto * bucket = &m_buckets[bucket_index ];
589611 if (bucket->state == BucketState::Free)
590612 return nullptr ;
591- if (predicate (*bucket->slot ()))
613+ if (bucket-> hash . check (hash) && predicate (*bucket->slot ()))
592614 return bucket;
593- if (++hash == m_capacity) [[unlikely]]
594- hash = 0 ;
615+ if (++bucket_index == m_capacity) [[unlikely]]
616+ bucket_index = 0 ;
595617 }
596618 }
597619
@@ -663,7 +685,8 @@ class HashTable {
663685 }
664686 };
665687
666- auto bucket_index = TraitsForT::hash (value) % m_capacity;
688+ u32 const hash = TraitsForT::hash (value);
689+ auto bucket_index = hash % m_capacity;
667690 size_t probe_length = 0 ;
668691 for (;;) {
669692 auto * bucket = &m_buckets[bucket_index];
@@ -672,13 +695,15 @@ class HashTable {
672695 if (bucket->state == BucketState::Free) {
673696 new (bucket->slot ()) T (forward<U>(value));
674697 bucket->state = bucket_state_for_probe_length (probe_length);
698+ bucket->hash .set (hash);
675699 update_collection_for_new_bucket (*bucket);
676700 ++m_size;
677701 return HashSetResult::InsertedNewEntry;
678702 }
679703
680704 // The bucket is already used, does it have an identical value?
681- if (TraitsForT::equals (*bucket->slot (), static_cast <T const &>(value))) {
705+ if (bucket->hash .check (hash)
706+ && TraitsForT::equals (*bucket->slot (), static_cast <T const &>(value))) {
682707 if (existing_entry_behavior == HashSetExistingEntryBehavior::Replace) {
683708 (*bucket->slot ()) = forward<U>(value);
684709 return HashSetResult::ReplacedExistingEntry;
@@ -697,6 +722,7 @@ class HashTable {
697722 // Write new bucket
698723 new (bucket->slot ()) T (forward<U>(value));
699724 bucket->state = bucket_state_for_probe_length (probe_length);
725+ bucket->hash .set (hash);
700726 probe_length = target_probe_length;
701727 if constexpr (IsOrdered)
702728 bucket->next = nullptr ;
0 commit comments