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