Home | History | Annotate | Download | only in base
      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:
     35   void MakeEmpty(T& item) const {
     36     item = T();
     37   }
     38   bool IsEmpty(const T& item) const {
     39     return item == T();
     40   }
     41 };
     42 
     43 template <class T>
     44 class DefaultEmptyFn<T*> {
     45  public:
     46   void MakeEmpty(T*& item) const {
     47     item = nullptr;
     48   }
     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;
     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 
    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.
    135   void Clear() {
    136     DeallocateStorage();
    137     num_elements_ = 0;
    138     elements_until_expand_ = 0;
    139   }
    140 
    141   HashSet() : HashSet(kDefaultMinLoadFactor, kDefaultMaxLoadFactor) {}
    142 
    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 
    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 
    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
    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_.
    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.
    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 
    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.
    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.
    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.
    290   iterator end() {
    291     return iterator(this, NumBuckets());
    292   }
    293 
    294   // Lower case for c++11 for each. const version.
    295   const_iterator end() const {
    296     return const_iterator(this, NumBuckets());
    297   }
    298 
    299   bool Empty() const {
    300     return Size() == 0;
    301   }
    302 
    303   // Return true if the hash set has ownership of the underlying data.
    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.
    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>
    367   iterator Find(const K& key) {
    368     return FindWithHash(key, hashfn_(key));
    369   }
    370 
    371   template <typename K>
    372   const_iterator Find(const K& key) const {
    373     return FindWithHash(key, hashfn_(key));
    374   }
    375 
    376   template <typename K>
    377   iterator FindWithHash(const K& key, size_t hash) {
    378     return iterator(this, FindIndex(key, hash));
    379   }
    380 
    381   template <typename K>
    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.
    387   void Insert(const T& element) {
    388     InsertWithHash(element, hashfn_(element));
    389   }
    390 
    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 
    402   size_t Size() const {
    403     return num_elements_;
    404   }
    405 
    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 
    422   allocator_type get_allocator() const {
    423     return allocfn_;
    424   }
    425 
    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.
    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.
    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.
    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.
    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 
    486   double GetMinLoadFactor() const {
    487     return min_load_factor_;
    488   }
    489 
    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.
    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().
    512   size_t ElementsUntilExpand() const {
    513     return elements_until_expand_;
    514   }
    515 
    516   size_t NumBuckets() const {
    517     return num_buckets_;
    518   }
    519 
    520  private:
    521   T& ElementForIndex(size_t index) {
    522     DCHECK_LT(index, NumBuckets());
    523     DCHECK(data_ != nullptr);
    524     return data_[index];
    525   }
    526 
    527   const T& ElementForIndex(size_t index) const {
    528     DCHECK_LT(index, NumBuckets());
    529     DCHECK(data_ != nullptr);
    530     return data_[index];
    531   }
    532 
    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 
    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>
    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 
    571   bool IsFreeSlot(size_t index) const {
    572     return emptyfn_.IsEmpty(ElementForIndex(index));
    573   }
    574 
    575   // Allocate a number of 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 
    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.
    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.
    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 
    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>
    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>
    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   ART_FRIEND_TEST(InternTableTest, CrossHash);
    677 };
    678 
    679 template <class T, class EmptyFn, class HashFn, class Pred, class Alloc>
    680 void swap(HashSet<T, EmptyFn, HashFn, Pred, Alloc>& lhs,
    681           HashSet<T, EmptyFn, HashFn, Pred, Alloc>& rhs) {
    682   lhs.swap(rhs);
    683 }
    684 
    685 }  // namespace art
    686 
    687 #endif  // ART_RUNTIME_BASE_HASH_SET_H_
    688