1 /*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef ART_LIBARTBASE_BASE_HASH_SET_H_
18 #define ART_LIBARTBASE_BASE_HASH_SET_H_
19
20 #include <stdint.h>
21
22 #include <functional>
23 #include <iterator>
24 #include <memory>
25 #include <type_traits>
26 #include <utility>
27
28 #include <android-base/logging.h>
29
30 #include "bit_utils.h"
31 #include "macros.h"
32
33 namespace art {
34
35 // Returns true if an item is empty.
36 template <class T>
37 class DefaultEmptyFn {
38 public:
MakeEmpty(T & item)39 void MakeEmpty(T& item) const {
40 item = T();
41 }
IsEmpty(const T & item)42 bool IsEmpty(const T& item) const {
43 return item == T();
44 }
45 };
46
47 template <class T>
48 class DefaultEmptyFn<T*> {
49 public:
MakeEmpty(T * & item)50 void MakeEmpty(T*& item) const {
51 item = nullptr;
52 }
IsEmpty(T * const & item)53 bool IsEmpty(T* const& item) const {
54 return item == nullptr;
55 }
56 };
57
58 // Low memory version of a hash set, uses less memory than std::unordered_set since elements aren't
59 // boxed. Uses linear probing to resolve collisions.
60 // EmptyFn needs to implement two functions MakeEmpty(T& item) and IsEmpty(const T& item).
61 // TODO: We could get rid of this requirement by using a bitmap, though maybe this would be slower
62 // and more complicated.
63 template <class T, class EmptyFn = DefaultEmptyFn<T>, class HashFn = std::hash<T>,
64 class Pred = std::equal_to<T>, class Alloc = std::allocator<T>>
65 class HashSet {
66 template <class Elem, class HashSetType>
67 class BaseIterator : std::iterator<std::forward_iterator_tag, Elem> {
68 public:
69 BaseIterator(const BaseIterator&) = default;
70 BaseIterator(BaseIterator&&) = default;
BaseIterator(HashSetType * hash_set,size_t index)71 BaseIterator(HashSetType* hash_set, size_t index) : index_(index), hash_set_(hash_set) {
72 }
73 BaseIterator& operator=(const BaseIterator&) = default;
74 BaseIterator& operator=(BaseIterator&&) = default;
75
76 bool operator==(const BaseIterator& other) const {
77 return hash_set_ == other.hash_set_ && this->index_ == other.index_;
78 }
79
80 bool operator!=(const BaseIterator& other) const {
81 return !(*this == other);
82 }
83
84 BaseIterator operator++() { // Value after modification.
85 this->index_ = this->NextNonEmptySlot(this->index_, hash_set_);
86 return *this;
87 }
88
89 BaseIterator operator++(int) {
90 BaseIterator temp = *this;
91 this->index_ = this->NextNonEmptySlot(this->index_, hash_set_);
92 return temp;
93 }
94
95 Elem& operator*() const {
96 DCHECK(!hash_set_->IsFreeSlot(this->index_));
97 return hash_set_->ElementForIndex(this->index_);
98 }
99
100 Elem* operator->() const {
101 return &**this;
102 }
103
104 // TODO: Operator -- --(int) (and use std::bidirectional_iterator_tag)
105
106 private:
107 size_t index_;
108 HashSetType* hash_set_;
109
NextNonEmptySlot(size_t index,const HashSet * hash_set)110 size_t NextNonEmptySlot(size_t index, const HashSet* hash_set) const {
111 const size_t num_buckets = hash_set->NumBuckets();
112 DCHECK_LT(index, num_buckets);
113 do {
114 ++index;
115 } while (index < num_buckets && hash_set->IsFreeSlot(index));
116 return index;
117 }
118
119 friend class HashSet;
120 };
121
122 public:
123 using value_type = T;
124 using allocator_type = Alloc;
125 using reference = T&;
126 using const_reference = const T&;
127 using pointer = T*;
128 using const_pointer = const T*;
129 using iterator = BaseIterator<T, HashSet>;
130 using const_iterator = BaseIterator<const T, const HashSet>;
131 using size_type = size_t;
132 using difference_type = ptrdiff_t;
133
134 static constexpr double kDefaultMinLoadFactor = 0.4;
135 static constexpr double kDefaultMaxLoadFactor = 0.7;
136 static constexpr size_t kMinBuckets = 1000;
137
138 // If we don't own the data, this will create a new array which owns the data.
Clear()139 void Clear() {
140 DeallocateStorage();
141 num_elements_ = 0;
142 elements_until_expand_ = 0;
143 }
144
HashSet()145 HashSet() : HashSet(kDefaultMinLoadFactor, kDefaultMaxLoadFactor) {}
146
HashSet(double min_load_factor,double max_load_factor)147 HashSet(double min_load_factor, double max_load_factor) noexcept
148 : num_elements_(0u),
149 num_buckets_(0u),
150 elements_until_expand_(0u),
151 owns_data_(false),
152 data_(nullptr),
153 min_load_factor_(min_load_factor),
154 max_load_factor_(max_load_factor) {
155 DCHECK_GT(min_load_factor, 0.0);
156 DCHECK_LT(max_load_factor, 1.0);
157 }
158
HashSet(const allocator_type & alloc)159 explicit HashSet(const allocator_type& alloc) noexcept
160 : allocfn_(alloc),
161 hashfn_(),
162 emptyfn_(),
163 pred_(),
164 num_elements_(0u),
165 num_buckets_(0u),
166 elements_until_expand_(0u),
167 owns_data_(false),
168 data_(nullptr),
169 min_load_factor_(kDefaultMinLoadFactor),
170 max_load_factor_(kDefaultMaxLoadFactor) {
171 }
172
HashSet(const HashSet & other)173 HashSet(const HashSet& other) noexcept
174 : allocfn_(other.allocfn_),
175 hashfn_(other.hashfn_),
176 emptyfn_(other.emptyfn_),
177 pred_(other.pred_),
178 num_elements_(other.num_elements_),
179 num_buckets_(0),
180 elements_until_expand_(other.elements_until_expand_),
181 owns_data_(false),
182 data_(nullptr),
183 min_load_factor_(other.min_load_factor_),
184 max_load_factor_(other.max_load_factor_) {
185 AllocateStorage(other.NumBuckets());
186 for (size_t i = 0; i < num_buckets_; ++i) {
187 ElementForIndex(i) = other.data_[i];
188 }
189 }
190
191 // noexcept required so that the move constructor is used instead of copy constructor.
192 // b/27860101
HashSet(HashSet && other)193 HashSet(HashSet&& other) noexcept
194 : allocfn_(std::move(other.allocfn_)),
195 hashfn_(std::move(other.hashfn_)),
196 emptyfn_(std::move(other.emptyfn_)),
197 pred_(std::move(other.pred_)),
198 num_elements_(other.num_elements_),
199 num_buckets_(other.num_buckets_),
200 elements_until_expand_(other.elements_until_expand_),
201 owns_data_(other.owns_data_),
202 data_(other.data_),
203 min_load_factor_(other.min_load_factor_),
204 max_load_factor_(other.max_load_factor_) {
205 other.num_elements_ = 0u;
206 other.num_buckets_ = 0u;
207 other.elements_until_expand_ = 0u;
208 other.owns_data_ = false;
209 other.data_ = nullptr;
210 }
211
212 // Construct from existing data.
213 // Read from a block of memory, if make_copy_of_data is false, then data_ points to within the
214 // passed in ptr_.
HashSet(const uint8_t * ptr,bool make_copy_of_data,size_t * read_count)215 HashSet(const uint8_t* ptr, bool make_copy_of_data, size_t* read_count) noexcept {
216 uint64_t temp;
217 size_t offset = 0;
218 offset = ReadFromBytes(ptr, offset, &temp);
219 num_elements_ = static_cast<uint64_t>(temp);
220 offset = ReadFromBytes(ptr, offset, &temp);
221 num_buckets_ = static_cast<uint64_t>(temp);
222 CHECK_LE(num_elements_, num_buckets_);
223 offset = ReadFromBytes(ptr, offset, &temp);
224 elements_until_expand_ = static_cast<uint64_t>(temp);
225 offset = ReadFromBytes(ptr, offset, &min_load_factor_);
226 offset = ReadFromBytes(ptr, offset, &max_load_factor_);
227 if (!make_copy_of_data) {
228 owns_data_ = false;
229 data_ = const_cast<T*>(reinterpret_cast<const T*>(ptr + offset));
230 offset += sizeof(*data_) * num_buckets_;
231 } else {
232 AllocateStorage(num_buckets_);
233 // Write elements, not that this may not be safe for cross compilation if the elements are
234 // pointer sized.
235 for (size_t i = 0; i < num_buckets_; ++i) {
236 offset = ReadFromBytes(ptr, offset, &data_[i]);
237 }
238 }
239 // Caller responsible for aligning.
240 *read_count = offset;
241 }
242
243 // Returns how large the table is after being written. If target is null, then no writing happens
244 // but the size is still returned. Target must be 8 byte aligned.
WriteToMemory(uint8_t * ptr)245 size_t WriteToMemory(uint8_t* ptr) const {
246 size_t offset = 0;
247 offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(num_elements_));
248 offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(num_buckets_));
249 offset = WriteToBytes(ptr, offset, static_cast<uint64_t>(elements_until_expand_));
250 offset = WriteToBytes(ptr, offset, min_load_factor_);
251 offset = WriteToBytes(ptr, offset, max_load_factor_);
252 // Write elements, not that this may not be safe for cross compilation if the elements are
253 // pointer sized.
254 for (size_t i = 0; i < num_buckets_; ++i) {
255 offset = WriteToBytes(ptr, offset, data_[i]);
256 }
257 // Caller responsible for aligning.
258 return offset;
259 }
260
~HashSet()261 ~HashSet() {
262 DeallocateStorage();
263 }
264
265 HashSet& operator=(HashSet&& other) noexcept {
266 HashSet(std::move(other)).swap(*this); // NOLINT [runtime/explicit] [5]
267 return *this;
268 }
269
270 HashSet& operator=(const HashSet& other) noexcept {
271 HashSet(other).swap(*this); // NOLINT(runtime/explicit) - a case of lint gone mad.
272 return *this;
273 }
274
275 // Lower case for c++11 for each.
begin()276 iterator begin() {
277 iterator ret(this, 0);
278 if (num_buckets_ != 0 && IsFreeSlot(ret.index_)) {
279 ++ret; // Skip all the empty slots.
280 }
281 return ret;
282 }
283
284 // Lower case for c++11 for each. const version.
begin()285 const_iterator begin() const {
286 const_iterator ret(this, 0);
287 if (num_buckets_ != 0 && IsFreeSlot(ret.index_)) {
288 ++ret; // Skip all the empty slots.
289 }
290 return ret;
291 }
292
293 // Lower case for c++11 for each.
end()294 iterator end() {
295 return iterator(this, NumBuckets());
296 }
297
298 // Lower case for c++11 for each. const version.
end()299 const_iterator end() const {
300 return const_iterator(this, NumBuckets());
301 }
302
Empty()303 bool Empty() const {
304 return Size() == 0;
305 }
306
307 // Return true if the hash set has ownership of the underlying data.
OwnsData()308 bool OwnsData() const {
309 return owns_data_;
310 }
311
312 // Erase algorithm:
313 // Make an empty slot where the iterator is pointing.
314 // Scan forwards until we hit another empty slot.
315 // If an element in between doesn't rehash to the range from the current empty slot to the
316 // iterator. It must be before the empty slot, in that case we can move it to the empty slot
317 // and set the empty slot to be the location we just moved from.
318 // Relies on maintaining the invariant that there's no empty slots from the 'ideal' index of an
319 // element to its actual location/index.
Erase(iterator it)320 iterator Erase(iterator it) {
321 // empty_index is the index that will become empty.
322 size_t empty_index = it.index_;
323 DCHECK(!IsFreeSlot(empty_index));
324 size_t next_index = empty_index;
325 bool filled = false; // True if we filled the empty index.
326 while (true) {
327 next_index = NextIndex(next_index);
328 T& next_element = ElementForIndex(next_index);
329 // If the next element is empty, we are done. Make sure to clear the current empty index.
330 if (emptyfn_.IsEmpty(next_element)) {
331 emptyfn_.MakeEmpty(ElementForIndex(empty_index));
332 break;
333 }
334 // Otherwise try to see if the next element can fill the current empty index.
335 const size_t next_hash = hashfn_(next_element);
336 // Calculate the ideal index, if it is within empty_index + 1 to next_index then there is
337 // nothing we can do.
338 size_t next_ideal_index = IndexForHash(next_hash);
339 // Loop around if needed for our check.
340 size_t unwrapped_next_index = next_index;
341 if (unwrapped_next_index < empty_index) {
342 unwrapped_next_index += NumBuckets();
343 }
344 // Loop around if needed for our check.
345 size_t unwrapped_next_ideal_index = next_ideal_index;
346 if (unwrapped_next_ideal_index < empty_index) {
347 unwrapped_next_ideal_index += NumBuckets();
348 }
349 if (unwrapped_next_ideal_index <= empty_index ||
350 unwrapped_next_ideal_index > unwrapped_next_index) {
351 // If the target index isn't within our current range it must have been probed from before
352 // the empty index.
353 ElementForIndex(empty_index) = std::move(next_element);
354 filled = true; // TODO: Optimize
355 empty_index = next_index;
356 }
357 }
358 --num_elements_;
359 // If we didn't fill the slot then we need go to the next non free slot.
360 if (!filled) {
361 ++it;
362 }
363 return it;
364 }
365
366 // Find an element, returns end() if not found.
367 // Allows custom key (K) types, example of when this is useful:
368 // Set of Class* sorted by name, want to find a class with a name but can't allocate a dummy
369 // object in the heap for performance solution.
370 template <typename K>
Find(const K & key)371 iterator Find(const K& key) {
372 return FindWithHash(key, hashfn_(key));
373 }
374
375 template <typename K>
Find(const K & key)376 const_iterator Find(const K& key) const {
377 return FindWithHash(key, hashfn_(key));
378 }
379
380 template <typename K>
FindWithHash(const K & key,size_t hash)381 iterator FindWithHash(const K& key, size_t hash) {
382 return iterator(this, FindIndex(key, hash));
383 }
384
385 template <typename K>
FindWithHash(const K & key,size_t hash)386 const_iterator FindWithHash(const K& key, size_t hash) const {
387 return const_iterator(this, FindIndex(key, hash));
388 }
389
390 // Insert an element, allows duplicates.
391 template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
Insert(U && element)392 void Insert(U&& element) {
393 InsertWithHash(std::forward<U>(element), hashfn_(element));
394 }
395
396 template <typename U, typename = typename std::enable_if<std::is_convertible<U, T>::value>::type>
InsertWithHash(U && element,size_t hash)397 void InsertWithHash(U&& element, size_t hash) {
398 DCHECK_EQ(hash, hashfn_(element));
399 if (num_elements_ >= elements_until_expand_) {
400 Expand();
401 DCHECK_LT(num_elements_, elements_until_expand_);
402 }
403 const size_t index = FirstAvailableSlot(IndexForHash(hash));
404 data_[index] = std::forward<U>(element);
405 ++num_elements_;
406 }
407
Size()408 size_t Size() const {
409 return num_elements_;
410 }
411
swap(HashSet & other)412 void swap(HashSet& other) {
413 // Use argument-dependent lookup with fall-back to std::swap() for function objects.
414 using std::swap;
415 swap(allocfn_, other.allocfn_);
416 swap(hashfn_, other.hashfn_);
417 swap(emptyfn_, other.emptyfn_);
418 swap(pred_, other.pred_);
419 std::swap(data_, other.data_);
420 std::swap(num_buckets_, other.num_buckets_);
421 std::swap(num_elements_, other.num_elements_);
422 std::swap(elements_until_expand_, other.elements_until_expand_);
423 std::swap(min_load_factor_, other.min_load_factor_);
424 std::swap(max_load_factor_, other.max_load_factor_);
425 std::swap(owns_data_, other.owns_data_);
426 }
427
get_allocator()428 allocator_type get_allocator() const {
429 return allocfn_;
430 }
431
ShrinkToMaximumLoad()432 void ShrinkToMaximumLoad() {
433 Resize(Size() / max_load_factor_);
434 }
435
436 // Reserve enough room to insert until Size() == num_elements without requiring to grow the hash
437 // set. No-op if the hash set is already large enough to do this.
Reserve(size_t num_elements)438 void Reserve(size_t num_elements) {
439 size_t num_buckets = num_elements / max_load_factor_;
440 // Deal with rounding errors. Add one for rounding.
441 while (static_cast<size_t>(num_buckets * max_load_factor_) <= num_elements + 1u) {
442 ++num_buckets;
443 }
444 if (num_buckets > NumBuckets()) {
445 Resize(num_buckets);
446 }
447 }
448
449 // To distance that inserted elements were probed. Used for measuring how good hash functions
450 // are.
TotalProbeDistance()451 size_t TotalProbeDistance() const {
452 size_t total = 0;
453 for (size_t i = 0; i < NumBuckets(); ++i) {
454 const T& element = ElementForIndex(i);
455 if (!emptyfn_.IsEmpty(element)) {
456 size_t ideal_location = IndexForHash(hashfn_(element));
457 if (ideal_location > i) {
458 total += i + NumBuckets() - ideal_location;
459 } else {
460 total += i - ideal_location;
461 }
462 }
463 }
464 return total;
465 }
466
467 // Calculate the current load factor and return it.
CalculateLoadFactor()468 double CalculateLoadFactor() const {
469 return static_cast<double>(Size()) / static_cast<double>(NumBuckets());
470 }
471
472 // Make sure that everything reinserts in the right spot. Returns the number of errors.
Verify()473 size_t Verify() NO_THREAD_SAFETY_ANALYSIS {
474 size_t errors = 0;
475 for (size_t i = 0; i < num_buckets_; ++i) {
476 T& element = data_[i];
477 if (!emptyfn_.IsEmpty(element)) {
478 T temp;
479 emptyfn_.MakeEmpty(temp);
480 std::swap(temp, element);
481 size_t first_slot = FirstAvailableSlot(IndexForHash(hashfn_(temp)));
482 if (i != first_slot) {
483 LOG(ERROR) << "Element " << i << " should be in slot " << first_slot;
484 ++errors;
485 }
486 std::swap(temp, element);
487 }
488 }
489 return errors;
490 }
491
GetMinLoadFactor()492 double GetMinLoadFactor() const {
493 return min_load_factor_;
494 }
495
GetMaxLoadFactor()496 double GetMaxLoadFactor() const {
497 return max_load_factor_;
498 }
499
500 // Change the load factor of the hash set. If the current load factor is greater than the max
501 // specified, then we resize the hash table storage.
SetLoadFactor(double min_load_factor,double max_load_factor)502 void SetLoadFactor(double min_load_factor, double max_load_factor) {
503 DCHECK_LT(min_load_factor, max_load_factor);
504 DCHECK_GT(min_load_factor, 0.0);
505 DCHECK_LT(max_load_factor, 1.0);
506 min_load_factor_ = min_load_factor;
507 max_load_factor_ = max_load_factor;
508 elements_until_expand_ = NumBuckets() * max_load_factor_;
509 // If the current load factor isn't in the range, then resize to the mean of the minimum and
510 // maximum load factor.
511 const double load_factor = CalculateLoadFactor();
512 if (load_factor > max_load_factor_) {
513 Resize(Size() / ((min_load_factor_ + max_load_factor_) * 0.5));
514 }
515 }
516
517 // The hash set expands when Size() reaches ElementsUntilExpand().
ElementsUntilExpand()518 size_t ElementsUntilExpand() const {
519 return elements_until_expand_;
520 }
521
NumBuckets()522 size_t NumBuckets() const {
523 return num_buckets_;
524 }
525
526 private:
ElementForIndex(size_t index)527 T& ElementForIndex(size_t index) {
528 DCHECK_LT(index, NumBuckets());
529 DCHECK(data_ != nullptr);
530 return data_[index];
531 }
532
ElementForIndex(size_t index)533 const T& ElementForIndex(size_t index) const {
534 DCHECK_LT(index, NumBuckets());
535 DCHECK(data_ != nullptr);
536 return data_[index];
537 }
538
IndexForHash(size_t hash)539 size_t IndexForHash(size_t hash) const {
540 // Protect against undefined behavior (division by zero).
541 if (UNLIKELY(num_buckets_ == 0)) {
542 return 0;
543 }
544 return hash % num_buckets_;
545 }
546
NextIndex(size_t index)547 size_t NextIndex(size_t index) const {
548 if (UNLIKELY(++index >= num_buckets_)) {
549 DCHECK_EQ(index, NumBuckets());
550 return 0;
551 }
552 return index;
553 }
554
555 // Find the hash table slot for an element, or return NumBuckets() if not found.
556 // This value for not found is important so that iterator(this, FindIndex(...)) == end().
557 template <typename K>
FindIndex(const K & element,size_t hash)558 size_t FindIndex(const K& element, size_t hash) const {
559 // Guard against failing to get an element for a non-existing index.
560 if (UNLIKELY(NumBuckets() == 0)) {
561 return 0;
562 }
563 DCHECK_EQ(hashfn_(element), hash);
564 size_t index = IndexForHash(hash);
565 while (true) {
566 const T& slot = ElementForIndex(index);
567 if (emptyfn_.IsEmpty(slot)) {
568 return NumBuckets();
569 }
570 if (pred_(slot, element)) {
571 return index;
572 }
573 index = NextIndex(index);
574 }
575 }
576
IsFreeSlot(size_t index)577 bool IsFreeSlot(size_t index) const {
578 return emptyfn_.IsEmpty(ElementForIndex(index));
579 }
580
581 // Allocate a number of buckets.
AllocateStorage(size_t num_buckets)582 void AllocateStorage(size_t num_buckets) {
583 num_buckets_ = num_buckets;
584 data_ = allocfn_.allocate(num_buckets_);
585 owns_data_ = true;
586 for (size_t i = 0; i < num_buckets_; ++i) {
587 allocfn_.construct(allocfn_.address(data_[i]));
588 emptyfn_.MakeEmpty(data_[i]);
589 }
590 }
591
DeallocateStorage()592 void DeallocateStorage() {
593 if (owns_data_) {
594 for (size_t i = 0; i < NumBuckets(); ++i) {
595 allocfn_.destroy(allocfn_.address(data_[i]));
596 }
597 if (data_ != nullptr) {
598 allocfn_.deallocate(data_, NumBuckets());
599 }
600 owns_data_ = false;
601 }
602 data_ = nullptr;
603 num_buckets_ = 0;
604 }
605
606 // Expand the set based on the load factors.
Expand()607 void Expand() {
608 size_t min_index = static_cast<size_t>(Size() / min_load_factor_);
609 // Resize based on the minimum load factor.
610 Resize(min_index);
611 }
612
613 // Expand / shrink the table to the new specified size.
Resize(size_t new_size)614 void Resize(size_t new_size) {
615 if (new_size < kMinBuckets) {
616 new_size = kMinBuckets;
617 }
618 DCHECK_GE(new_size, Size());
619 T* const old_data = data_;
620 size_t old_num_buckets = num_buckets_;
621 // Reinsert all of the old elements.
622 const bool owned_data = owns_data_;
623 AllocateStorage(new_size);
624 for (size_t i = 0; i < old_num_buckets; ++i) {
625 T& element = old_data[i];
626 if (!emptyfn_.IsEmpty(element)) {
627 data_[FirstAvailableSlot(IndexForHash(hashfn_(element)))] = std::move(element);
628 }
629 if (owned_data) {
630 allocfn_.destroy(allocfn_.address(element));
631 }
632 }
633 if (owned_data) {
634 allocfn_.deallocate(old_data, old_num_buckets);
635 }
636
637 // When we hit elements_until_expand_, we are at the max load factor and must expand again.
638 elements_until_expand_ = NumBuckets() * max_load_factor_;
639 }
640
FirstAvailableSlot(size_t index)641 ALWAYS_INLINE size_t FirstAvailableSlot(size_t index) const {
642 DCHECK_LT(index, NumBuckets()); // Don't try to get a slot out of range.
643 size_t non_empty_count = 0;
644 while (!emptyfn_.IsEmpty(data_[index])) {
645 index = NextIndex(index);
646 non_empty_count++;
647 DCHECK_LE(non_empty_count, NumBuckets()); // Don't loop forever.
648 }
649 return index;
650 }
651
652 // Return new offset.
653 template <typename Elem>
WriteToBytes(uint8_t * ptr,size_t offset,Elem n)654 static size_t WriteToBytes(uint8_t* ptr, size_t offset, Elem n) {
655 DCHECK_ALIGNED(ptr + offset, sizeof(n));
656 if (ptr != nullptr) {
657 *reinterpret_cast<Elem*>(ptr + offset) = n;
658 }
659 return offset + sizeof(n);
660 }
661
662 template <typename Elem>
ReadFromBytes(const uint8_t * ptr,size_t offset,Elem * out)663 static size_t ReadFromBytes(const uint8_t* ptr, size_t offset, Elem* out) {
664 DCHECK(ptr != nullptr);
665 DCHECK_ALIGNED(ptr + offset, sizeof(*out));
666 *out = *reinterpret_cast<const Elem*>(ptr + offset);
667 return offset + sizeof(*out);
668 }
669
670 Alloc allocfn_; // Allocator function.
671 HashFn hashfn_; // Hashing function.
672 EmptyFn emptyfn_; // IsEmpty/SetEmpty function.
673 Pred pred_; // Equals function.
674 size_t num_elements_; // Number of inserted elements.
675 size_t num_buckets_; // Number of hash table buckets.
676 size_t elements_until_expand_; // Maximum number of elements until we expand the table.
677 bool owns_data_; // If we own data_ and are responsible for freeing it.
678 T* data_; // Backing storage.
679 double min_load_factor_;
680 double max_load_factor_;
681
682 ART_FRIEND_TEST(InternTableTest, CrossHash);
683 };
684
685 template <class T, class EmptyFn, class HashFn, class Pred, class Alloc>
swap(HashSet<T,EmptyFn,HashFn,Pred,Alloc> & lhs,HashSet<T,EmptyFn,HashFn,Pred,Alloc> & rhs)686 void swap(HashSet<T, EmptyFn, HashFn, Pred, Alloc>& lhs,
687 HashSet<T, EmptyFn, HashFn, Pred, Alloc>& rhs) {
688 lhs.swap(rhs);
689 }
690
691 } // namespace art
692
693 #endif // ART_LIBARTBASE_BASE_HASH_SET_H_
694