xref: /petsc/include/petsc/private/cpp/unordered_map.hpp (revision 7f031e8bf1f008cdd443cdad0cd45837cb20997c)
1 #pragma once
2 
3 #if defined(__clang__)
4   #pragma clang diagnostic push
5   #pragma clang diagnostic ignored "-Wshorten-64-to-32"
6 #endif
7 
8 #include <petsc/private/cpp/type_traits.hpp>
9 #include <petsc/private/cpp/utility.hpp>    // std ::pair
10 #include <petsc/private/cpp/functional.hpp> // std::hash, std::equal_to
11 
12 #if PETSC_CPP_VERSION >= 17
13   #include <optional>
14   #define PETSC_OPTIONAL_GET_KEY(...)  *(__VA_ARGS__)
15   #define PETSC_KHASH_MAP_USE_OPTIONAL 1
16 #else
17   #define PETSC_OPTIONAL_GET_KEY(...)  __VA_ARGS__
18   #define PETSC_KHASH_MAP_USE_OPTIONAL 0
19 #endif
20 
21 #include <cstdint>   // std::uint32_t
22 #include <climits>   // CHAR_BIT
23 #include <iterator>  // std::inserter
24 #include <limits>    // std::numeric_limits
25 #include <algorithm> // std::fill
26 #include <vector>
27 
28 namespace Petsc
29 {
30 
31 namespace khash
32 {
33 
34 // ==========================================================================================
35 // KHashTable - The hash table implementation which underpins UnorderedMap (and possibly
36 // UnorderedSet in the future).
37 //
38 // This class serves to implement the majority of functionality for both classes. In fact, it
39 // is possible to use -- without modification -- as a khash_unordered_set already.
40 //
41 // Template parameters are as follows:
42 //
43 // Value:
44 // The value type of the hash table, i.e. the set of unique items it stores
45 //
46 // Hash:
47 // The hasher type, provides a std::size_t operator()(const Value&) to produce a hash of Value
48 //
49 // Eq:
50 // The comparison type, provides a bool operator()(const Value&, const Value&) to compare two
51 // different Value's
52 // ==========================================================================================
53 
54 template <typename Value, typename Hash, typename Eq>
55 class KHashTable : util::compressed_pair<Hash, Eq> {
56   // Note we derive from compressed_pair<Hash, Eq>! This is to enable us to efficiently
57   // implement hash_function() and key_eq() since -- if Hash and Eq are empty -- we do not have
58   // to pay to store them due to empty base-class optimization.
59   template <bool>
60   class table_iterator;
61 
62   template <bool>
63   friend class table_iterator;
64 
65 public:
66   using value_type      = Value;
67   using hasher          = Hash;
68   using key_equal       = Eq;
69   using size_type       = std::size_t;
70   using reference       = value_type &;
71   using const_reference = const value_type &;
72   using difference_type = std::ptrdiff_t;
73   using iterator        = table_iterator</* is const = */ false>;
74   using const_iterator  = table_iterator</* is const = */ true>;
75   using khash_int       = std::uint32_t; // used as the internal iterator type
76   using flags_type      = std::uint32_t;
77 
78   using flag_bucket_width     = std::integral_constant<unsigned long, sizeof(flags_type) * CHAR_BIT>;
79   using flag_pairs_per_bucket = std::integral_constant<unsigned long, flag_bucket_width::value / 2>;
80 
81   static_assert(std::numeric_limits<flags_type>::is_integer && std::is_unsigned<flags_type>::value, "");
82   static_assert(flag_bucket_width::value % 2 == 0, "");
83 
84   KHashTable()  = default;
85   ~KHashTable() = default;
86 
87   KHashTable(const KHashTable &)            = default;
88   KHashTable &operator=(const KHashTable &) = default;
89 
90   KHashTable(KHashTable &&) noexcept;
91   KHashTable &operator=(KHashTable &&) noexcept;
92 
93   template <typename Iter>
94   KHashTable(Iter, Iter) noexcept;
95 
96   PETSC_NODISCARD iterator       begin() noexcept;
97   PETSC_NODISCARD const_iterator cbegin() const noexcept;
98   PETSC_NODISCARD const_iterator begin() const noexcept;
99 
100   PETSC_NODISCARD iterator       end() noexcept;
101   PETSC_NODISCARD const_iterator cend() const noexcept;
102   PETSC_NODISCARD const_iterator end() const noexcept;
103 
104   PETSC_NODISCARD size_type bucket_count() const noexcept;
105   PETSC_NODISCARD size_type size() const noexcept;
106   PETSC_NODISCARD size_type capacity() const noexcept;
107   PETSC_NODISCARD bool      empty() const noexcept;
108 
109   PetscErrorCode reserve(size_type) noexcept;
110   PetscErrorCode resize(size_type) noexcept;
111   PetscErrorCode clear() noexcept;
112 
113   PETSC_NODISCARD bool occupied(khash_int) const noexcept;
114   PETSC_NODISCARD bool occupied(const_iterator) const noexcept;
115 
116   iterator erase(iterator) noexcept;
117   iterator erase(const_iterator) noexcept;
118   iterator erase(const_iterator, const_iterator) noexcept;
119 
120   template <typename... Args>
121   std::pair<iterator, bool> emplace(Args &&...) noexcept;
122 
123   std::pair<iterator, bool> insert(const value_type &) noexcept;
124   std::pair<iterator, bool> insert(value_type &&) noexcept;
125   iterator                  insert(const_iterator, const value_type &) noexcept;
126   iterator                  insert(const_iterator, value_type &&) noexcept;
127 
128   hasher    hash_function() const noexcept;
129   key_equal key_eq() const noexcept;
130 
131   PetscErrorCode shrink_to_fit() noexcept;
132 
133   void swap(KHashTable &) noexcept;
134 
135 protected:
136   PETSC_NODISCARD iterator       make_iterator_(khash_int) noexcept;
137   PETSC_NODISCARD const_iterator make_iterator_(khash_int) const noexcept;
138 
139   template <typename T>
140   PetscErrorCode khash_find_(T &&, khash_int *) const noexcept;
141 
142   // emplacement for the hash map, where key and value are constructed separately
143   template <typename KeyType, typename... ValueTypeArgs>
144   PETSC_NODISCARD std::pair<iterator, bool> find_and_emplace_(KeyType &&, ValueTypeArgs &&...) noexcept;
145   template <typename KeyValueType>
146   PETSC_NODISCARD std::pair<iterator, bool> find_and_emplace_(KeyValueType &&) noexcept;
147 
148 private:
149   template <typename Iter>
150   KHashTable(Iter, Iter, std::input_iterator_tag) noexcept;
151   template <typename Iter>
152   KHashTable(Iter, Iter, std::random_access_iterator_tag) noexcept;
153 
154   // Every element in the table has a pair of 2 flags that describe its current state. These
155   // are addressed via the *index* into values_ for the element. These flags are:
156   //
157   // 1. Empty: has the element *ever* been constructed? Note empty of yes implies deleted no.
158   // 2. Deleted: has the element been constructed and marked as deleted?
159   //
160   // Since these flags are combineable we can store them in compressed form in a bit-table,
161   // where each pair of consecutive 2*i and 2*i+1 bits denote the flags for element i.
162   //
163   // Thus if we use a vector of std::bitset's (which are each N-bits wide) we can effectively
164   // store N / 2 flags per index, for example:
165   //
166   // std::vector<std::bitset<32>> flags;
167   //
168   // int flags_per_idx = 32 / 2; (16 pairs of flags)
169   // int value_index = 24;
170   // int flags_index = value_index / flags_per_idx; (index into the right bucket in flags)
171   // int flags_bucket_index = (value_index % flags_per_idx) << 1; (within that bucket grab the
172   //                                                               right entry)
173   //
174   // or visually speaking:
175   //
176   // flags = [
177   //   [00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00],
178   //   [00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00], <--- flags_index (1)
179   //   ...                   ^--- flags_bucket_index (16)
180   // ]
181   //
182   // Thus to access a particular flag pair, one must right-shift flags[flags_index] by
183   // flags_bucket_index. Then the desired flag pair will be the first and second bits of the
184   // result.
185   PETSC_NODISCARD static constexpr khash_int flag_bucket_index_(khash_int) noexcept;
186 
187   PETSC_NODISCARD static flags_type       &flag_bucket_at_(khash_int, std::vector<flags_type> &) noexcept;
188   PETSC_NODISCARD static const flags_type &flag_bucket_at_(khash_int, const std::vector<flags_type> &) noexcept;
189   PETSC_NODISCARD flags_type              &flag_bucket_at_(khash_int) noexcept;
190   PETSC_NODISCARD const flags_type        &flag_bucket_at_(khash_int) const noexcept;
191 
192   template <unsigned>
193   PETSC_NODISCARD static bool khash_test_flag_(khash_int, const std::vector<flags_type> &) noexcept;
194   PETSC_NODISCARD static bool khash_is_del_(khash_int, const std::vector<flags_type> &) noexcept;
195   PETSC_NODISCARD static bool khash_is_empty_(khash_int, const std::vector<flags_type> &) noexcept;
196   PETSC_NODISCARD static bool khash_is_either_(khash_int, const std::vector<flags_type> &) noexcept;
197   PETSC_NODISCARD static bool khash_occupied_(khash_int, const std::vector<flags_type> &) noexcept;
198   PETSC_NODISCARD bool        khash_is_del_(khash_int) const noexcept;
199   PETSC_NODISCARD bool        khash_is_empty_(khash_int) const noexcept;
200   PETSC_NODISCARD bool        khash_is_either_(khash_int) const noexcept;
201 
202   template <unsigned, bool>
203   static PetscErrorCode khash_set_flag_(khash_int, std::vector<flags_type> &) noexcept;
204   template <bool>
205   static PetscErrorCode khash_set_deleted_(khash_int, std::vector<flags_type> &) noexcept;
206   template <bool>
207   static PetscErrorCode khash_set_empty_(khash_int, std::vector<flags_type> &) noexcept;
208   template <bool>
209   static PetscErrorCode khash_set_both_(khash_int, std::vector<flags_type> &) noexcept;
210   template <bool>
211   PetscErrorCode khash_set_deleted_(khash_int) noexcept;
212   template <bool>
213   PetscErrorCode khash_set_empty_(khash_int) noexcept;
214   template <bool>
215   PetscErrorCode khash_set_both_(khash_int) noexcept;
216 
217   // produce the default bit pattern:
218   //
219   //               v--- deleted: no
220   // 0b101010 ... 10
221   //              ^---- empty: yes
222   template <std::size_t mask_width>
default_bit_pattern_impl_()223   static PETSC_CONSTEXPR_14 flags_type default_bit_pattern_impl_() noexcept
224   {
225     flags_type x{};
226 
227     for (std::size_t i = 0; i < mask_width; ++i) {
228       if (i % 2) {
229         // odd,
230         x |= 1ULL << i;
231       } else {
232         // even
233         x &= ~(1UL << i);
234       }
235     }
236     return x;
237   }
238 
239 public:
default_bit_pattern()240   PETSC_NODISCARD static PETSC_CONSTEXPR_14 flags_type default_bit_pattern() noexcept
241   {
242     // forces constexpr evaluation, which may not be guaranteed. Note that after GCC 6.1+
243     // tries to constexpr-evaluate _any_ function marked constexpr and will inline evaluate
244     // default_bit_mask_impl_() at any optimization level > 0.
245     //
246     // clang constexpr evaluates this at 3.7 but is inconsistent between versions at which
247     // optimization level the call is fully unraveled.
248     PETSC_CONSTEXPR_14 auto ret = default_bit_pattern_impl_<flag_bucket_width::value>();
249     return ret;
250   }
251 
252 private:
253   template <typename KeyType, typename ValueConstructor>
254   PETSC_NODISCARD std::pair<iterator, bool> find_and_emplace_final_(KeyType &&, ValueConstructor &&) noexcept;
255 
256   PetscErrorCode khash_maybe_rehash_() noexcept;
257   PetscErrorCode khash_erase_(khash_int) noexcept;
258 
259 #if PETSC_KHASH_MAP_USE_OPTIONAL
260   using internal_value_type = std::optional<value_type>;
261 #else
262   using internal_value_type = value_type;
263 #endif
264 
265   std::vector<internal_value_type> values_{};
266   std::vector<flags_type>          flags_{};
267   size_type                        count_       = 0;
268   size_type                        n_occupied_  = 0;
269   size_type                        upper_bound_ = 0;
270 };
271 
272 // ==========================================================================================
273 // KHashTable::table_iterator
274 // ==========================================================================================
275 
276 template <typename V, typename H, typename KE>
277 template <bool is_const_it>
278 class KHashTable<V, H, KE>::table_iterator {
279   template <typename U>
280   using conditional_const = util::conditional_t<is_const_it, util::add_const_t<U>, U>;
281 
282   template <bool>
283   friend class table_iterator;
284 
285   friend class KHashTable;
286 
287 public:
288   // internal typedef
289   using table_type = conditional_const<KHashTable>;
290   using khash_int  = typename table_type::khash_int;
291 
292   // iterator-related typedefs
293   using iterator_category = std::bidirectional_iterator_tag;
294   using difference_type   = typename table_type::difference_type;
295   using value_type        = conditional_const<typename table_type::value_type>;
296   using reference         = value_type &;
297   using pointer           = value_type *;
298 
299   table_iterator() noexcept = default;
table_iterator(table_type * map,khash_int it)300   table_iterator(table_type *map, khash_int it) noexcept : map_{std::move(map)}, it_{std::move(it)} { }
301 
302   table_iterator(const table_iterator &) noexcept            = default;
303   table_iterator &operator=(const table_iterator &) noexcept = default;
304 
305   table_iterator(table_iterator &&) noexcept            = default;
306   table_iterator &operator=(table_iterator &&) noexcept = default;
307 
308   template <bool other_is_const_it, util::enable_if_t<is_const_it && !other_is_const_it> * = nullptr>
table_iterator(const table_iterator<other_is_const_it> & other)309   table_iterator(const table_iterator<other_is_const_it> &other) noexcept : table_iterator{other.map_, other.it_}
310   {
311   }
312 
313   template <bool other_is_const_it, util::enable_if_t<is_const_it && !other_is_const_it> * = nullptr>
operator =(const table_iterator<other_is_const_it> & other)314   table_iterator &operator=(const table_iterator<other_is_const_it> &other) noexcept
315   {
316     // self assignment is OK here
317     PetscFunctionBegin;
318     map_ = other.map_;
319     it_  = other.it_;
320     PetscFunctionReturn(*this);
321   }
322 
323   // prefix
operator --()324   table_iterator &operator--() noexcept
325   {
326     constexpr khash_int map_begin = 0;
327 
328     PetscFunctionBegin;
329     // Use of map_begin + 1 instead of map_begin (like in operator++()) is deliberate. We do
330     // not want it_ == map_begin here since that would mean that the while-loop decrements it
331     // out of bounds!
332     // Likewise we are allowed to be 1 past the bucket size, otherwise backwards iteration
333     // would not work!
334     PetscCallAbort(PETSC_COMM_SELF, check_iterator_inbounds_(1, 1));
335     do {
336       --it_;
337     } while ((it_ > map_begin) && !map_->occupied(it_));
338     PetscFunctionReturn(*this);
339   }
340 
341   // postfix
operator --(int)342   table_iterator operator--(int) noexcept
343   {
344     table_iterator old{*this};
345 
346     PetscFunctionBegin;
347     --(*this);
348     PetscFunctionReturn(old);
349   }
350 
351   // prefix
operator ++()352   table_iterator &operator++() noexcept
353   {
354     khash_int map_end = 0;
355 
356     PetscFunctionBegin;
357     PetscCallAbort(PETSC_COMM_SELF, check_iterator_inbounds_());
358     map_end = map_->bucket_count();
359     do {
360       ++it_;
361     } while (it_ != map_end && !map_->occupied(it_));
362     PetscFunctionReturn(*this);
363   }
364 
365   // postfix
operator ++(int)366   table_iterator operator++(int) noexcept
367   {
368     table_iterator old{*this};
369 
370     PetscFunctionBegin;
371     ++(*this);
372     PetscFunctionReturn(old);
373   }
374 
operator *() const375   PETSC_NODISCARD reference operator*() const noexcept
376   {
377     PetscFunctionBegin;
378     PetscCallAbort(PETSC_COMM_SELF, check_iterator_inbounds_());
379     PetscFunctionReturn(PETSC_OPTIONAL_GET_KEY(map_->values_[it_]));
380   }
381 
operator ->() const382   PETSC_NODISCARD pointer operator->() const noexcept
383   {
384     PetscFunctionBegin;
385     PetscCallAbort(PETSC_COMM_SELF, check_iterator_inbounds_());
386     PetscFunctionReturn(std::addressof(PETSC_OPTIONAL_GET_KEY(map_->values_[it_])));
387   }
388 
389   template <bool rc>
operator ==(const table_iterator<rc> & r) const390   PETSC_NODISCARD bool operator==(const table_iterator<rc> &r) const noexcept
391   {
392     return std::tie(map_, it_) == std::tie(r.map_, r.it_);
393   }
394 
395   template <bool rc>
operator !=(const table_iterator<rc> & r) const396   PETSC_NODISCARD bool operator!=(const table_iterator<rc> &r) const noexcept
397   {
398     return !(*this == r);
399   }
400 
401 private:
402   table_type *map_ = nullptr;
403   khash_int   it_  = 0;
404 
check_iterator_inbounds_(int map_begin_offset=0,int map_end_offset=0) const405   PetscErrorCode check_iterator_inbounds_(int map_begin_offset = 0, int map_end_offset = 0) const noexcept
406   {
407     PetscFunctionBegin;
408     if (PetscDefined(USE_DEBUG)) {
409       std::int64_t map_begin = map_begin_offset;
410       std::int64_t map_end   = map_end_offset;
411 
412       PetscCheck(map_, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Iterator has a NULL map pointer");
413       map_end += map_->bucket_count();
414       PetscCheck((it_ >= map_begin) && (it_ < map_end), PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Iterator index value %" PRId32 " is out of range for map (%p): [%" PRId64 ", %" PRId64 ")", it_, (void *)map_, map_begin, map_end);
415     } else {
416       static_cast<void>(map_begin_offset);
417       static_cast<void>(map_end_offset);
418     }
419     PetscFunctionReturn(PETSC_SUCCESS);
420   }
421 };
422 
423 // ==========================================================================================
424 // KHashTable - Private API
425 // ==========================================================================================
426 
427 // Generic iterator constructor
428 template <typename V, typename H, typename KE>
429 template <typename Iter>
KHashTable(Iter first,Iter last,std::input_iterator_tag)430 inline KHashTable<V, H, KE>::KHashTable(Iter first, Iter last, std::input_iterator_tag) noexcept
431 {
432   PetscFunctionBegin;
433   PetscCallCXXAbort(PETSC_COMM_SELF, std::copy(std::move(first), std::move(last), std::inserter(*this, begin())));
434   PetscFunctionReturnVoid();
435 }
436 
437 // An optimization for random_access_iterators. Since these mandate that std::distance() is
438 // equivalent to end-begin, we can use this to pre-allocate the hashmap for free before we
439 // insert
440 template <typename V, typename H, typename KE>
441 template <typename Iter>
KHashTable(Iter first,Iter last,std::random_access_iterator_tag)442 inline KHashTable<V, H, KE>::KHashTable(Iter first, Iter last, std::random_access_iterator_tag) noexcept
443 {
444   PetscFunctionBegin;
445   PetscCallAbort(PETSC_COMM_SELF, reserve(static_cast<size_type>(std::distance(first, last))));
446   PetscCallCXXAbort(PETSC_COMM_SELF, std::copy(std::move(first), std::move(last), std::inserter(*this, begin())));
447   PetscFunctionReturnVoid();
448 }
449 
450 // ------------------------------------------------------------------------------------------
451 // KHashTable - Private API - flag bucket API - accessors
452 // ------------------------------------------------------------------------------------------
453 
454 template <typename V, typename H, typename KE>
flag_bucket_index_(khash_int it)455 inline constexpr typename KHashTable<V, H, KE>::khash_int KHashTable<V, H, KE>::flag_bucket_index_(khash_int it) noexcept
456 {
457   return (it % flag_pairs_per_bucket::value) << 1;
458 }
459 
460 template <typename V, typename H, typename KE>
flag_bucket_at_(khash_int it,std::vector<flags_type> & flags)461 inline typename KHashTable<V, H, KE>::flags_type &KHashTable<V, H, KE>::flag_bucket_at_(khash_int it, std::vector<flags_type> &flags) noexcept
462 {
463   return flags[it / flag_pairs_per_bucket::value];
464 }
465 
466 template <typename V, typename H, typename KE>
flag_bucket_at_(khash_int it,const std::vector<flags_type> & flags)467 inline const typename KHashTable<V, H, KE>::flags_type &KHashTable<V, H, KE>::flag_bucket_at_(khash_int it, const std::vector<flags_type> &flags) noexcept
468 {
469   return flags[it / flag_pairs_per_bucket::value];
470 }
471 
472 template <typename V, typename H, typename KE>
flag_bucket_at_(khash_int it)473 inline typename KHashTable<V, H, KE>::flags_type &KHashTable<V, H, KE>::flag_bucket_at_(khash_int it) noexcept
474 {
475   return flag_bucket_at_(it, flags_);
476 }
477 
478 template <typename V, typename H, typename KE>
flag_bucket_at_(khash_int it) const479 inline const typename KHashTable<V, H, KE>::flags_type &KHashTable<V, H, KE>::flag_bucket_at_(khash_int it) const noexcept
480 {
481   return flag_bucket_at_(it, flags_);
482 }
483 
484 // ------------------------------------------------------------------------------------------
485 // KHashTable - Private API - flag bucket API - query
486 // ------------------------------------------------------------------------------------------
487 
488 template <typename V, typename H, typename KE>
489 template <unsigned selector>
khash_test_flag_(khash_int it,const std::vector<flags_type> & flags)490 inline bool KHashTable<V, H, KE>::khash_test_flag_(khash_int it, const std::vector<flags_type> &flags) noexcept
491 {
492   static_assert(selector > 0 || selector <= 3, "");
493   return (flag_bucket_at_(it, flags) >> flag_bucket_index_(it)) & selector;
494 }
495 
496 template <typename V, typename H, typename KE>
khash_is_del_(khash_int it,const std::vector<flags_type> & flags)497 inline bool KHashTable<V, H, KE>::khash_is_del_(khash_int it, const std::vector<flags_type> &flags) noexcept
498 {
499   return khash_test_flag_<1>(it, flags);
500 }
501 
502 template <typename V, typename H, typename KE>
khash_is_empty_(khash_int it,const std::vector<flags_type> & flags)503 inline bool KHashTable<V, H, KE>::khash_is_empty_(khash_int it, const std::vector<flags_type> &flags) noexcept
504 {
505   return khash_test_flag_<2>(it, flags);
506 }
507 
508 template <typename V, typename H, typename KE>
khash_is_either_(khash_int it,const std::vector<flags_type> & flags)509 inline bool KHashTable<V, H, KE>::khash_is_either_(khash_int it, const std::vector<flags_type> &flags) noexcept
510 {
511   return khash_test_flag_<3>(it, flags);
512 }
513 
514 template <typename V, typename H, typename KE>
khash_occupied_(khash_int it,const std::vector<flags_type> & flags)515 inline bool KHashTable<V, H, KE>::khash_occupied_(khash_int it, const std::vector<flags_type> &flags) noexcept
516 {
517   return !khash_is_either_(it, flags);
518 }
519 
520 template <typename V, typename H, typename KE>
khash_is_del_(khash_int it) const521 inline bool KHashTable<V, H, KE>::khash_is_del_(khash_int it) const noexcept
522 {
523   return khash_is_del_(it, flags_);
524 }
525 
526 template <typename V, typename H, typename KE>
khash_is_empty_(khash_int it) const527 inline bool KHashTable<V, H, KE>::khash_is_empty_(khash_int it) const noexcept
528 {
529   return khash_is_empty_(it, flags_);
530 }
531 
532 template <typename V, typename H, typename KE>
khash_is_either_(khash_int it) const533 inline bool KHashTable<V, H, KE>::khash_is_either_(khash_int it) const noexcept
534 {
535   return khash_is_either_(it, flags_);
536 }
537 
538 // ------------------------------------------------------------------------------------------
539 // KHashTable - Private API - flag bucket API - set
540 // ------------------------------------------------------------------------------------------
541 
542 template <typename V, typename H, typename KE>
543 template <unsigned flag_selector, bool set>
khash_set_flag_(khash_int it,std::vector<flags_type> & flags)544 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_flag_(khash_int it, std::vector<flags_type> &flags) noexcept
545 {
546   static_assert(flag_selector > 0U && flag_selector <= 3U, "");
547 
548   PetscFunctionBegin;
549   if (set) {
550     flag_bucket_at_(it, flags) |= flag_selector << flag_bucket_index_(it);
551   } else {
552     flag_bucket_at_(it, flags) &= ~(flag_selector << flag_bucket_index_(it));
553   }
554   PetscFunctionReturn(PETSC_SUCCESS);
555 }
556 
557 template <typename V, typename H, typename KE>
558 template <bool b>
khash_set_deleted_(khash_int it,std::vector<flags_type> & flags)559 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_deleted_(khash_int it, std::vector<flags_type> &flags) noexcept
560 {
561   PetscFunctionBegin;
562   PetscCall(khash_set_flag_<1, b>(it, flags));
563   PetscFunctionReturn(PETSC_SUCCESS);
564 }
565 
566 template <typename V, typename H, typename KE>
567 template <bool b>
khash_set_empty_(khash_int it,std::vector<flags_type> & flags)568 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_empty_(khash_int it, std::vector<flags_type> &flags) noexcept
569 {
570   PetscFunctionBegin;
571   PetscCall(khash_set_flag_<2, b>(it, flags));
572   PetscFunctionReturn(PETSC_SUCCESS);
573 }
574 
575 template <typename V, typename H, typename KE>
576 template <bool b>
khash_set_both_(khash_int it,std::vector<flags_type> & flags)577 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_both_(khash_int it, std::vector<flags_type> &flags) noexcept
578 {
579   PetscFunctionBegin;
580   PetscCall(khash_set_flag_<3, b>(it, flags));
581   PetscFunctionReturn(PETSC_SUCCESS);
582 }
583 
584 template <typename V, typename H, typename KE>
585 template <bool b>
khash_set_deleted_(khash_int it)586 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_deleted_(khash_int it) noexcept
587 {
588   PetscFunctionBegin;
589   PetscCall(khash_set_deleted_<b>(it, flags_));
590   PetscFunctionReturn(PETSC_SUCCESS);
591 }
592 
593 template <typename V, typename H, typename KE>
594 template <bool b>
khash_set_empty_(khash_int it)595 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_empty_(khash_int it) noexcept
596 {
597   PetscFunctionBegin;
598   PetscCall(khash_set_empty_<b>(it, flags_));
599   PetscFunctionReturn(PETSC_SUCCESS);
600 }
601 
602 template <typename V, typename H, typename KE>
603 template <bool b>
khash_set_both_(khash_int it)604 inline PetscErrorCode KHashTable<V, H, KE>::khash_set_both_(khash_int it) noexcept
605 {
606   PetscFunctionBegin;
607   PetscCall(khash_set_both_<b>(it, flags_));
608   PetscFunctionReturn(PETSC_SUCCESS);
609 }
610 
611 template <typename V, typename H, typename KE>
612 template <typename KeyType, typename ValueConstructor>
find_and_emplace_final_(KeyType && key,ValueConstructor && constructor)613 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::find_and_emplace_final_(KeyType &&key, ValueConstructor &&constructor) noexcept
614 {
615   khash_int it = 0;
616 
617   PetscFunctionBegin;
618   PetscCallAbort(PETSC_COMM_SELF, khash_maybe_rehash_());
619   {
620     const auto nb   = bucket_count();
621     const auto mask = nb - 1;
622     const auto hash = hash_function()(key);
623     auto       i    = hash & mask;
624 
625     PetscAssertAbort(nb > 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Have %zu bucket count after rehash", nb);
626     if (khash_is_empty_(i)) {
627       it = i; // for speed up
628     } else {
629       const auto last = i;
630       auto       site = nb;
631       khash_int  step = 0;
632 
633       it = nb;
634       while (!khash_is_empty_(i) && (khash_is_del_(i) || !key_eq()(PETSC_OPTIONAL_GET_KEY(values_[i]), key))) {
635         if (khash_is_del_(i)) site = i;
636         ++step;
637         i = (i + step) & mask;
638         if (i == last) {
639           it = site;
640           break;
641         }
642       }
643       if (it == nb) {
644         // didn't find a completely empty place to put it, see if we can reuse an existing
645         // bucket
646         if (khash_is_empty_(i) && (site != nb)) {
647           // reuse a deleted element (I think)
648           it = site;
649         } else {
650           it = i;
651         }
652       }
653     }
654   }
655   if (occupied(it)) PetscFunctionReturn({make_iterator_(it), false});
656   // not present at all or deleted, so create (or replace) the element using the constructor
657   // lambda
658   PetscCallCXXAbort(PETSC_COMM_SELF, values_[it] = constructor());
659   ++count_;
660   if (khash_is_empty_(it)) ++n_occupied_;
661   // order matters, must do this _after_ we check is_empty() since this call sets is_empty to
662   // false!
663   PetscCallAbort(PETSC_COMM_SELF, khash_set_both_<false>(it));
664   PetscFunctionReturn({make_iterator_(it), true});
665 }
666 
667 template <typename V, typename H, typename KE>
khash_maybe_rehash_()668 inline PetscErrorCode KHashTable<V, H, KE>::khash_maybe_rehash_() noexcept
669 {
670   PetscFunctionBegin;
671   if (n_occupied_ >= upper_bound_) {
672     auto target_size = bucket_count();
673 
674     if (target_size > (size() << 1)) {
675       // clear "deleted" elements
676       --target_size;
677     } else {
678       // expand the hash table
679       ++target_size;
680     }
681     PetscCall(resize(target_size));
682   }
683   PetscFunctionReturn(PETSC_SUCCESS);
684 }
685 
686 template <typename V, typename H, typename KE>
khash_erase_(khash_int it)687 inline PetscErrorCode KHashTable<V, H, KE>::khash_erase_(khash_int it) noexcept
688 {
689   PetscFunctionBegin;
690   PetscAssert(it < bucket_count(), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Attempting to erase iterator (index %d) which did not exist in the map", it);
691   PetscAssert(count_ > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Attempting to erase iterator (index %d) which did not exist in the map, have element count %zu", it, count_);
692   PetscAssert(occupied(it), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Attempting to erase iterator (index %d) which exists in the map but is not occupied", it);
693   --count_;
694   PetscCall(khash_set_deleted_<true>(it));
695   PetscCallCXX(values_[it] = internal_value_type{});
696   PetscFunctionReturn(PETSC_SUCCESS);
697 }
698 
699 // ==========================================================================================
700 // KHashTable - Protected API
701 // ==========================================================================================
702 
703 template <typename V, typename H, typename KE>
make_iterator_(khash_int it)704 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::make_iterator_(khash_int it) noexcept
705 {
706   return {this, it};
707 }
708 
709 template <typename V, typename H, typename KE>
make_iterator_(khash_int it) const710 inline typename KHashTable<V, H, KE>::const_iterator KHashTable<V, H, KE>::make_iterator_(khash_int it) const noexcept
711 {
712   return {this, it};
713 }
714 
715 template <typename V, typename H, typename KE>
716 template <typename T>
khash_find_(T && key,khash_int * it) const717 inline PetscErrorCode KHashTable<V, H, KE>::khash_find_(T &&key, khash_int *it) const noexcept
718 {
719   const auto nb  = bucket_count();
720   auto       ret = nb;
721 
722   PetscFunctionBegin;
723   if (nb) {
724     const auto mask = nb - 1;
725     const auto hash = hash_function()(key);
726     auto       i    = hash & mask;
727     const auto last = i;
728     khash_int  step = 0;
729 
730     while (!khash_is_empty_(i) && (khash_is_del_(i) || !key_equal()(PETSC_OPTIONAL_GET_KEY(values_[i]), key))) {
731       ++step;
732       i = (i + step) & mask;
733       if (i == last) {
734         *it = ret;
735         PetscFunctionReturn(PETSC_SUCCESS);
736       }
737     }
738     if (occupied(i)) ret = i;
739   }
740   *it = ret;
741   PetscFunctionReturn(PETSC_SUCCESS);
742 }
743 
744 template <typename V, typename H, typename KE>
745 template <typename KeyType, typename... ValueTypeArgs>
find_and_emplace_(KeyType && key,ValueTypeArgs &&...value_ctor_args)746 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::find_and_emplace_(KeyType &&key, ValueTypeArgs &&...value_ctor_args) noexcept
747 {
748   return find_and_emplace_final_(std::forward<KeyType>(key), [&] { return value_type{std::forward<ValueTypeArgs>(value_ctor_args)...}; });
749 }
750 
751 template <typename V, typename H, typename KE>
752 template <typename KeyValueType>
find_and_emplace_(KeyValueType && key_value)753 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::find_and_emplace_(KeyValueType &&key_value) noexcept
754 {
755   return find_and_emplace_final_(std::forward<KeyValueType>(key_value), [&] { return std::forward<KeyValueType>(key_value); });
756 }
757 
758 // ==========================================================================================
759 // KHashTable - Public API
760 // ==========================================================================================
761 
762 // Generic iterator constructor
763 template <typename V, typename H, typename KE>
764 template <typename Iter>
KHashTable(Iter first,Iter last)765 inline KHashTable<V, H, KE>::KHashTable(Iter first, Iter last) noexcept : KHashTable(std::move(first), std::move(last), typename std::iterator_traits<Iter>::iterator_category{})
766 {
767 }
768 
769 template <typename V, typename H, typename KE>
KHashTable(KHashTable && other)770 inline KHashTable<V, H, KE>::KHashTable(KHashTable &&other) noexcept :
771   values_(std::move(other.values_)), flags_(std::move(other.flags_)), count_(util::exchange(other.count_, 0)), n_occupied_(util::exchange(other.n_occupied_, 0)), upper_bound_(util::exchange(other.upper_bound_, 0))
772 {
773 }
774 
775 template <typename V, typename H, typename KE>
operator =(KHashTable && other)776 inline KHashTable<V, H, KE> &KHashTable<V, H, KE>::operator=(KHashTable &&other) noexcept
777 {
778   PetscFunctionBegin;
779   if (this != &other) {
780     PetscCallCXXAbort(PETSC_COMM_SELF, values_ = std::move(other.values_));
781     PetscCallCXXAbort(PETSC_COMM_SELF, flags_ = std::move(other.flags_));
782     count_       = util::exchange(other.count_, 0);
783     n_occupied_  = util::exchange(other.n_occupied_, 0);
784     upper_bound_ = util::exchange(other.upper_bound_, 0);
785   }
786   PetscFunctionReturn(*this);
787 }
788 
789 template <typename V, typename H, typename KE>
begin()790 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::begin() noexcept
791 {
792   khash_int it = 0;
793 
794   PetscFunctionBegin;
795   for (; it < bucket_count(); ++it) {
796     if (occupied(it)) break;
797   }
798   PetscFunctionReturn(make_iterator_(it));
799 }
800 
801 template <typename V, typename H, typename KE>
cbegin() const802 inline typename KHashTable<V, H, KE>::const_iterator KHashTable<V, H, KE>::cbegin() const noexcept
803 {
804   khash_int it = 0;
805 
806   PetscFunctionBegin;
807   for (; it < bucket_count(); ++it) {
808     if (occupied(it)) break;
809   }
810   PetscFunctionReturn(make_iterator_(it));
811 }
812 
813 template <typename V, typename H, typename KE>
begin() const814 inline typename KHashTable<V, H, KE>::const_iterator KHashTable<V, H, KE>::begin() const noexcept
815 {
816   return cbegin();
817 }
818 
819 template <typename V, typename H, typename KE>
end()820 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::end() noexcept
821 {
822   return make_iterator_(bucket_count());
823 }
824 
825 template <typename V, typename H, typename KE>
cend() const826 inline typename KHashTable<V, H, KE>::const_iterator KHashTable<V, H, KE>::cend() const noexcept
827 {
828   return make_iterator_(bucket_count());
829 }
830 
831 template <typename V, typename H, typename KE>
end() const832 inline typename KHashTable<V, H, KE>::const_iterator KHashTable<V, H, KE>::end() const noexcept
833 {
834   return cend();
835 }
836 
837 template <typename V, typename H, typename KE>
bucket_count() const838 inline typename KHashTable<V, H, KE>::size_type KHashTable<V, H, KE>::bucket_count() const noexcept
839 {
840   return values_.size();
841 }
842 
843 template <typename V, typename H, typename KE>
size() const844 inline typename KHashTable<V, H, KE>::size_type KHashTable<V, H, KE>::size() const noexcept
845 {
846   return count_;
847 }
848 
849 template <typename V, typename H, typename KE>
capacity() const850 inline typename KHashTable<V, H, KE>::size_type KHashTable<V, H, KE>::capacity() const noexcept
851 {
852   return flags_.size() * flag_pairs_per_bucket::value;
853 }
854 
855 template <typename V, typename H, typename KE>
empty() const856 inline bool KHashTable<V, H, KE>::empty() const noexcept
857 {
858   return size() == 0;
859 }
860 
861 // REVIEW ME: should really be called rehash()
862 template <typename V, typename H, typename KE>
reserve(size_type req_size)863 inline PetscErrorCode KHashTable<V, H, KE>::reserve(size_type req_size) noexcept
864 {
865   PetscFunctionBegin;
866   if (size() < req_size) PetscCall(resize(req_size));
867   PetscFunctionReturn(PETSC_SUCCESS);
868 }
869 
870 namespace detail
871 {
872 
873 // templated version of
874 // https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2. See also
875 // https://stackoverflow.com/questions/466204/rounding-up-to-next-power-of-2
876 template <typename T>
round_up_to_next_pow2(T v)877 static inline PETSC_CONSTEXPR_14 T round_up_to_next_pow2(T v) noexcept
878 {
879   static_assert(std::numeric_limits<T>::is_integer && std::is_unsigned<T>::value, "");
880   if (v <= 1) return 1;
881   --v;
882   for (std::size_t i = 1; i < (sizeof(v) * CHAR_BIT); i *= 2) v |= v >> i;
883   ++v;
884   return v;
885 }
886 
887 // compilers sadly don't yet recognize that the above is just searching for the next nonzero
888 // bit (https://godbolt.org/z/3q1qxqK4a) and won't emit the versions below, which usually
889 // boil down to a single tailor-made instruction.
890 //
891 // __builtin_clz():
892 // Returns the number of leading 0-bits in x, starting at the most significant bit
893 // position. If x is 0, the result is undefined.
894 //
895 // see https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
896 
897 #if PetscHasBuiltin(__builtin_clz)
898 template <>
round_up_to_next_pow2(unsigned int v)899 inline constexpr unsigned int round_up_to_next_pow2(unsigned int v) noexcept
900 {
901   return v <= 1 ? 1 : 1 << ((sizeof(v) * CHAR_BIT) - __builtin_clz(v - 1));
902 }
903 #endif
904 
905 #if PetscHasBuiltin(__builtin_clzl)
906 template <>
round_up_to_next_pow2(unsigned long v)907 inline constexpr unsigned long round_up_to_next_pow2(unsigned long v) noexcept
908 {
909   return v <= 1 ? 1 : 1 << ((sizeof(v) * CHAR_BIT) - __builtin_clzl(v - 1));
910 }
911 #endif
912 
913 // both MSVC and Intel compilers lie about having __builtin_clzll so just disable this
914 #if PetscHasBuiltin(__builtin_clzll) && !PetscDefined(HAVE_WINDOWS_COMPILERS)
915 template <>
round_up_to_next_pow2(unsigned long long v)916 inline constexpr unsigned long long round_up_to_next_pow2(unsigned long long v) noexcept
917 {
918   return v <= 1 ? 1 : 1 << ((sizeof(v) * CHAR_BIT) - __builtin_clzll(v - 1));
919 }
920 #endif
921 
922 template <typename T>
integer_log2(T x)923 static inline constexpr unsigned integer_log2(T x) noexcept
924 {
925   static_assert(std::numeric_limits<T>::is_integer && std::is_unsigned<T>::value, "");
926   return x ? 1 + integer_log2(x >> 1) : std::numeric_limits<unsigned>::max();
927 }
928 
929 } // namespace detail
930 
931 template <typename V, typename H, typename KE>
resize(size_type req_size)932 inline PetscErrorCode KHashTable<V, H, KE>::resize(size_type req_size) noexcept
933 {
934   constexpr size_type min_n_buckets = 4;
935   const auto          new_n_buckets = std::max(detail::round_up_to_next_pow2(req_size), min_n_buckets);
936   const auto          new_size      = (new_n_buckets >> 1) + (new_n_buckets >> 2);
937 
938   PetscFunctionBegin;
939   if (req_size == 0) {
940     // resize(0) is nominally equivalent to clear(), but clear() does not actually reduce
941     // capacity (only resets flags_ to default_bit_pattern()). So we manually kill the capacity
942     // first.
943     PetscCallCXX(flags_.clear());
944     PetscCall(clear());
945     PetscFunctionReturn(PETSC_SUCCESS);
946   }
947   // hash table count to be changed (shrink or expand); rehash
948   if (size() < new_size) {
949     const auto old_n_buckets = bucket_count();
950     const auto new_mask      = new_n_buckets - 1;
951     const auto khash_fsize   = [](size_type size) -> size_type {
952       if (size >= flag_pairs_per_bucket::value) {
953         // use constexpr here to force compiler to evaluate this at all optimization levels
954         constexpr auto shift_val = detail::integer_log2(flag_pairs_per_bucket::value);
955 
956         return size >> shift_val;
957       }
958       return 1;
959     };
960     std::vector<flags_type> new_flags(khash_fsize(new_n_buckets), default_bit_pattern());
961 
962     // grow the hash table, note order is important! we cannot just call
963     // values_.resize(new_n_buckets) because that might drop buckets containing data. The loop
964     // below (if new_n_buckets < bucket_count()) will compress the table, such that we can
965     // shrink afterwards
966     if (old_n_buckets < new_n_buckets) PetscCallCXX(values_.resize(new_n_buckets));
967     for (size_type i = 0; i < old_n_buckets; ++i) {
968       if (!occupied(i)) continue;
969       // kick-out process; sort of like in Cuckoo hashing
970       PetscCall(khash_set_deleted_<true>(i));
971       while (true) {
972         // key is updated every loop from the swap so need to recompute the hash function each
973         // time... could possibly consider stashing the hash value in the key-value pair
974         auto      &key  = values_[i];
975         const auto hash = hash_function()(PETSC_OPTIONAL_GET_KEY(key));
976         auto       j    = hash & new_mask;
977         khash_int  step = 0;
978 
979         while (!khash_is_empty_(j, new_flags)) {
980           ++step;
981           j = (j + step) & new_mask;
982         }
983         PetscCall(khash_set_empty_<false>(j, new_flags));
984         if (j < old_n_buckets && occupied(j)) {
985           using std::swap;
986 
987           // i == j should never reach this point since occupied(j) (in this case equivalent
988           // to occupied(i)) should never be true because we call khash_set_deleted_<true>(i)
989           // above!
990           PetscAssert(i != j, PETSC_COMM_SELF, PETSC_ERR_PLIB, "i %zu = j %zu. About to swap the same element!", static_cast<std::size_t>(i), static_cast<std::size_t>(j));
991           // kick out the existing element
992           PetscCallCXX(swap(values_[j], key));
993           // mark it as deleted in the old hash table
994           PetscCall(khash_set_deleted_<true>(j));
995         } else {
996           // write the element and jump out of the loop but check that we don't self
997           // move-assign
998           if (i != j) PetscCallCXX(values_[j] = std::move(key));
999           break;
1000         }
1001       }
1002     }
1003 
1004     // shrink the hash table
1005     if (old_n_buckets > new_n_buckets) PetscCallCXX(values_.resize(new_n_buckets));
1006     PetscCallCXX(flags_ = std::move(new_flags));
1007     n_occupied_  = count_;
1008     upper_bound_ = new_size;
1009   }
1010   PetscFunctionReturn(PETSC_SUCCESS);
1011 }
1012 
1013 template <typename V, typename H, typename KE>
clear()1014 inline PetscErrorCode KHashTable<V, H, KE>::clear() noexcept
1015 {
1016   PetscFunctionBegin;
1017   PetscCallCXX(values_.clear());
1018   PetscCallCXX(std::fill(flags_.begin(), flags_.end(), default_bit_pattern()));
1019   count_       = 0;
1020   n_occupied_  = 0;
1021   upper_bound_ = 0;
1022   PetscAssert(size() == 0, PETSC_COMM_SELF, PETSC_ERR_PLIB, "clear() did not set size (%zu) to 0", size());
1023   PetscFunctionReturn(PETSC_SUCCESS);
1024 }
1025 
1026 template <typename V, typename H, typename KE>
occupied(khash_int it) const1027 inline bool KHashTable<V, H, KE>::occupied(khash_int it) const noexcept
1028 {
1029   return khash_occupied_(it, flags_);
1030 }
1031 
1032 template <typename V, typename H, typename KE>
occupied(const_iterator it) const1033 inline bool KHashTable<V, H, KE>::occupied(const_iterator it) const noexcept
1034 {
1035   return occupied(it.it_);
1036 }
1037 
1038 template <typename V, typename H, typename KE>
erase(iterator pos)1039 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::erase(iterator pos) noexcept
1040 {
1041   iterator ret(pos);
1042 
1043   PetscFunctionBegin;
1044   ++ret;
1045   PetscCallAbort(PETSC_COMM_SELF, khash_erase_(pos.it_));
1046   PetscFunctionReturn(ret);
1047 }
1048 
1049 template <typename V, typename H, typename KE>
erase(const_iterator pos)1050 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::erase(const_iterator pos) noexcept
1051 {
1052   iterator ret(pos);
1053 
1054   PetscFunctionBegin;
1055   ++ret;
1056   PetscCallAbort(PETSC_COMM_SELF, khash_erase_(pos.it_));
1057   PetscFunctionReturn(ret);
1058 }
1059 
1060 template <typename V, typename H, typename KE>
erase(const_iterator begin_pos,const_iterator end_pos)1061 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::erase(const_iterator begin_pos, const_iterator end_pos) noexcept
1062 {
1063   PetscFunctionBegin;
1064   if (begin_pos == cbegin() && end_pos == cend()) {
1065     PetscCallAbort(PETSC_COMM_SELF, clear());
1066     PetscFunctionReturn(end());
1067   }
1068   for (; begin_pos != end_pos; ++begin_pos) PetscCallAbort(PETSC_COMM_SELF, khash_erase_(begin_pos.it_));
1069   PetscFunctionReturn(make_iterator_(begin_pos.it_));
1070 }
1071 
1072 template <typename V, typename H, typename KE>
1073 template <typename... Args>
emplace(Args &&...args)1074 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::emplace(Args &&...args) noexcept
1075 {
1076   return find_and_emplace_(value_type{std::forward<Args>(args)...});
1077 }
1078 
1079 template <typename V, typename H, typename KE>
insert(const value_type & val)1080 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::insert(const value_type &val) noexcept
1081 {
1082   return find_and_emplace_(val);
1083 }
1084 
1085 template <typename V, typename H, typename KE>
insert(const_iterator,const value_type & val)1086 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::insert(const_iterator, const value_type &val) noexcept
1087 {
1088   return insert(val).first;
1089 }
1090 
1091 template <typename V, typename H, typename KE>
insert(value_type && val)1092 inline std::pair<typename KHashTable<V, H, KE>::iterator, bool> KHashTable<V, H, KE>::insert(value_type &&val) noexcept
1093 {
1094   return find_and_emplace_(std::move(val));
1095 }
1096 
1097 template <typename V, typename H, typename KE>
insert(const_iterator,value_type && val)1098 inline typename KHashTable<V, H, KE>::iterator KHashTable<V, H, KE>::insert(const_iterator, value_type &&val) noexcept
1099 {
1100   return insert(std::move(val)).first;
1101 }
1102 
1103 template <typename V, typename H, typename KE>
hash_function() const1104 inline typename KHashTable<V, H, KE>::hasher KHashTable<V, H, KE>::hash_function() const noexcept
1105 {
1106   return this->first();
1107 }
1108 
1109 template <typename V, typename H, typename KE>
key_eq() const1110 inline typename KHashTable<V, H, KE>::key_equal KHashTable<V, H, KE>::key_eq() const noexcept
1111 {
1112   return this->second();
1113 }
1114 
1115 template <typename V, typename H, typename KE>
shrink_to_fit()1116 inline PetscErrorCode KHashTable<V, H, KE>::shrink_to_fit() noexcept
1117 {
1118   PetscFunctionBegin;
1119   PetscCall(resize(size()));
1120   PetscFunctionReturn(PETSC_SUCCESS);
1121 }
1122 
1123 template <typename V, typename H, typename KE>
swap(KHashTable & other)1124 inline void KHashTable<V, H, KE>::swap(KHashTable &other) noexcept
1125 {
1126   PetscFunctionBegin;
1127   if (PetscUnlikely(this != &other)) {
1128     using std::swap;
1129 
1130     swap(values_, other.values_);
1131     swap(flags_, other.flags_);
1132     swap(count_, other.count_);
1133     swap(n_occupied_, other.n_occupied_);
1134     swap(upper_bound_, other.upper_bound_);
1135   }
1136   PetscFunctionReturnVoid();
1137 }
1138 
1139 namespace detail
1140 {
1141 
1142 template <typename KeyType, typename Hasher>
1143 struct indirect_hasher : Hasher {
1144   using nested_value_type = Hasher;
1145   using key_type          = KeyType;
1146 
1147   template <typename T>
operator ()Petsc::khash::detail::indirect_hasher1148   PETSC_NODISCARD std::size_t operator()(const std::pair<key_type, T> &kv) const noexcept
1149   {
1150     return static_cast<const nested_value_type &>(*this)(kv.first);
1151   }
1152 
1153 #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__)
1154   #pragma GCC diagnostic push
1155   #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
1156 #endif
1157   template <typename T>
operator ()Petsc::khash::detail::indirect_hasher1158   PETSC_NODISCARD std::size_t operator()(const std::pair<key_type, T> &kv) noexcept
1159   {
1160     return static_cast<nested_value_type &>(*this)(kv.first);
1161   }
1162 #if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__)
1163   #pragma GCC diagnostic pop
1164 #endif
1165 
1166   using nested_value_type::operator();
1167 };
1168 
1169 template <typename KeyType, typename KeyEqual>
1170 struct indirect_equal : KeyEqual {
1171   using nested_value_type = KeyEqual;
1172   using key_type          = KeyType;
1173 
1174   template <typename T>
operator ()Petsc::khash::detail::indirect_equal1175   PETSC_NODISCARD bool operator()(const std::pair<key_type, T> &lhs, const std::pair<key_type, T> &rhs) const noexcept
1176   {
1177     return static_cast<const nested_value_type &>(*this)(lhs.first, rhs.first);
1178   }
1179 
1180   template <typename T>
operator ()Petsc::khash::detail::indirect_equal1181   PETSC_NODISCARD bool operator()(const std::pair<key_type, T> &lhs, const key_type &rhs) const noexcept
1182   {
1183     return static_cast<const nested_value_type &>(*this)(lhs.first, rhs);
1184   }
1185 };
1186 
1187 } // namespace detail
1188 
1189 } // namespace khash
1190 
1191 // ==========================================================================================
1192 // UnorderedMap - A drop-in replacement for std::unordered_map that is more memory efficient
1193 // and performant.
1194 //
1195 // Has identical API to a C++17 conformant std::unordered_map, and behaves identically to
1196 // it. The only exception is iterator invalidation:
1197 //
1198 //  Operation                                  |  std::unorderd_map    | Petsc::UnorderedMap
1199 // --------------------------------------------|-----------------------|---------------------
1200 // - All read only operations, swap, std::swap | Never                 | Never
1201 // - clear, operator=                          | Always                | Always
1202 // - rehash, reserve                           | Always                | Only if causes
1203 //                                             |                       | resizing
1204 // - insert, emplace, emplace_hint, operator[] | Only if causes rehash | Only if it causes
1205 //                                             |                       | rehash, in which case
1206 //                                             |                       | rehash will ALWAYS
1207 //                                             |                       | resize
1208 // - erase                                     | Only to the element   | Only to the element
1209 //                                             | erased                | erased
1210 // ==========================================================================================
1211 template <typename K, typename T, typename H = std::hash<K>,
1212 #if PETSC_CPP_VERSION >= 14
1213           typename KE = std::equal_to<>
1214 #else
1215           typename KE = std::equal_to<K>
1216 #endif
1217           >
1218 class UnorderedMap;
1219 
1220 template <typename KeyType, typename T, typename Hash, typename KeyEqual>
1221 class UnorderedMap : public khash::KHashTable<std::pair<KeyType, T>, khash::detail::indirect_hasher<KeyType, Hash>, khash::detail::indirect_equal<KeyType, KeyEqual>> {
1222   using table_type = khash::KHashTable<std::pair<KeyType, T>, khash::detail::indirect_hasher<KeyType, Hash>, khash::detail::indirect_equal<KeyType, KeyEqual>>;
1223   using typename table_type::khash_int;
1224 
1225 public:
1226   // workaround for MSVC bug
1227   // https://developercommunity.visualstudio.com/t/error-c2244-unable-to-match-function-definition-to/225941
1228   using value_type      = typename table_type::value_type;
1229   using key_type        = typename value_type::first_type;
1230   using mapped_type     = typename value_type::second_type;
1231   using hasher          = typename table_type::hasher::nested_value_type;
1232   using key_equal       = typename table_type::key_equal::nested_value_type;
1233   using size_type       = typename table_type::size_type;
1234   using reference       = typename table_type::reference;
1235   using const_reference = typename table_type::const_reference;
1236   using iterator        = typename table_type::iterator;
1237   using const_iterator  = typename table_type::const_iterator;
1238 
1239   using table_type::table_type; // inherit constructors
1240 
1241   PETSC_NODISCARD iterator       find(const key_type &) noexcept;
1242   PETSC_NODISCARD const_iterator find(const key_type &) const noexcept;
1243 
1244   template <typename... Args>
1245   std::pair<iterator, bool> emplace(Args &&...) noexcept;
1246 
1247   using table_type::erase; // inherit erase overloads
1248   size_type erase(const key_type &) noexcept;
1249 
1250   mapped_type &operator[](const key_type &) noexcept;
1251 
1252   PETSC_NODISCARD std::pair<iterator, iterator>             equal_range(const key_type &) noexcept;
1253   PETSC_NODISCARD std::pair<const_iterator, const_iterator> equal_range(const key_type &) const noexcept;
1254 
1255   PETSC_NODISCARD size_type count(const key_type &) const noexcept;
1256   PETSC_NODISCARD bool      contains(const key_type &) const noexcept;
1257 
1258   // must be declared in class definition...
swap(UnorderedMap & lhs,UnorderedMap & rhs)1259   friend void swap(UnorderedMap &lhs, UnorderedMap &rhs) noexcept
1260   {
1261     PetscFunctionBegin;
1262     PetscCallCXXAbort(PETSC_COMM_SELF, lhs.swap(rhs));
1263     PetscFunctionReturnVoid();
1264   }
1265 
1266 private:
1267   template <typename KeyTuple, typename ArgTuple>
1268   PETSC_NODISCARD std::pair<iterator, bool> emplace_(std::piecewise_construct_t, KeyTuple &&, ArgTuple &&) noexcept;
1269   template <typename Key, typename... Args>
1270   PETSC_NODISCARD std::pair<iterator, bool> emplace_(Key &&, Args &&...) noexcept;
1271 };
1272 
1273 // ==========================================================================================
1274 // UnorderedMap - Private API
1275 // ==========================================================================================
1276 
1277 template <typename K, typename T, typename H, typename KE>
1278 template <typename KeyTuple, typename MappedTuple>
emplace_(std::piecewise_construct_t pcw,KeyTuple && key_tuple,MappedTuple && mapped_type_constructor_args)1279 inline std::pair<typename UnorderedMap<K, T, H, KE>::iterator, bool> UnorderedMap<K, T, H, KE>::emplace_(std::piecewise_construct_t pcw, KeyTuple &&key_tuple, MappedTuple &&mapped_type_constructor_args) noexcept
1280 {
1281   // clang-format off
1282   return this->find_and_emplace_(
1283     std::get<0>(key_tuple),
1284     pcw,
1285     std::forward<KeyTuple>(key_tuple),
1286     std::forward<MappedTuple>(mapped_type_constructor_args)
1287   );
1288   // clang-format on
1289 }
1290 
1291 template <typename K, typename T, typename H, typename KE>
1292 template <typename Key, typename... Args>
emplace_(Key && key,Args &&...mapped_type_constructor_args)1293 inline std::pair<typename UnorderedMap<K, T, H, KE>::iterator, bool> UnorderedMap<K, T, H, KE>::emplace_(Key &&key, Args &&...mapped_type_constructor_args) noexcept
1294 {
1295   return this->emplace_(std::piecewise_construct, std::forward_as_tuple(std::forward<Key>(key)), std::forward_as_tuple(std::forward<Args>(mapped_type_constructor_args)...));
1296 }
1297 
1298 // ==========================================================================================
1299 // UnorderedMap - Public API
1300 // ==========================================================================================
1301 
1302 template <typename K, typename T, typename H, typename KE>
find(const key_type & key)1303 inline typename UnorderedMap<K, T, H, KE>::iterator UnorderedMap<K, T, H, KE>::find(const key_type &key) noexcept
1304 {
1305   khash_int it = 0;
1306 
1307   PetscFunctionBegin;
1308   PetscCallAbort(PETSC_COMM_SELF, this->khash_find_(key, &it));
1309   PetscFunctionReturn(this->make_iterator_(it));
1310 }
1311 
1312 template <typename K, typename T, typename H, typename KE>
find(const key_type & key) const1313 inline typename UnorderedMap<K, T, H, KE>::const_iterator UnorderedMap<K, T, H, KE>::find(const key_type &key) const noexcept
1314 {
1315   khash_int it = 0;
1316 
1317   PetscFunctionBegin;
1318   PetscCallAbort(PETSC_COMM_SELF, this->khash_find_(key, &it));
1319   PetscFunctionReturn(this->make_iterator_(it));
1320 }
1321 
1322 template <typename K, typename T, typename H, typename KE>
1323 template <typename... Args>
emplace(Args &&...args)1324 inline std::pair<typename UnorderedMap<K, T, H, KE>::iterator, bool> UnorderedMap<K, T, H, KE>::emplace(Args &&...args) noexcept
1325 {
1326   return this->emplace_(std::forward<Args>(args)...);
1327 }
1328 
1329 template <typename K, typename T, typename H, typename KE>
operator [](const key_type & key)1330 inline typename UnorderedMap<K, T, H, KE>::mapped_type &UnorderedMap<K, T, H, KE>::operator[](const key_type &key) noexcept
1331 {
1332   return this->emplace(key).first->second;
1333 }
1334 
1335 template <typename K, typename T, typename H, typename KE>
erase(const key_type & key)1336 inline typename UnorderedMap<K, T, H, KE>::size_type UnorderedMap<K, T, H, KE>::erase(const key_type &key) noexcept
1337 {
1338   PetscFunctionBegin;
1339   {
1340     auto it = this->find(key);
1341 
1342     if (it == this->end()) PetscFunctionReturn(0);
1343     PetscCallCXX(this->erase(it));
1344   }
1345   PetscFunctionReturn(1);
1346 }
1347 
1348 template <typename K, typename T, typename H, typename KE>
equal_range(const key_type & key)1349 inline std::pair<typename UnorderedMap<K, T, H, KE>::iterator, typename UnorderedMap<K, T, H, KE>::iterator> UnorderedMap<K, T, H, KE>::equal_range(const key_type &key) noexcept
1350 {
1351   auto it = this->find(key);
1352   return {it, it == this->end() ? it : std::next(it)};
1353 }
1354 
1355 template <typename K, typename T, typename H, typename KE>
equal_range(const key_type & key) const1356 inline std::pair<typename UnorderedMap<K, T, H, KE>::const_iterator, typename UnorderedMap<K, T, H, KE>::const_iterator> UnorderedMap<K, T, H, KE>::equal_range(const key_type &key) const noexcept
1357 {
1358   auto it = this->find(key);
1359   return {it, it == this->end() ? it : std::next(it)};
1360 }
1361 
1362 template <typename K, typename T, typename H, typename KE>
count(const key_type & key) const1363 inline typename UnorderedMap<K, T, H, KE>::size_type UnorderedMap<K, T, H, KE>::count(const key_type &key) const noexcept
1364 {
1365   return this->contains(key);
1366 }
1367 
1368 template <typename K, typename T, typename H, typename KE>
contains(const key_type & key) const1369 inline bool UnorderedMap<K, T, H, KE>::contains(const key_type &key) const noexcept
1370 {
1371   return this->find(key) != this->end();
1372 }
1373 
1374 // ==========================================================================================
1375 // UnorderedMap - Global functions
1376 // ==========================================================================================
1377 
1378 template <typename K, typename T, typename H, typename KE>
operator ==(const UnorderedMap<K,T,H,KE> & lhs,const UnorderedMap<K,T,H,KE> & rhs)1379 PETSC_NODISCARD bool operator==(const UnorderedMap<K, T, H, KE> &lhs, const UnorderedMap<K, T, H, KE> &rhs) noexcept
1380 {
1381   PetscFunctionBegin;
1382   if (lhs.size() != rhs.size()) PetscFunctionReturn(false);
1383   for (auto it = lhs.begin(), lhs_end = lhs.end(), rhs_end = rhs.end(); it != lhs_end; ++it) {
1384     const auto rhs_it = rhs.find(it->first);
1385 
1386     if (rhs_it == rhs_end || !(*it == *rhs_it)) PetscFunctionReturn(false);
1387   }
1388   PetscFunctionReturn(true);
1389 }
1390 
1391 template <typename K, typename T, typename H, typename KE>
operator !=(const UnorderedMap<K,T,H,KE> & lhs,const UnorderedMap<K,T,H,KE> & rhs)1392 PETSC_NODISCARD bool operator!=(const UnorderedMap<K, T, H, KE> &lhs, const UnorderedMap<K, T, H, KE> &rhs) noexcept
1393 {
1394   return !(lhs == rhs);
1395 }
1396 
1397 } // namespace Petsc
1398 
1399 #undef PETSC_OPTIONAL_GET_KEY
1400 
1401 #if defined(__clang__)
1402   #pragma clang diagnostic pop
1403 #endif
1404