1 // Copyright 2016 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #pragma once 6 7 #include <zircon/assert.h> 8 #include <fbl/intrusive_container_utils.h> 9 #include <fbl/intrusive_pointer_traits.h> 10 #include <fbl/intrusive_single_list.h> 11 #include <fbl/macros.h> 12 13 #include <utility> 14 15 namespace fbl { 16 17 // Fwd decl of sanity checker class used by tests. 18 namespace tests { 19 namespace intrusive_containers { 20 class HashTableChecker; 21 } // namespace tests 22 } // namespace intrusive_containers 23 24 // DefaultHashTraits defines a default implementation of traits used to 25 // define the hash function for a hash table. 26 // 27 // At a minimum, a class or a struct which is to be used to define the 28 // hash traits of a hashtable must define... 29 // 30 // GetHash : A static method which take a constant reference to an instance of 31 // the container's KeyType and returns an instance of the container's 32 // HashType representing the hashed value of the key. The value must 33 // be on the range from [0, Container::kNumBuckets - 1] 34 // 35 // DefaultHashTraits generates a compliant implementation of hash traits taking 36 // its KeyType, ObjType, HashType and NumBuckets from template parameters. 37 // Users of DefaultHashTraits only need to implement a static method of ObjType 38 // named GetHash which takes a const reference to a KeyType and returns a 39 // HashType. The default implementation will automatically mod by the number of 40 // buckets given in the template parameters. If the user's hash function 41 // already automatically guarantees that the returned hash value will be in the 42 // proper range, he/she should implement their own hash traits to avoid the 43 // extra div/mod operation. 44 template <typename KeyType, 45 typename ObjType, 46 typename HashType, 47 HashType kNumBuckets> 48 struct DefaultHashTraits { 49 static_assert(is_unsigned_integer<HashType>::value, "HashTypes must be unsigned integers"); GetHashDefaultHashTraits50 static HashType GetHash(const KeyType& key) { 51 return static_cast<HashType>(ObjType::GetHash(key) % kNumBuckets); 52 } 53 }; 54 55 template <typename _KeyType, 56 typename _PtrType, 57 typename _BucketType = SinglyLinkedList<_PtrType>, 58 typename _HashType = size_t, 59 _HashType _NumBuckets = 37, 60 typename _KeyTraits = DefaultKeyedObjectTraits< 61 _KeyType, 62 typename internal::ContainerPtrTraits<_PtrType>::ValueType>, 63 typename _HashTraits = DefaultHashTraits< 64 _KeyType, 65 typename internal::ContainerPtrTraits<_PtrType>::ValueType, 66 _HashType, 67 _NumBuckets>> 68 class HashTable { 69 private: 70 // Private fwd decls of the iterator implementation. 71 template <typename IterTraits> class iterator_impl; 72 struct iterator_traits; 73 struct const_iterator_traits; 74 75 public: 76 // Pointer types/traits 77 using PtrType = _PtrType; 78 using PtrTraits = internal::ContainerPtrTraits<PtrType>; 79 using ValueType = typename PtrTraits::ValueType; 80 81 // Key types/traits 82 using KeyType = _KeyType; 83 using KeyTraits = _KeyTraits; 84 85 // Hash types/traits 86 using HashType = _HashType; 87 using HashTraits = _HashTraits; 88 89 // Bucket types/traits 90 using BucketType = _BucketType; 91 using NodeTraits = typename BucketType::NodeTraits; 92 93 // Declarations of the standard iterator types. 94 using iterator = iterator_impl<iterator_traits>; 95 using const_iterator = iterator_impl<const_iterator_traits>; 96 97 // An alias for the type of this specific HashTable<...> and its test sanity checker. 98 using ContainerType = HashTable<_KeyType, _PtrType, _BucketType, _HashType, 99 _NumBuckets, _KeyTraits, _HashTraits>; 100 using CheckerType = ::fbl::tests::intrusive_containers::HashTableChecker; 101 102 // The number of buckets should be a nice prime such as 37, 211, 389 unless 103 // The hash function is really good. Lots of cheap hash functions have 104 // hidden periods for which the mod with prime above 'mostly' fixes. 105 static constexpr HashType kNumBuckets = _NumBuckets; 106 107 // Hash tables only support constant order erase if their underlying bucket 108 // type does. 109 static constexpr bool SupportsConstantOrderErase = BucketType::SupportsConstantOrderErase; 110 static constexpr bool SupportsConstantOrderSize = true; 111 static constexpr bool IsAssociative = true; 112 static constexpr bool IsSequenced = false; 113 114 static_assert(kNumBuckets > 0, "Hash tables must have at least one bucket"); 115 static_assert(is_unsigned_integer<HashType>::value, "HashTypes must be unsigned integers"); 116 HashTable()117 constexpr HashTable() {} ~HashTable()118 ~HashTable() { ZX_DEBUG_ASSERT(PtrTraits::IsManaged || is_empty()); } 119 120 // Standard begin/end, cbegin/cend iterator accessors. begin()121 iterator begin() { return iterator(this, iterator::BEGIN); } begin()122 const_iterator begin() const { return const_iterator(this, const_iterator::BEGIN); } cbegin()123 const_iterator cbegin() const { return const_iterator(this, const_iterator::BEGIN); } 124 end()125 iterator end() { return iterator(this, iterator::END); } end()126 const_iterator end() const { return const_iterator(this, const_iterator::END); } cend()127 const_iterator cend() const { return const_iterator(this, const_iterator::END); } 128 129 // make_iterator : construct an iterator out of a reference to an object. make_iterator(ValueType & obj)130 iterator make_iterator(ValueType& obj) { 131 HashType ndx = GetHash(KeyTraits::GetKey(obj)); 132 return iterator(this, ndx, buckets_[ndx].make_iterator(obj)); 133 } 134 insert(const PtrType & ptr)135 void insert(const PtrType& ptr) { insert(PtrType(ptr)); } insert(PtrType && ptr)136 void insert(PtrType&& ptr) { 137 ZX_DEBUG_ASSERT(ptr != nullptr); 138 KeyType key = KeyTraits::GetKey(*ptr); 139 BucketType& bucket = GetBucket(key); 140 141 // Duplicate keys are disallowed. Debug assert if someone tries to to 142 // insert an element with a duplicate key. If the user thought that 143 // there might be a duplicate key in the HashTable already, he/she 144 // should have used insert_or_find() instead. 145 ZX_DEBUG_ASSERT(FindInBucket(bucket, key).IsValid() == false); 146 147 bucket.push_front(std::move(ptr)); 148 ++count_; 149 } 150 151 // insert_or_find 152 // 153 // Insert the element pointed to by ptr if it is not already in the 154 // HashTable, or find the element that the ptr collided with instead. 155 // 156 // 'iter' is an optional out parameter pointer to an iterator which 157 // will reference either the newly inserted item, or the item whose key 158 // collided with ptr. 159 // 160 // insert_or_find returns true if there was no collision and the item was 161 // successfully inserted, otherwise it returns false. 162 // 163 bool insert_or_find(const PtrType& ptr, iterator* iter = nullptr) { 164 return insert_or_find(PtrType(ptr), iter); 165 } 166 167 bool insert_or_find(PtrType&& ptr, iterator* iter = nullptr) { 168 ZX_DEBUG_ASSERT(ptr != nullptr); 169 KeyType key = KeyTraits::GetKey(*ptr); 170 HashType ndx = GetHash(key); 171 auto& bucket = buckets_[ndx]; 172 auto bucket_iter = FindInBucket(bucket, key); 173 174 if (bucket_iter.IsValid()) { 175 if (iter) *iter = iterator(this, ndx, bucket_iter); 176 return false; 177 } 178 179 bucket.push_front(std::move(ptr)); 180 ++count_; 181 if (iter) *iter = iterator(this, ndx, bucket.begin()); 182 return true; 183 } 184 185 // insert_or_replace 186 // 187 // Find the element in the hashtable with the same key as *ptr and replace 188 // it with ptr, then return the pointer to the element which was replaced. 189 // If no element in the hashtable shares a key with *ptr, simply add ptr to 190 // the hashtable and return nullptr. 191 // insert_or_replace(const PtrType & ptr)192 PtrType insert_or_replace(const PtrType& ptr) { 193 return insert_or_replace(PtrType(ptr)); 194 } 195 insert_or_replace(PtrType && ptr)196 PtrType insert_or_replace(PtrType&& ptr) { 197 ZX_DEBUG_ASSERT(ptr != nullptr); 198 KeyType key = KeyTraits::GetKey(*ptr); 199 HashType ndx = GetHash(key); 200 auto& bucket = buckets_[ndx]; 201 auto orig = PtrTraits::GetRaw(ptr); 202 203 PtrType replaced = bucket.replace_if( 204 [key](const ValueType& other) -> bool { 205 return KeyTraits::EqualTo(key, KeyTraits::GetKey(other)); 206 }, 207 std::move(ptr)); 208 209 if (orig == PtrTraits::GetRaw(replaced)) { 210 bucket.push_front(std::move(replaced)); 211 count_++; 212 return nullptr; 213 } 214 215 return replaced; 216 } 217 find(const KeyType & key)218 iterator find(const KeyType& key) { 219 HashType ndx = GetHash(key); 220 auto& bucket = buckets_[ndx]; 221 auto bucket_iter = FindInBucket(bucket, key); 222 223 return bucket_iter.IsValid() ? iterator(this, ndx, bucket_iter) 224 : iterator(this, iterator::END); 225 } 226 find(const KeyType & key)227 const_iterator find(const KeyType& key) const { 228 HashType ndx = GetHash(key); 229 const auto& bucket = buckets_[ndx]; 230 auto bucket_iter = FindInBucket(bucket, key); 231 232 return bucket_iter.IsValid() ? const_iterator(this, ndx, bucket_iter) 233 : const_iterator(this, const_iterator::END); 234 } 235 erase(const KeyType & key)236 PtrType erase(const KeyType& key) { 237 BucketType& bucket = GetBucket(key); 238 239 PtrType ret = internal::KeyEraseUtils<BucketType, KeyTraits>::erase(bucket, key); 240 if (ret != nullptr) 241 --count_; 242 243 return ret; 244 } 245 erase(const iterator & iter)246 PtrType erase(const iterator& iter) { 247 if (!iter.IsValid()) 248 return PtrType(nullptr); 249 250 return direct_erase(buckets_[iter.bucket_ndx_], *iter); 251 } 252 erase(ValueType & obj)253 PtrType erase(ValueType& obj) { 254 return direct_erase(GetBucket(obj), obj); 255 } 256 257 // clear 258 // 259 // Clear out the all of the hashtable buckets. For managed pointer types, 260 // this will release all references held by the hashtable to the objects 261 // which were in it. clear()262 void clear() { 263 for (auto& e : buckets_) 264 e.clear(); 265 count_ = 0; 266 } 267 268 // clear_unsafe 269 // 270 // Perform a clear_unsafe on all buckets and reset the internal count to 271 // zero. See comments in fbl/intrusive_single_list.h 272 // Think carefully before calling this! clear_unsafe()273 void clear_unsafe() { 274 static_assert(PtrTraits::IsManaged == false, 275 "clear_unsafe is not allowed for containers of managed pointers"); 276 277 for (auto& e : buckets_) 278 e.clear_unsafe(); 279 280 count_ = 0; 281 } 282 size()283 size_t size() const { return count_; } is_empty()284 bool is_empty() const { return count_ == 0; } 285 286 // erase_if 287 // 288 // Find the first member of the hash table which satisfies the predicate 289 // given by 'fn' and erase it from the list, returning a referenced pointer 290 // to the removed element. Return nullptr if no member satisfies the 291 // predicate. 292 template <typename UnaryFn> erase_if(UnaryFn fn)293 PtrType erase_if(UnaryFn fn) { 294 if (is_empty()) 295 return PtrType(nullptr); 296 297 for (HashType i = 0; i < kNumBuckets; ++i) { 298 auto& bucket = buckets_[i]; 299 if (!bucket.is_empty()) { 300 PtrType ret = bucket.erase_if(fn); 301 if (ret != nullptr) { 302 --count_; 303 return ret; 304 } 305 } 306 } 307 308 return PtrType(nullptr); 309 } 310 311 // find_if 312 // 313 // Find the first member of the hash table which satisfies the predicate 314 // given by 'fn' and return an iterator to it. Return end() if no member 315 // satisfies the predicate. 316 template <typename UnaryFn> find_if(UnaryFn fn)317 const_iterator find_if(UnaryFn fn) const { 318 for (auto iter = begin(); iter.IsValid(); ++iter) 319 if (fn(*iter)) 320 return iter; 321 322 return end(); 323 } 324 325 template <typename UnaryFn> find_if(UnaryFn fn)326 iterator find_if(UnaryFn fn) { 327 for (auto iter = begin(); iter.IsValid(); ++iter) 328 if (fn(*iter)) 329 return iter; 330 331 return end(); 332 } 333 334 private: 335 // The traits of a non-const iterator 336 struct iterator_traits { 337 using RefType = typename PtrTraits::RefType; 338 using RawPtrType = typename PtrTraits::RawPtrType; 339 using IterType = typename BucketType::iterator; 340 BucketBeginiterator_traits341 static IterType BucketBegin(BucketType& bucket) { return bucket.begin(); } BucketEnditerator_traits342 static IterType BucketEnd (BucketType& bucket) { return bucket.end(); } 343 }; 344 345 // The traits of a const iterator 346 struct const_iterator_traits { 347 using RefType = typename PtrTraits::ConstRefType; 348 using RawPtrType = typename PtrTraits::ConstRawPtrType; 349 using IterType = typename BucketType::const_iterator; 350 BucketBeginconst_iterator_traits351 static IterType BucketBegin(const BucketType& bucket) { return bucket.cbegin(); } BucketEndconst_iterator_traits352 static IterType BucketEnd (const BucketType& bucket) { return bucket.cend(); } 353 }; 354 355 // The shared implementation of the iterator 356 template <class IterTraits> 357 class iterator_impl { 358 public: iterator_impl()359 iterator_impl() { } iterator_impl(const iterator_impl & other)360 iterator_impl(const iterator_impl& other) { 361 hash_table_ = other.hash_table_; 362 bucket_ndx_ = other.bucket_ndx_; 363 iter_ = other.iter_; 364 } 365 366 iterator_impl& operator=(const iterator_impl& other) { 367 hash_table_ = other.hash_table_; 368 bucket_ndx_ = other.bucket_ndx_; 369 iter_ = other.iter_; 370 return *this; 371 } 372 IsValid()373 bool IsValid() const { return iter_.IsValid(); } 374 bool operator==(const iterator_impl& other) const { return iter_ == other.iter_; } 375 bool operator!=(const iterator_impl& other) const { return iter_ != other.iter_; } 376 377 // Prefix 378 iterator_impl& operator++() { 379 if (!IsValid()) return *this; 380 ZX_DEBUG_ASSERT(hash_table_); 381 382 // Bump the bucket iterator and go looking for a new bucket if the 383 // iterator has become invalid. 384 ++iter_; 385 advance_if_invalid_iter(); 386 387 return *this; 388 } 389 390 iterator_impl& operator--() { 391 // If we have never been bound to a HashTable instance, the we had 392 // better be invalid. 393 if (!hash_table_) { 394 ZX_DEBUG_ASSERT(!IsValid()); 395 return *this; 396 } 397 398 // Back up the bucket iterator. If it is still valid, then we are done. 399 --iter_; 400 if (iter_.IsValid()) 401 return *this; 402 403 // If the iterator is invalid after backing up, check previous 404 // buckets to see if they contain any nodes. 405 while (bucket_ndx_) { 406 --bucket_ndx_; 407 auto& bucket = GetBucket(bucket_ndx_); 408 if (!bucket.is_empty()) { 409 iter_ = --IterTraits::BucketEnd(bucket); 410 ZX_DEBUG_ASSERT(iter_.IsValid()); 411 return *this; 412 } 413 } 414 415 // Looks like we have backed up past the beginning. Update the 416 // bookkeeping to point at the end of the last bucket. 417 bucket_ndx_ = kNumBuckets - 1; 418 iter_ = IterTraits::BucketEnd(GetBucket(bucket_ndx_)); 419 420 return *this; 421 } 422 423 // Postfix 424 iterator_impl operator++(int) { 425 iterator_impl ret(*this); 426 ++(*this); 427 return ret; 428 } 429 430 iterator_impl operator--(int) { 431 iterator_impl ret(*this); 432 --(*this); 433 return ret; 434 } 435 CopyPointer()436 typename PtrTraits::PtrType CopyPointer() const { return iter_.CopyPointer(); } 437 typename IterTraits::RefType operator*() const { return iter_.operator*(); } 438 typename IterTraits::RawPtrType operator->() const { return iter_.operator->(); } 439 440 private: 441 friend ContainerType; 442 using IterType = typename IterTraits::IterType; 443 444 enum BeginTag { BEGIN }; 445 enum EndTag { END }; 446 iterator_impl(const ContainerType * hash_table,BeginTag)447 iterator_impl(const ContainerType* hash_table, BeginTag) 448 : hash_table_(hash_table), 449 bucket_ndx_(0), 450 iter_(IterTraits::BucketBegin(GetBucket(0))) { 451 advance_if_invalid_iter(); 452 } 453 iterator_impl(const ContainerType * hash_table,EndTag)454 iterator_impl(const ContainerType* hash_table, EndTag) 455 : hash_table_(hash_table), 456 bucket_ndx_(kNumBuckets - 1), 457 iter_(IterTraits::BucketEnd(GetBucket(kNumBuckets - 1))) { } 458 iterator_impl(const ContainerType * hash_table,HashType bucket_ndx,const IterType & iter)459 iterator_impl(const ContainerType* hash_table, HashType bucket_ndx, const IterType& iter) 460 : hash_table_(hash_table), 461 bucket_ndx_(bucket_ndx), 462 iter_(iter) { } 463 GetBucket(HashType ndx)464 BucketType& GetBucket(HashType ndx) { 465 return const_cast<ContainerType*>(hash_table_)->buckets_[ndx]; 466 } 467 advance_if_invalid_iter()468 void advance_if_invalid_iter() { 469 // If the iterator has run off the end of it's current bucket, then 470 // check to see if there are nodes in any of the remaining buckets. 471 if (!iter_.IsValid()) { 472 while (bucket_ndx_ < (kNumBuckets - 1)) { 473 ++bucket_ndx_; 474 auto& bucket = GetBucket(bucket_ndx_); 475 476 if (!bucket.is_empty()) { 477 iter_ = IterTraits::BucketBegin(bucket); 478 ZX_DEBUG_ASSERT(iter_.IsValid()); 479 break; 480 } else if (bucket_ndx_ == (kNumBuckets - 1)) { 481 iter_ = IterTraits::BucketEnd(bucket); 482 } 483 } 484 } 485 } 486 487 const ContainerType* hash_table_ = nullptr; 488 HashType bucket_ndx_ = 0; 489 IterType iter_; 490 }; 491 direct_erase(BucketType & bucket,ValueType & obj)492 PtrType direct_erase(BucketType& bucket, ValueType& obj) { 493 PtrType ret = internal::DirectEraseUtils<BucketType>::erase(bucket, obj); 494 495 if (ret != nullptr) 496 --count_; 497 498 return ret; 499 } 500 FindInBucket(BucketType & bucket,const KeyType & key)501 static typename BucketType::iterator FindInBucket(BucketType& bucket, 502 const KeyType& key) { 503 return bucket.find_if( 504 [key](const ValueType& other) -> bool { 505 return KeyTraits::EqualTo(key, KeyTraits::GetKey(other)); 506 }); 507 } 508 FindInBucket(const BucketType & bucket,const KeyType & key)509 static typename BucketType::const_iterator FindInBucket(const BucketType& bucket, 510 const KeyType& key) { 511 return bucket.find_if( 512 [key](const ValueType& other) -> bool { 513 return KeyTraits::EqualTo(key, KeyTraits::GetKey(other)); 514 }); 515 } 516 517 // The test framework's 'checker' class is our friend. 518 friend CheckerType; 519 520 // Iterators need to access our bucket array in order to iterate. 521 friend iterator; 522 friend const_iterator; 523 524 // Hash tables may not currently be copied, assigned or moved. 525 DISALLOW_COPY_ASSIGN_AND_MOVE(HashTable); 526 GetBucket(const KeyType & key)527 BucketType& GetBucket(const KeyType& key) { return buckets_[GetHash(key)]; } GetBucket(const ValueType & obj)528 BucketType& GetBucket(const ValueType& obj) { return GetBucket(KeyTraits::GetKey(obj)); } 529 GetHash(const KeyType & obj)530 static HashType GetHash(const KeyType& obj) { 531 HashType ret = HashTraits::GetHash(obj); 532 ZX_DEBUG_ASSERT((ret >= 0) && (ret < kNumBuckets)); 533 return ret; 534 } 535 536 size_t count_ = 0UL; 537 BucketType buckets_[kNumBuckets]; 538 }; 539 540 // Explicit declaration of constexpr storage. 541 #define HASH_TABLE_PROP(_type, _name) \ 542 template <typename KeyType, typename PtrType, typename BucketType, typename HashType, \ 543 HashType NumBuckets, typename KeyTraits, typename HashTraits> \ 544 constexpr _type HashTable<KeyType, PtrType, BucketType, HashType, \ 545 NumBuckets, KeyTraits, HashTraits>::_name 546 547 HASH_TABLE_PROP(HashType, kNumBuckets); 548 HASH_TABLE_PROP(bool, SupportsConstantOrderErase); 549 HASH_TABLE_PROP(bool, SupportsConstantOrderSize); 550 HASH_TABLE_PROP(bool, IsAssociative); 551 HASH_TABLE_PROP(bool, IsSequenced); 552 553 #undef HASH_TABLE_PROP 554 555 } // namespace fbl 556