1 /*
2     Copyright (c) 2005-2021 Intel Corporation
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 __TBB_concurrent_hash_map_H
18 #define __TBB_concurrent_hash_map_H
19 
20 #include "detail/_namespace_injection.h"
21 #include "detail/_utils.h"
22 #include "detail/_assert.h"
23 #include "detail/_allocator_traits.h"
24 #include "detail/_containers_helpers.h"
25 #include "detail/_template_helpers.h"
26 #include "detail/_hash_compare.h"
27 #include "detail/_range_common.h"
28 #include "tbb_allocator.h"
29 #include "spin_rw_mutex.h"
30 
31 #include <atomic>
32 #include <initializer_list>
33 #include <tuple>
34 #include <iterator>
35 #include <utility>      // Need std::pair
36 #include <cstring>      // Need std::memset
37 
38 namespace tbb {
39 namespace detail {
40 namespace d2 {
41 
42 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS && __TBB_CPP20_CONCEPTS_PRESENT
43 template <typename Mutex>
44 concept ch_map_rw_scoped_lockable = rw_scoped_lockable<Mutex> &&
45 	requires(const typename Mutex::scoped_lock& sl) {
46 		{ sl.is_writer() } -> std::convertible_to<bool>;
47 };
48 #endif
49 
50 template <typename MutexType>
51 struct hash_map_node_base : no_copy {
52     using mutex_type = MutexType;
53     // Scoped lock type for mutex
54     using scoped_type = typename MutexType::scoped_lock;
55     // Next node in chain
56     hash_map_node_base* next;
57     mutex_type mutex;
58 };
59 
60 // Incompleteness flag value
61 static void* const rehash_req_flag = reinterpret_cast<void*>(std::size_t(3));
62 // Rehashed empty bucket flag
63 static void* const empty_rehashed_flag = reinterpret_cast<void*>(std::size_t(0));
64 
65 template <typename MutexType>
66 bool rehash_required( hash_map_node_base<MutexType>* node_ptr ) {
67     return reinterpret_cast<void*>(node_ptr) == rehash_req_flag;
68 }
69 
70 #if TBB_USE_ASSERT
71 template <typename MutexType>
72 bool empty_rehashed( hash_map_node_base<MutexType>* node_ptr ) {
73     return reinterpret_cast<void*>(node_ptr) == empty_rehashed_flag;
74 }
75 #endif
76 
77 // base class of concurrent_hash_map
78 
79 template <typename Allocator, typename MutexType>
80 class hash_map_base {
81 public:
82     using size_type = std::size_t;
83     using hashcode_type = std::size_t;
84     using segment_index_type = std::size_t;
85     using node_base = hash_map_node_base<MutexType>;
86 
87     struct bucket : no_copy {
88         using mutex_type = MutexType;
89         using scoped_type = typename mutex_type::scoped_lock;
90 
91         bucket() : node_list(nullptr) {}
92         bucket( node_base* ptr ) : node_list(ptr) {}
93 
94         mutex_type mutex;
95         std::atomic<node_base*> node_list;
96     };
97 
98     using allocator_type = Allocator;
99     using allocator_traits_type = tbb::detail::allocator_traits<allocator_type>;
100     using bucket_allocator_type = typename allocator_traits_type::template rebind_alloc<bucket>;
101     using bucket_allocator_traits = tbb::detail::allocator_traits<bucket_allocator_type>;
102 
103     // Count of segments in the first block
104     static constexpr size_type embedded_block = 1;
105     // Count of segments in the first block
106     static constexpr size_type embedded_buckets = 1 << embedded_block;
107     // Count of segments in the first block
108     static constexpr size_type first_block = 8; //including embedded_block. perfect with bucket size 16, so the allocations are power of 4096
109     // Size of a pointer / table size
110     static constexpr size_type pointers_per_table = sizeof(segment_index_type) * 8; // one segment per bit
111 
112     using segment_ptr_type = bucket*;
113     using atomic_segment_type = std::atomic<segment_ptr_type>;
114     using segments_table_type = atomic_segment_type[pointers_per_table];
115 
116     hash_map_base( const allocator_type& alloc ) : my_allocator(alloc), my_mask(embedded_buckets - 1), my_size(0) {
117         for (size_type i = 0; i != embedded_buckets; ++i) {
118             my_embedded_segment[i].node_list.store(nullptr, std::memory_order_relaxed);
119         }
120 
121         for (size_type segment_index = 0; segment_index < pointers_per_table; ++segment_index) {
122             auto argument = segment_index < embedded_block ? my_embedded_segment + segment_base(segment_index) : nullptr;
123             my_table[segment_index].store(argument, std::memory_order_relaxed);
124         }
125 
126         __TBB_ASSERT( embedded_block <= first_block, "The first block number must include embedded blocks");
127     }
128 
129     // segment index of given index in the array
130     static segment_index_type segment_index_of( size_type index ) {
131         return segment_index_type(tbb::detail::log2( index|1 ));
132     }
133 
134     // the first array index of given segment
135     static segment_index_type segment_base( segment_index_type k ) {
136         return (segment_index_type(1) << k & ~segment_index_type(1));
137     }
138 
139     // segment size except for k == 0
140     static size_type segment_size( segment_index_type k ) {
141         return size_type(1) << k; // fake value for k==0
142     }
143 
144     // true if ptr is valid pointer
145     static bool is_valid( void* ptr ) {
146         return reinterpret_cast<uintptr_t>(ptr) > uintptr_t(63);
147     }
148 
149     template <typename... Args>
150     void init_buckets_impl( segment_ptr_type ptr, size_type sz, Args&&... args ) {
151         for (size_type i = 0; i < sz; ++i) {
152             bucket_allocator_traits::construct(my_allocator, ptr + i, std::forward<Args>(args)...);
153         }
154     }
155 
156     // Initialize buckets
157     void init_buckets( segment_ptr_type ptr, size_type sz, bool is_initial ) {
158         if (is_initial) {
159             init_buckets_impl(ptr, sz);
160         } else {
161             init_buckets_impl(ptr, sz, reinterpret_cast<node_base*>(rehash_req_flag));
162         }
163     }
164 
165     // Add node n to bucket b
166     static void add_to_bucket( bucket* b, node_base* n ) {
167         __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), nullptr);
168         n->next = b->node_list.load(std::memory_order_relaxed);
169         b->node_list.store(n, std::memory_order_relaxed); // its under lock and flag is set
170     }
171 
172     const bucket_allocator_type& get_allocator() const {
173         return my_allocator;
174     }
175 
176     bucket_allocator_type& get_allocator() {
177         return my_allocator;
178     }
179 
180     // Enable segment
181     void enable_segment( segment_index_type k, bool is_initial = false ) {
182         __TBB_ASSERT( k, "Zero segment must be embedded" );
183         size_type sz;
184         __TBB_ASSERT( !is_valid(my_table[k].load(std::memory_order_relaxed)), "Wrong concurrent assignment");
185         if (k >= first_block) {
186             sz = segment_size(k);
187             segment_ptr_type ptr = nullptr;
188             try_call( [&] {
189                 ptr = bucket_allocator_traits::allocate(my_allocator, sz);
190             } ).on_exception( [&] {
191                 my_table[k].store(nullptr, std::memory_order_relaxed);
192             });
193 
194             __TBB_ASSERT(ptr, nullptr);
195             init_buckets(ptr, sz, is_initial);
196             my_table[k].store(ptr, std::memory_order_release);
197             sz <<= 1;// double it to get entire capacity of the container
198         } else { // the first block
199             __TBB_ASSERT( k == embedded_block, "Wrong segment index" );
200             sz = segment_size(first_block);
201             segment_ptr_type ptr = nullptr;
202             try_call( [&] {
203                 ptr = bucket_allocator_traits::allocate(my_allocator, sz - embedded_buckets);
204             } ).on_exception( [&] {
205                 my_table[k].store(nullptr, std::memory_order_relaxed);
206             });
207 
208             __TBB_ASSERT(ptr, nullptr);
209             init_buckets(ptr, sz - embedded_buckets, is_initial);
210             ptr -= segment_base(embedded_block);
211             for(segment_index_type i = embedded_block; i < first_block; i++) // calc the offsets
212                 my_table[i].store(ptr + segment_base(i), std::memory_order_release);
213         }
214         my_mask.store(sz-1, std::memory_order_release);
215     }
216 
217     void delete_segment( segment_index_type s ) {
218         segment_ptr_type buckets_ptr = my_table[s].load(std::memory_order_relaxed);
219         size_type sz = segment_size( s ? s : 1 );
220 
221         size_type deallocate_size = 0;
222 
223         if (s >= first_block) { // the first segment or the next
224             deallocate_size = sz;
225         } else if (s == embedded_block && embedded_block != first_block) {
226             deallocate_size = segment_size(first_block) - embedded_buckets;
227         }
228 
229         for (size_type i = 0; i < deallocate_size; ++i) {
230             bucket_allocator_traits::destroy(my_allocator, buckets_ptr + i);
231         }
232         if (deallocate_size != 0) {
233             bucket_allocator_traits::deallocate(my_allocator, buckets_ptr, deallocate_size);
234         }
235 
236         if (s >= embedded_block) my_table[s].store(nullptr, std::memory_order_relaxed);
237     }
238 
239     // Get bucket by (masked) hashcode
240     bucket *get_bucket( hashcode_type h ) const noexcept {
241         segment_index_type s = segment_index_of( h );
242         h -= segment_base(s);
243         segment_ptr_type seg = my_table[s].load(std::memory_order_acquire);
244         __TBB_ASSERT( is_valid(seg), "hashcode must be cut by valid mask for allocated segments" );
245         return &seg[h];
246     }
247 
248     // detail serial rehashing helper
249     void mark_rehashed_levels( hashcode_type h ) noexcept {
250         segment_index_type s = segment_index_of( h );
251         while (segment_ptr_type seg = my_table[++s].load(std::memory_order_relaxed))
252             if (rehash_required(seg[h].node_list.load(std::memory_order_relaxed))) {
253                 seg[h].node_list.store(reinterpret_cast<node_base*>(empty_rehashed_flag), std::memory_order_relaxed);
254                 mark_rehashed_levels( h + ((hashcode_type)1<<s) ); // optimized segment_base(s)
255             }
256     }
257 
258     // Check for mask race
259     // Splitting into two functions should help inlining
260     inline bool check_mask_race( const hashcode_type h, hashcode_type &m ) const {
261         hashcode_type m_now, m_old = m;
262         m_now = my_mask.load(std::memory_order_acquire);
263         if (m_old != m_now) {
264             return check_rehashing_collision(h, m_old, m = m_now);
265         }
266         return false;
267     }
268 
269     // Process mask race, check for rehashing collision
270     bool check_rehashing_collision( const hashcode_type h, hashcode_type m_old, hashcode_type m ) const {
271         __TBB_ASSERT(m_old != m, nullptr); // TODO?: m arg could be optimized out by passing h = h&m
272         if( (h & m_old) != (h & m) ) { // mask changed for this hashcode, rare event
273             // condition above proves that 'h' has some other bits set beside 'm_old'
274             // find next applicable mask after m_old    //TODO: look at bsl instruction
275             for( ++m_old; !(h & m_old); m_old <<= 1 ) // at maximum few rounds depending on the first block size
276                 ;
277             m_old = (m_old<<1) - 1; // get full mask from a bit
278             __TBB_ASSERT((m_old&(m_old+1))==0 && m_old <= m, nullptr);
279             // check whether it is rehashing/ed
280             if (!rehash_required(get_bucket(h & m_old)->node_list.load(std::memory_order_acquire))) {
281                 return true;
282             }
283         }
284         return false;
285     }
286 
287     // Insert a node and check for load factor. @return segment index to enable.
288     segment_index_type insert_new_node( bucket *b, node_base *n, hashcode_type mask ) {
289         size_type sz = ++my_size; // prefix form is to enforce allocation after the first item inserted
290         add_to_bucket( b, n );
291         // check load factor
292         if( sz >= mask ) { // TODO: add custom load_factor
293             segment_index_type new_seg = tbb::detail::log2( mask+1 ); //optimized segment_index_of
294             __TBB_ASSERT( is_valid(my_table[new_seg-1].load(std::memory_order_relaxed)), "new allocations must not publish new mask until segment has allocated");
295             static const segment_ptr_type is_allocating = segment_ptr_type(2);;
296             segment_ptr_type disabled = nullptr;
297             if (!(my_table[new_seg].load(std::memory_order_acquire))
298                 && my_table[new_seg].compare_exchange_strong(disabled, is_allocating))
299                 return new_seg; // The value must be processed
300         }
301         return 0;
302     }
303 
304     // Prepare enough segments for number of buckets
305     void reserve(size_type buckets) {
306         if( !buckets-- ) return;
307         bool is_initial = !my_size.load(std::memory_order_relaxed);
308         for (size_type m = my_mask.load(std::memory_order_relaxed); buckets > m;
309             m = my_mask.load(std::memory_order_relaxed))
310         {
311             enable_segment( segment_index_of( m+1 ), is_initial );
312         }
313     }
314 
315     // Swap hash_map_bases
316     void internal_swap_content(hash_map_base &table) {
317         using std::swap;
318         swap_atomics_relaxed(my_mask, table.my_mask);
319         swap_atomics_relaxed(my_size, table.my_size);
320 
321         for(size_type i = 0; i < embedded_buckets; i++) {
322             auto temp = my_embedded_segment[i].node_list.load(std::memory_order_relaxed);
323             my_embedded_segment[i].node_list.store(table.my_embedded_segment[i].node_list.load(std::memory_order_relaxed),
324                 std::memory_order_relaxed);
325             table.my_embedded_segment[i].node_list.store(temp, std::memory_order_relaxed);
326         }
327         for(size_type i = embedded_block; i < pointers_per_table; i++) {
328             auto temp = my_table[i].load(std::memory_order_relaxed);
329             my_table[i].store(table.my_table[i].load(std::memory_order_relaxed),
330                 std::memory_order_relaxed);
331             table.my_table[i].store(temp, std::memory_order_relaxed);
332         }
333     }
334 
335     void internal_move(hash_map_base&& other) {
336         my_mask.store(other.my_mask.load(std::memory_order_relaxed), std::memory_order_relaxed);
337         other.my_mask.store(embedded_buckets - 1, std::memory_order_relaxed);
338 
339         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
340         other.my_size.store(0, std::memory_order_relaxed);
341 
342         for (size_type i = 0; i < embedded_buckets; ++i) {
343             my_embedded_segment[i].node_list.store(other.my_embedded_segment[i].node_list, std::memory_order_relaxed);
344             other.my_embedded_segment[i].node_list.store(nullptr, std::memory_order_relaxed);
345         }
346 
347         for (size_type i = embedded_block; i < pointers_per_table; ++i) {
348             my_table[i].store(other.my_table[i].load(std::memory_order_relaxed),
349                 std::memory_order_relaxed);
350             other.my_table[i].store(nullptr, std::memory_order_relaxed);
351         }
352     }
353 
354 protected:
355     bucket_allocator_type my_allocator;
356     // Hash mask = sum of allocated segment sizes - 1
357     std::atomic<hashcode_type> my_mask;
358     // Size of container in stored items
359     std::atomic<size_type> my_size; // It must be in separate cache line from my_mask due to performance effects
360     // Zero segment
361     bucket my_embedded_segment[embedded_buckets];
362     // Segment pointers table. Also prevents false sharing between my_mask and my_size
363     segments_table_type my_table;
364 };
365 
366 template <typename Iterator>
367 class hash_map_range;
368 
369 // Meets requirements of a forward iterator for STL
370 // Value is either the T or const T type of the container.
371 template <typename Container, typename Value>
372 class hash_map_iterator {
373     using map_type = Container;
374     using node = typename Container::node;
375     using map_base = typename Container::base_type;
376     using node_base = typename map_base::node_base;
377     using bucket = typename map_base::bucket;
378 public:
379     using value_type = Value;
380     using size_type = typename Container::size_type;
381     using difference_type = typename Container::difference_type;
382     using pointer = value_type*;
383     using reference = value_type&;
384     using iterator_category = std::forward_iterator_tag;
385 
386     // Construct undefined iterator
387     hash_map_iterator(): my_map(), my_index(), my_bucket(), my_node() {}
388     hash_map_iterator( const hash_map_iterator<Container, typename Container::value_type>& other ) :
389         my_map(other.my_map),
390         my_index(other.my_index),
391         my_bucket(other.my_bucket),
392         my_node(other.my_node)
393     {}
394 
395     hash_map_iterator& operator=( const hash_map_iterator<Container, typename Container::value_type>& other ) {
396         my_map = other.my_map;
397         my_index = other.my_index;
398         my_bucket = other.my_bucket;
399         my_node = other.my_node;
400         return *this;
401     }
402 
403     Value& operator*() const {
404         __TBB_ASSERT( map_base::is_valid(my_node), "iterator uninitialized or at end of container?" );
405         return my_node->value();
406     }
407 
408     Value* operator->() const {return &operator*();}
409 
410     hash_map_iterator& operator++() {
411         my_node = static_cast<node*>( my_node->next );
412         if( !my_node ) advance_to_next_bucket();
413         return *this;
414     }
415 
416     // Post increment
417     hash_map_iterator operator++(int) {
418         hash_map_iterator old(*this);
419         operator++();
420         return old;
421     }
422 private:
423     template <typename C, typename T, typename U>
424     friend bool operator==( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j );
425 
426     template <typename C, typename T, typename U>
427     friend bool operator!=( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j );
428 
429     template <typename C, typename T, typename U>
430     friend ptrdiff_t operator-( const hash_map_iterator<C,T>& i, const hash_map_iterator<C,U>& j );
431 
432     template <typename C, typename U>
433     friend class hash_map_iterator;
434 
435     template <typename I>
436     friend class hash_map_range;
437 
438     void advance_to_next_bucket() { // TODO?: refactor to iterator_base class
439         size_t k = my_index+1;
440         __TBB_ASSERT( my_bucket, "advancing an invalid iterator?");
441         while (k <= my_map->my_mask.load(std::memory_order_relaxed)) {
442             // Following test uses 2's-complement wizardry
443             if( k&(k-2) ) // not the beginning of a segment
444                 ++my_bucket;
445             else my_bucket = my_map->get_bucket( k );
446             my_node = static_cast<node*>( my_bucket->node_list.load(std::memory_order_relaxed) );
447             if( map_base::is_valid(my_node) ) {
448                 my_index = k; return;
449             }
450             ++k;
451         }
452         my_bucket = 0; my_node = 0; my_index = k; // the end
453     }
454 
455     template <typename Key, typename T, typename HashCompare, typename A
456 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
457             , typename M
458 			 >
459 		__TBB_requires(tbb::detail::hash_compare<HashCompare, Key> &&
460 					   ch_map_rw_scoped_lockable<M>)
461 #else
462 			 >
463 		__TBB_requires(tbb::detail::hash_compare<HashCompare, Key>)
464 #endif
465     friend class concurrent_hash_map;
466 
467     hash_map_iterator( const Container &map, std::size_t index, const bucket *b, node_base *n ) :
468         my_map(&map), my_index(index), my_bucket(b), my_node(static_cast<node*>(n))
469     {
470         if( b && !map_base::is_valid(n) )
471             advance_to_next_bucket();
472     }
473 
474     // concurrent_hash_map over which we are iterating.
475     const Container *my_map;
476     // Index in hash table for current item
477     size_t my_index;
478     // Pointer to bucket
479     const bucket* my_bucket;
480     // Pointer to node that has current item
481     node* my_node;
482 };
483 
484 template <typename Container, typename T, typename U>
485 bool operator==( const hash_map_iterator<Container,T>& i, const hash_map_iterator<Container,U>& j ) {
486     return i.my_node == j.my_node && i.my_map == j.my_map;
487 }
488 
489 template <typename Container, typename T, typename U>
490 bool operator!=( const hash_map_iterator<Container,T>& i, const hash_map_iterator<Container,U>& j ) {
491     return i.my_node != j.my_node || i.my_map != j.my_map;
492 }
493 
494 // Range class used with concurrent_hash_map
495 template <typename Iterator>
496 class hash_map_range {
497     using map_type = typename Iterator::map_type;
498 public:
499     // Type for size of a range
500     using size_type = std::size_t;
501     using value_type = typename Iterator::value_type;
502     using reference = typename Iterator::reference;
503     using difference_type = typename Iterator::difference_type;
504     using iterator = Iterator;
505 
506     // True if range is empty.
507     bool empty() const { return my_begin == my_end; }
508 
509     // True if range can be partitioned into two subranges.
510     bool is_divisible() const {
511         return my_midpoint != my_end;
512     }
513 
514     // Split range.
515     hash_map_range( hash_map_range& r, split ) :
516         my_end(r.my_end),
517         my_grainsize(r.my_grainsize)
518     {
519         r.my_end = my_begin = r.my_midpoint;
520         __TBB_ASSERT( !empty(), "Splitting despite the range is not divisible" );
521         __TBB_ASSERT( !r.empty(), "Splitting despite the range is not divisible" );
522         set_midpoint();
523         r.set_midpoint();
524     }
525 
526     // Init range with container and grainsize specified
527     hash_map_range( const map_type &map, size_type grainsize_ = 1 ) :
528         my_begin( Iterator( map, 0, map.my_embedded_segment, map.my_embedded_segment->node_list.load(std::memory_order_relaxed) ) ),
529         my_end( Iterator( map, map.my_mask.load(std::memory_order_relaxed) + 1, 0, 0 ) ),
530         my_grainsize( grainsize_ )
531     {
532         __TBB_ASSERT( grainsize_>0, "grainsize must be positive" );
533         set_midpoint();
534     }
535 
536     Iterator begin() const { return my_begin; }
537     Iterator end() const { return my_end; }
538     // The grain size for this range.
539     size_type grainsize() const { return my_grainsize; }
540 
541 private:
542     Iterator my_begin;
543     Iterator my_end;
544     mutable Iterator my_midpoint;
545     size_t my_grainsize;
546     // Set my_midpoint to point approximately half way between my_begin and my_end.
547     void set_midpoint() const;
548     template <typename U> friend class hash_map_range;
549 };
550 
551 template <typename Iterator>
552 void hash_map_range<Iterator>::set_midpoint() const {
553     // Split by groups of nodes
554     size_t m = my_end.my_index-my_begin.my_index;
555     if( m > my_grainsize ) {
556         m = my_begin.my_index + m/2u;
557         auto b = my_begin.my_map->get_bucket(m);
558         my_midpoint = Iterator(*my_begin.my_map,m,b,b->node_list.load(std::memory_order_relaxed));
559     } else {
560         my_midpoint = my_end;
561     }
562     __TBB_ASSERT( my_begin.my_index <= my_midpoint.my_index,
563         "my_begin is after my_midpoint" );
564     __TBB_ASSERT( my_midpoint.my_index <= my_end.my_index,
565         "my_midpoint is after my_end" );
566     __TBB_ASSERT( my_begin != my_midpoint || my_begin == my_end,
567         "[my_begin, my_midpoint) range should not be empty" );
568 }
569 
570 template <typename Key, typename T,
571           typename HashCompare = d1::tbb_hash_compare<Key>,
572           typename Allocator = tbb_allocator<std::pair<const Key, T>>
573 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
574         , typename MutexType = spin_rw_mutex
575          >
576     __TBB_requires(tbb::detail::hash_compare<HashCompare, Key> &&
577                    ch_map_rw_scoped_lockable<MutexType>)
578 #else
579          >
580     __TBB_requires(tbb::detail::hash_compare<HashCompare, Key>)
581 #endif
582 class concurrent_hash_map
583 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
584     : protected hash_map_base<Allocator, MutexType>
585 #else
586     : protected hash_map_base<Allocator, spin_rw_mutex>
587 #endif
588 {
589     template <typename Container, typename Value>
590     friend class hash_map_iterator;
591 
592     template <typename I>
593     friend class hash_map_range;
594     using allocator_traits_type = tbb::detail::allocator_traits<Allocator>;
595 
596 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
597     using base_type = hash_map_base<Allocator, MutexType>;
598 #else
599     using base_type = hash_map_base<Allocator, spin_rw_mutex>;
600 #endif
601 public:
602     using key_type = Key;
603     using mapped_type = T;
604     // type_identity is needed to disable implicit deduction guides for std::initializer_list constructors
605     // and copy/move constructor with explicit allocator argument
606     using allocator_type = tbb::detail::type_identity_t<Allocator>;
607     using hash_compare_type = tbb::detail::type_identity_t<HashCompare>;
608     using value_type = std::pair<const Key, T>;
609     using size_type = typename base_type::size_type;
610     using difference_type = std::ptrdiff_t;
611 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
612     using mutex_type = MutexType;
613 #endif
614     using pointer = typename allocator_traits_type::pointer;
615     using const_pointer = typename allocator_traits_type::const_pointer;
616 
617     using reference = value_type&;
618     using const_reference = const value_type&;
619     using iterator = hash_map_iterator<concurrent_hash_map, value_type>;
620     using const_iterator = hash_map_iterator<concurrent_hash_map, const value_type>;
621     using range_type = hash_map_range<iterator>;
622     using const_range_type = hash_map_range<const_iterator>;
623 
624 protected:
625     static_assert(std::is_same<value_type, typename Allocator::value_type>::value,
626         "value_type of the container must be the same as its allocator's");
627 
628     friend class const_accessor;
629     class node;
630     using segment_index_type = typename base_type::segment_index_type;
631     using segment_ptr_type = typename base_type::segment_ptr_type;
632     using node_base = typename base_type::node_base;
633     using bucket = typename base_type::bucket;
634     using hashcode_type = typename base_type::hashcode_type;
635     using bucket_allocator_type = typename base_type::bucket_allocator_type;
636     using node_allocator_type = typename base_type::allocator_traits_type::template rebind_alloc<node>;
637     using node_allocator_traits = tbb::detail::allocator_traits<node_allocator_type>;
638     hash_compare_type my_hash_compare;
639 
640     class node : public node_base {
641     public:
642         node() {}
643         ~node() {}
644         pointer storage() { return &my_value; }
645         value_type& value() { return *storage(); }
646     private:
647         union {
648             value_type my_value;
649         };
650     };
651 
652     void delete_node( node_base *n ) {
653         node_allocator_type node_allocator(this->get_allocator());
654         node_allocator_traits::destroy(node_allocator, static_cast<node*>(n)->storage());
655         node_allocator_traits::destroy(node_allocator, static_cast<node*>(n));
656         node_allocator_traits::deallocate(node_allocator, static_cast<node*>(n), 1);
657     }
658 
659     template <typename... Args>
660     static node* create_node(bucket_allocator_type& allocator, Args&&... args) {
661         node_allocator_type node_allocator(allocator);
662         node* node_ptr = node_allocator_traits::allocate(node_allocator, 1);
663         auto guard = make_raii_guard([&] {
664             node_allocator_traits::destroy(node_allocator, node_ptr);
665             node_allocator_traits::deallocate(node_allocator, node_ptr, 1);
666         });
667 
668         node_allocator_traits::construct(node_allocator, node_ptr);
669         node_allocator_traits::construct(node_allocator, node_ptr->storage(), std::forward<Args>(args)...);
670         guard.dismiss();
671         return node_ptr;
672     }
673 
674     static node* allocate_node_copy_construct(bucket_allocator_type& allocator, const Key &key, const T * t){
675         return create_node(allocator, key, *t);
676     }
677 
678     static node* allocate_node_move_construct(bucket_allocator_type& allocator, const Key &key, const T * t){
679         return create_node(allocator, key, std::move(*const_cast<T*>(t)));
680     }
681 
682     template <typename K = Key>
683     static node* allocate_node_default_construct(bucket_allocator_type& allocator, const K &key, const T * ){
684         // Emplace construct an empty T object inside the pair
685         return create_node(allocator, std::piecewise_construct,
686                            std::forward_as_tuple(key), std::forward_as_tuple());
687     }
688 
689     static node* do_not_allocate_node(bucket_allocator_type& , const Key &, const T * ){
690         __TBB_ASSERT(false,"this dummy function should not be called");
691         return nullptr;
692     }
693 
694     template <typename K>
695     node *search_bucket( const K &key, bucket *b ) const {
696         node *n = static_cast<node*>( b->node_list.load(std::memory_order_relaxed) );
697         while (this->is_valid(n) && !my_hash_compare.equal(key, n->value().first))
698             n = static_cast<node*>( n->next );
699         __TBB_ASSERT(!rehash_required(n), "Search can be executed only for rehashed bucket");
700         return n;
701     }
702 
703     // bucket accessor is to find, rehash, acquire a lock, and access a bucket
704     class bucket_accessor : public bucket::scoped_type {
705         bucket *my_b;
706     public:
707         bucket_accessor( concurrent_hash_map *base, const hashcode_type h, bool writer = false ) { acquire( base, h, writer ); }
708         // find a bucket by masked hashcode, optionally rehash, and acquire the lock
709         inline void acquire( concurrent_hash_map *base, const hashcode_type h, bool writer = false ) {
710             my_b = base->get_bucket( h );
711             // TODO: actually, notification is unnecessary here, just hiding double-check
712             if (rehash_required(my_b->node_list.load(std::memory_order_acquire))
713                 && bucket::scoped_type::try_acquire( my_b->mutex, /*write=*/true ) )
714             {
715                 if (rehash_required(my_b->node_list.load(std::memory_order_relaxed))) base->rehash_bucket(my_b, h); // recursive rehashing
716             }
717             else bucket::scoped_type::acquire( my_b->mutex, writer );
718             __TBB_ASSERT(!rehash_required(my_b->node_list.load(std::memory_order_relaxed)), nullptr);
719         }
720 
721         // get bucket pointer
722         bucket *operator() () { return my_b; }
723     };
724 
725     // TODO refactor to hash_base
726     void rehash_bucket( bucket *b_new, const hashcode_type hash ) {
727         __TBB_ASSERT( hash > 1, "The lowermost buckets can't be rehashed" );
728         b_new->node_list.store(reinterpret_cast<node_base*>(empty_rehashed_flag), std::memory_order_release); // mark rehashed
729         hashcode_type mask = (1u << tbb::detail::log2(hash)) - 1; // get parent mask from the topmost bit
730         bucket_accessor b_old( this, hash & mask );
731 
732         mask = (mask<<1) | 1; // get full mask for new bucket
733         __TBB_ASSERT( (mask&(mask+1))==0 && (hash & mask) == hash, nullptr );
734     restart:
735         node_base* prev = nullptr;
736         node_base* curr = b_old()->node_list.load(std::memory_order_acquire);
737         while (this->is_valid(curr)) {
738             hashcode_type curr_node_hash = my_hash_compare.hash(static_cast<node*>(curr)->value().first);
739 
740             if ((curr_node_hash & mask) == hash) {
741                 if (!b_old.is_writer()) {
742                     if (!b_old.upgrade_to_writer()) {
743                         goto restart; // node ptr can be invalid due to concurrent erase
744                     }
745                 }
746                 node_base* next = curr->next;
747                 // exclude from b_old
748                 if (prev == nullptr) {
749                     b_old()->node_list.store(curr->next, std::memory_order_relaxed);
750                 } else {
751                     prev->next = curr->next;
752                 }
753                 this->add_to_bucket(b_new, curr);
754                 curr = next;
755             } else {
756                 prev = curr;
757                 curr = curr->next;
758             }
759         }
760     }
761 
762     template <typename U>
763     using hash_compare_is_transparent = dependent_bool<comp_is_transparent<hash_compare_type>, U>;
764 
765 public:
766 
767     class accessor;
768     // Combines data access, locking, and garbage collection.
769     class const_accessor : private node::scoped_type /*which derived from no_copy*/ {
770 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
771         friend class concurrent_hash_map<Key,T,HashCompare,Allocator,MutexType>;
772 #else
773         friend class concurrent_hash_map<Key,T,HashCompare,Allocator>;
774 #endif
775         friend class accessor;
776     public:
777         // Type of value
778         using value_type = const typename concurrent_hash_map::value_type;
779 
780         // True if result is empty.
781         bool empty() const { return !my_node; }
782 
783         // Set to null
784         void release() {
785             if( my_node ) {
786                 node::scoped_type::release();
787                 my_node = 0;
788             }
789         }
790 
791         // Return reference to associated value in hash table.
792         const_reference operator*() const {
793             __TBB_ASSERT( my_node, "attempt to dereference empty accessor" );
794             return my_node->value();
795         }
796 
797         // Return pointer to associated value in hash table.
798         const_pointer operator->() const {
799             return &operator*();
800         }
801 
802         // Create empty result
803         const_accessor() : my_node(nullptr) {}
804 
805         // Destroy result after releasing the underlying reference.
806         ~const_accessor() {
807             my_node = nullptr; // scoped lock's release() is called in its destructor
808         }
809     protected:
810         bool is_writer() { return node::scoped_type::is_writer(); }
811         node *my_node;
812         hashcode_type my_hash;
813     };
814 
815     // Allows write access to elements and combines data access, locking, and garbage collection.
816     class accessor: public const_accessor {
817     public:
818         // Type of value
819         using value_type = typename concurrent_hash_map::value_type;
820 
821         // Return reference to associated value in hash table.
822         reference operator*() const {
823             __TBB_ASSERT( this->my_node, "attempt to dereference empty accessor" );
824             return this->my_node->value();
825         }
826 
827         // Return pointer to associated value in hash table.
828         pointer operator->() const {
829             return &operator*();
830         }
831     };
832 
833     explicit concurrent_hash_map( const hash_compare_type& compare, const allocator_type& a = allocator_type() )
834         : base_type(a)
835         , my_hash_compare(compare)
836     {}
837 
838     concurrent_hash_map() : concurrent_hash_map(hash_compare_type()) {}
839 
840     explicit concurrent_hash_map( const allocator_type& a )
841         : concurrent_hash_map(hash_compare_type(), a)
842     {}
843 
844     // Construct empty table with n preallocated buckets. This number serves also as initial concurrency level.
845     concurrent_hash_map( size_type n, const allocator_type &a = allocator_type() )
846         : concurrent_hash_map(a)
847     {
848         this->reserve(n);
849     }
850 
851     concurrent_hash_map( size_type n, const hash_compare_type& compare, const allocator_type& a = allocator_type() )
852         : concurrent_hash_map(compare, a)
853     {
854         this->reserve(n);
855     }
856 
857     // Copy constructor
858     concurrent_hash_map( const concurrent_hash_map &table )
859         : concurrent_hash_map(node_allocator_traits::select_on_container_copy_construction(table.get_allocator()))
860     {
861         try_call( [&] {
862             internal_copy(table);
863         }).on_exception( [&] {
864             this->clear();
865         });
866     }
867 
868     concurrent_hash_map( const concurrent_hash_map &table, const allocator_type &a)
869         : concurrent_hash_map(a)
870     {
871         try_call( [&] {
872             internal_copy(table);
873         }).on_exception( [&] {
874             this->clear();
875         });
876     }
877 
878     // Move constructor
879     concurrent_hash_map( concurrent_hash_map &&table )
880         : concurrent_hash_map(std::move(table.get_allocator()))
881     {
882         this->internal_move(std::move(table));
883     }
884 
885     // Move constructor
886     concurrent_hash_map( concurrent_hash_map &&table, const allocator_type &a )
887         : concurrent_hash_map(a)
888     {
889         using is_equal_type = typename node_allocator_traits::is_always_equal;
890         internal_move_construct_with_allocator(std::move(table), a, is_equal_type());
891     }
892 
893     // Construction with copying iteration range and given allocator instance
894     template <typename I>
895     concurrent_hash_map( I first, I last, const allocator_type &a = allocator_type() )
896         : concurrent_hash_map(a)
897     {
898         try_call( [&] {
899             internal_copy(first, last, std::distance(first, last));
900         }).on_exception( [&] {
901             this->clear();
902         });
903     }
904 
905     template <typename I>
906     concurrent_hash_map( I first, I last, const hash_compare_type& compare, const allocator_type& a = allocator_type() )
907         : concurrent_hash_map(compare, a)
908     {
909         try_call( [&] {
910             internal_copy(first, last, std::distance(first, last));
911         }).on_exception( [&] {
912             this->clear();
913         });
914     }
915 
916     concurrent_hash_map( std::initializer_list<value_type> il, const hash_compare_type& compare = hash_compare_type(), const allocator_type& a = allocator_type() )
917         : concurrent_hash_map(compare, a)
918     {
919         try_call( [&] {
920             internal_copy(il.begin(), il.end(), il.size());
921         }).on_exception( [&] {
922             this->clear();
923         });
924     }
925 
926     concurrent_hash_map( std::initializer_list<value_type> il, const allocator_type& a )
927         : concurrent_hash_map(il, hash_compare_type(), a) {}
928 
929     // Assignment
930     concurrent_hash_map& operator=( const concurrent_hash_map &table ) {
931         if( this != &table ) {
932             clear();
933             copy_assign_allocators(this->my_allocator, table.my_allocator);
934             internal_copy(table);
935         }
936         return *this;
937     }
938 
939     // Move Assignment
940     concurrent_hash_map& operator=( concurrent_hash_map &&table ) {
941         if( this != &table ) {
942             using pocma_type = typename node_allocator_traits::propagate_on_container_move_assignment;
943             using is_equal_type = typename node_allocator_traits::is_always_equal;
944             move_assign_allocators(this->my_allocator, table.my_allocator);
945             internal_move_assign(std::move(table), tbb::detail::disjunction<is_equal_type, pocma_type>());
946         }
947         return *this;
948     }
949 
950     // Assignment
951     concurrent_hash_map& operator=( std::initializer_list<value_type> il ) {
952         clear();
953         internal_copy(il.begin(), il.end(), il.size());
954         return *this;
955     }
956 
957     // Rehashes and optionally resizes the whole table.
958     /** Useful to optimize performance before or after concurrent operations.
959         Also enables using of find() and count() concurrent methods in serial context. */
960     void rehash(size_type sz = 0) {
961         this->reserve(sz); // TODO: add reduction of number of buckets as well
962         hashcode_type mask = this->my_mask.load(std::memory_order_relaxed);
963         hashcode_type b = (mask+1)>>1; // size or first index of the last segment
964         __TBB_ASSERT((b&(b-1))==0, nullptr); // zero or power of 2
965         bucket *bp = this->get_bucket( b ); // only the last segment should be scanned for rehashing
966         for(; b <= mask; b++, bp++ ) {
967             node_base *n = bp->node_list.load(std::memory_order_relaxed);
968             __TBB_ASSERT( this->is_valid(n) || empty_rehashed(n) || rehash_required(n), "Broken internal structure" );
969             __TBB_ASSERT( *reinterpret_cast<intptr_t*>(&bp->mutex) == 0, "concurrent or unexpectedly terminated operation during rehash() execution" );
970             if (rehash_required(n)) { // rehash bucket, conditional because rehashing of a previous bucket may affect this one
971                 hashcode_type h = b; bucket *b_old = bp;
972                 do {
973                     __TBB_ASSERT( h > 1, "The lowermost buckets can't be rehashed" );
974                     hashcode_type m = ( 1u<<tbb::detail::log2( h ) ) - 1; // get parent mask from the topmost bit
975                     b_old = this->get_bucket( h &= m );
976                 } while( rehash_required(b_old->node_list.load(std::memory_order_relaxed)) );
977                 // now h - is index of the root rehashed bucket b_old
978                 this->mark_rehashed_levels( h ); // mark all non-rehashed children recursively across all segments
979                 node_base* prev = nullptr;
980                 node_base* curr = b_old->node_list.load(std::memory_order_relaxed);
981                 while (this->is_valid(curr)) {
982                     hashcode_type curr_node_hash = my_hash_compare.hash(static_cast<node*>(curr)->value().first);
983 
984                     if ((curr_node_hash & mask) != h) { // should be rehashed
985                         node_base* next = curr->next;
986                         // exclude from b_old
987                         if (prev == nullptr) {
988                             b_old->node_list.store(curr->next, std::memory_order_relaxed);
989                         } else {
990                             prev->next = curr->next;
991                         }
992                         bucket *b_new = this->get_bucket(curr_node_hash & mask);
993                         __TBB_ASSERT(!rehash_required(b_new->node_list.load(std::memory_order_relaxed)), "hash() function changed for key in table or internal error");
994                         this->add_to_bucket(b_new, curr);
995                         curr = next;
996                     } else {
997                         prev = curr;
998                         curr = curr->next;
999                     }
1000                 }
1001             }
1002         }
1003     }
1004 
1005     // Clear table
1006     void clear() {
1007         hashcode_type m = this->my_mask.load(std::memory_order_relaxed);
1008         __TBB_ASSERT((m&(m+1))==0, "data structure is invalid");
1009         this->my_size.store(0, std::memory_order_relaxed);
1010         segment_index_type s = this->segment_index_of( m );
1011         __TBB_ASSERT( s+1 == this->pointers_per_table || !this->my_table[s+1].load(std::memory_order_relaxed), "wrong mask or concurrent grow" );
1012         do {
1013             __TBB_ASSERT(this->is_valid(this->my_table[s].load(std::memory_order_relaxed)), "wrong mask or concurrent grow" );
1014             segment_ptr_type buckets_ptr = this->my_table[s].load(std::memory_order_relaxed);
1015             size_type sz = this->segment_size( s ? s : 1 );
1016             for( segment_index_type i = 0; i < sz; i++ )
1017                 for( node_base *n = buckets_ptr[i].node_list.load(std::memory_order_relaxed);
1018                     this->is_valid(n); n = buckets_ptr[i].node_list.load(std::memory_order_relaxed) )
1019                 {
1020                     buckets_ptr[i].node_list.store(n->next, std::memory_order_relaxed);
1021                     delete_node( n );
1022                 }
1023             this->delete_segment(s);
1024         } while(s-- > 0);
1025         this->my_mask.store(this->embedded_buckets - 1, std::memory_order_relaxed);
1026     }
1027 
1028     // Clear table and destroy it.
1029     ~concurrent_hash_map() { clear(); }
1030 
1031     //------------------------------------------------------------------------
1032     // Parallel algorithm support
1033     //------------------------------------------------------------------------
1034     range_type range( size_type grainsize=1 ) {
1035         return range_type( *this, grainsize );
1036     }
1037     const_range_type range( size_type grainsize=1 ) const {
1038         return const_range_type( *this, grainsize );
1039     }
1040 
1041     //------------------------------------------------------------------------
1042     // STL support - not thread-safe methods
1043     //------------------------------------------------------------------------
1044     iterator begin() { return iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); }
1045     const_iterator begin() const { return const_iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); }
1046     const_iterator cbegin() const { return const_iterator( *this, 0, this->my_embedded_segment, this->my_embedded_segment->node_list.load(std::memory_order_relaxed) ); }
1047     iterator end() { return iterator( *this, 0, 0, 0 ); }
1048     const_iterator end() const { return const_iterator( *this, 0, 0, 0 ); }
1049     const_iterator cend() const { return const_iterator( *this, 0, 0, 0 ); }
1050     std::pair<iterator, iterator> equal_range( const Key& key ) { return internal_equal_range( key, end() ); }
1051     std::pair<const_iterator, const_iterator> equal_range( const Key& key ) const { return internal_equal_range( key, end() ); }
1052 
1053 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1054     template <typename K>
1055     typename std::enable_if<hash_compare_is_transparent<K>::value,
1056                             std::pair<iterator, iterator>>::type equal_range( const K& key ) {
1057         return internal_equal_range(key, end());
1058     }
1059 
1060     template <typename K>
1061     typename std::enable_if<hash_compare_is_transparent<K>::value,
1062                             std::pair<const_iterator, const_iterator>>::type equal_range( const K& key ) const {
1063         return internal_equal_range(key, end());
1064     }
1065 #endif // __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1066 
1067     // Number of items in table.
1068     size_type size() const { return this->my_size.load(std::memory_order_acquire); }
1069 
1070     // True if size()==0.
1071     __TBB_nodiscard bool empty() const { return size() == 0; }
1072 
1073     // Upper bound on size.
1074     size_type max_size() const {
1075         return allocator_traits_type::max_size(base_type::get_allocator());
1076     }
1077 
1078     // Returns the current number of buckets
1079     size_type bucket_count() const { return this->my_mask.load(std::memory_order_relaxed) + 1; }
1080 
1081     // return allocator object
1082     allocator_type get_allocator() const { return base_type::get_allocator(); }
1083 
1084     // swap two instances. Iterators are invalidated
1085     void swap(concurrent_hash_map& table) {
1086         using pocs_type = typename node_allocator_traits::propagate_on_container_swap;
1087         using is_equal_type = typename node_allocator_traits::is_always_equal;
1088         swap_allocators(this->my_allocator, table.my_allocator);
1089         internal_swap(table, tbb::detail::disjunction<pocs_type, is_equal_type>());
1090     }
1091 
1092     //------------------------------------------------------------------------
1093     // concurrent map operations
1094     //------------------------------------------------------------------------
1095 
1096     // Return count of items (0 or 1)
1097     size_type count( const Key &key ) const {
1098         return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, nullptr, /*write=*/false, &do_not_allocate_node);
1099     }
1100 
1101 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1102     template <typename K>
1103     typename std::enable_if<hash_compare_is_transparent<K>::value,
1104                             size_type>::type count( const K& key ) const {
1105         return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, nullptr, /*write=*/false, &do_not_allocate_node);
1106     }
1107 #endif // __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1108 
1109     // Find item and acquire a read lock on the item.
1110     /** Return true if item is found, false otherwise. */
1111     bool find( const_accessor &result, const Key &key ) const {
1112         result.release();
1113         return const_cast<concurrent_hash_map*>(this)->lookup</*insert*/false>(key, nullptr, &result, /*write=*/false, &do_not_allocate_node );
1114     }
1115 
1116     // Find item and acquire a write lock on the item.
1117     /** Return true if item is found, false otherwise. */
1118     bool find( accessor &result, const Key &key ) {
1119         result.release();
1120         return lookup</*insert*/false>(key, nullptr, &result, /*write=*/true, &do_not_allocate_node);
1121     }
1122 
1123 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1124     template <typename K>
1125     typename std::enable_if<hash_compare_is_transparent<K>::value,
1126                             bool>::type find( const_accessor& result, const K& key ) {
1127         result.release();
1128         return lookup</*insert*/false>(key, nullptr, &result, /*write=*/false, &do_not_allocate_node);
1129     }
1130 
1131     template <typename K>
1132     typename std::enable_if<hash_compare_is_transparent<K>::value,
1133                             bool>::type find( accessor& result, const K& key ) {
1134         result.release();
1135         return lookup</*insert*/false>(key, nullptr, &result, /*write=*/true, &do_not_allocate_node);
1136     }
1137 #endif // __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1138 
1139     // Insert item (if not already present) and acquire a read lock on the item.
1140     /** Returns true if item is new. */
1141     bool insert( const_accessor &result, const Key &key ) {
1142         result.release();
1143         return lookup</*insert*/true>(key, nullptr, &result, /*write=*/false, &allocate_node_default_construct<>);
1144     }
1145 
1146     // Insert item (if not already present) and acquire a write lock on the item.
1147     /** Returns true if item is new. */
1148     bool insert( accessor &result, const Key &key ) {
1149         result.release();
1150         return lookup</*insert*/true>(key, nullptr, &result, /*write=*/true, &allocate_node_default_construct<>);
1151     }
1152 
1153 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1154     template <typename K>
1155     typename std::enable_if<hash_compare_is_transparent<K>::value &&
1156                             std::is_constructible<key_type, const K&>::value,
1157                             bool>::type insert( const_accessor& result, const K& key ) {
1158         result.release();
1159         return lookup</*insert*/true>(key, nullptr, &result, /*write=*/true, &allocate_node_default_construct<K>);
1160     }
1161 
1162     template <typename K>
1163     typename std::enable_if<hash_compare_is_transparent<K>::value &&
1164                             std::is_constructible<key_type, const K&>::value,
1165                             bool>::type insert( accessor& result, const K& key ) {
1166         result.release();
1167         return lookup</*insert*/true>(key, nullptr, &result, /*write=*/true, &allocate_node_default_construct<K>);
1168     }
1169 #endif // __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1170 
1171     // Insert item by copying if there is no such key present already and acquire a read lock on the item.
1172     /** Returns true if item is new. */
1173     bool insert( const_accessor &result, const value_type &value ) {
1174         result.release();
1175         return lookup</*insert*/true>(value.first, &value.second, &result, /*write=*/false, &allocate_node_copy_construct);
1176     }
1177 
1178     // Insert item by copying if there is no such key present already and acquire a write lock on the item.
1179     /** Returns true if item is new. */
1180     bool insert( accessor &result, const value_type &value ) {
1181         result.release();
1182         return lookup</*insert*/true>(value.first, &value.second, &result, /*write=*/true, &allocate_node_copy_construct);
1183     }
1184 
1185     // Insert item by copying if there is no such key present already
1186     /** Returns true if item is inserted. */
1187     bool insert( const value_type &value ) {
1188         return lookup</*insert*/true>(value.first, &value.second, nullptr, /*write=*/false, &allocate_node_copy_construct);
1189     }
1190 
1191     // Insert item by copying if there is no such key present already and acquire a read lock on the item.
1192     /** Returns true if item is new. */
1193     bool insert( const_accessor &result, value_type && value ) {
1194         return generic_move_insert(result, std::move(value));
1195     }
1196 
1197     // Insert item by copying if there is no such key present already and acquire a write lock on the item.
1198     /** Returns true if item is new. */
1199     bool insert( accessor &result, value_type && value ) {
1200         return generic_move_insert(result, std::move(value));
1201     }
1202 
1203     // Insert item by copying if there is no such key present already
1204     /** Returns true if item is inserted. */
1205     bool insert( value_type && value ) {
1206         return generic_move_insert(accessor_not_used(), std::move(value));
1207     }
1208 
1209     // Insert item by copying if there is no such key present already and acquire a read lock on the item.
1210     /** Returns true if item is new. */
1211     template <typename... Args>
1212     bool emplace( const_accessor &result, Args&&... args ) {
1213         return generic_emplace(result, std::forward<Args>(args)...);
1214     }
1215 
1216     // Insert item by copying if there is no such key present already and acquire a write lock on the item.
1217     /** Returns true if item is new. */
1218     template <typename... Args>
1219     bool emplace( accessor &result, Args&&... args ) {
1220         return generic_emplace(result, std::forward<Args>(args)...);
1221     }
1222 
1223     // Insert item by copying if there is no such key present already
1224     /** Returns true if item is inserted. */
1225     template <typename... Args>
1226     bool emplace( Args&&... args ) {
1227         return generic_emplace(accessor_not_used(), std::forward<Args>(args)...);
1228     }
1229 
1230     // Insert range [first, last)
1231     template <typename I>
1232     void insert( I first, I last ) {
1233         for ( ; first != last; ++first )
1234             insert( *first );
1235     }
1236 
1237     // Insert initializer list
1238     void insert( std::initializer_list<value_type> il ) {
1239         insert( il.begin(), il.end() );
1240     }
1241 
1242     // Erase item.
1243     /** Return true if item was erased by particularly this call. */
1244     bool erase( const Key &key ) {
1245         return internal_erase(key);
1246     }
1247 
1248 #if __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1249     template <typename K>
1250     typename std::enable_if<hash_compare_is_transparent<K>::value,
1251                             bool>::type erase( const K& key ) {
1252         return internal_erase(key);
1253     }
1254 #endif // __TBB_PREVIEW_CONCURRENT_HASH_MAP_EXTENSIONS
1255 
1256     // Erase item by const_accessor.
1257     /** Return true if item was erased by particularly this call. */
1258     bool erase( const_accessor& item_accessor ) {
1259         return exclude( item_accessor );
1260     }
1261 
1262     // Erase item by accessor.
1263     /** Return true if item was erased by particularly this call. */
1264     bool erase( accessor& item_accessor ) {
1265         return exclude( item_accessor );
1266     }
1267 
1268 protected:
1269     template <typename K, typename AllocateNodeType>
1270     node* allocate_node_helper( const K& key, const T* t, AllocateNodeType allocate_node, std::true_type ) {
1271         return allocate_node(base_type::get_allocator(), key, t);
1272     }
1273 
1274     template <typename K, typename AllocateNodeType>
1275     node* allocate_node_helper( const K&, const T*, AllocateNodeType, std::false_type ) {
1276         __TBB_ASSERT(false, "allocate_node_helper with std::false_type should never been called");
1277         return nullptr;
1278     }
1279 
1280     // Insert or find item and optionally acquire a lock on the item.
1281     template <bool OpInsert, typename K, typename AllocateNodeType>
1282     bool lookup( const K &key, const T *t, const_accessor *result, bool write, AllocateNodeType allocate_node, node *tmp_n  = 0)
1283     {
1284         __TBB_ASSERT( !result || !result->my_node, nullptr );
1285         bool return_value;
1286         hashcode_type const h = my_hash_compare.hash( key );
1287         hashcode_type m = this->my_mask.load(std::memory_order_acquire);
1288         segment_index_type grow_segment = 0;
1289         node *n;
1290         restart:
1291         {//lock scope
1292             __TBB_ASSERT((m&(m+1))==0, "data structure is invalid");
1293             return_value = false;
1294             // get bucket
1295             bucket_accessor b( this, h & m );
1296             // find a node
1297             n = search_bucket( key, b() );
1298             if( OpInsert ) {
1299                 // [opt] insert a key
1300                 if( !n ) {
1301                     if( !tmp_n ) {
1302                         tmp_n = allocate_node_helper(key, t, allocate_node, std::integral_constant<bool, OpInsert>{});
1303                     }
1304                     while ( !b.is_writer() && !b.upgrade_to_writer() ) { // TODO: improved insertion
1305                         // Rerun search list, in case another thread inserted the intem during the upgrade
1306                         n = search_bucket(key, b());
1307                         if (this->is_valid(n)) { // unfortunately, it did
1308                             if (!b.downgrade_to_reader()) {
1309                                 // If the lock was downgraded with reacquiring the mutex
1310                                 // Rerun search list in case another thread removed the item during the downgrade
1311                                 n = search_bucket(key, b());
1312                                 if (!this->is_valid(n)) {
1313                                     // Unfortunately, it did
1314                                     // We need to try upgrading to writer again
1315                                     continue;
1316                                 }
1317                             }
1318                             goto exists;
1319                         }
1320                     }
1321 
1322                     if( this->check_mask_race(h, m) )
1323                         goto restart; // b.release() is done in ~b().
1324                     // insert and set flag to grow the container
1325                     grow_segment = this->insert_new_node( b(), n = tmp_n, m );
1326                     tmp_n = 0;
1327                     return_value = true;
1328                 }
1329             } else { // find or count
1330                 if( !n ) {
1331                     if( this->check_mask_race( h, m ) )
1332                         goto restart; // b.release() is done in ~b(). TODO: replace by continue
1333                     return false;
1334                 }
1335                 return_value = true;
1336             }
1337         exists:
1338             if( !result ) goto check_growth;
1339             // TODO: the following seems as generic/regular operation
1340             // acquire the item
1341             if( !result->try_acquire( n->mutex, write ) ) {
1342                 for( tbb::detail::atomic_backoff backoff(true);; ) {
1343                     if( result->try_acquire( n->mutex, write ) ) break;
1344                     if( !backoff.bounded_pause() ) {
1345                         // the wait takes really long, restart the operation
1346                         b.release();
1347                         __TBB_ASSERT( !OpInsert || !return_value, "Can't acquire new item in locked bucket?" );
1348                         yield();
1349                         m = this->my_mask.load(std::memory_order_acquire);
1350                         goto restart;
1351                     }
1352                 }
1353             }
1354         }//lock scope
1355         result->my_node = n;
1356         result->my_hash = h;
1357     check_growth:
1358         // [opt] grow the container
1359         if( grow_segment ) {
1360             this->enable_segment( grow_segment );
1361         }
1362         if( tmp_n ) // if OpInsert only
1363             delete_node( tmp_n );
1364         return return_value;
1365     }
1366 
1367     struct accessor_not_used { void release(){}};
1368     friend const_accessor* accessor_location( accessor_not_used const& ){ return nullptr;}
1369     friend const_accessor* accessor_location( const_accessor & a )      { return &a;}
1370 
1371     friend bool is_write_access_needed( accessor const& )           { return true;}
1372     friend bool is_write_access_needed( const_accessor const& )     { return false;}
1373     friend bool is_write_access_needed( accessor_not_used const& )  { return false;}
1374 
1375     template <typename Accessor>
1376     bool generic_move_insert( Accessor && result, value_type && value ) {
1377         result.release();
1378         return lookup</*insert*/true>(value.first, &value.second, accessor_location(result), is_write_access_needed(result), &allocate_node_move_construct);
1379     }
1380 
1381     template <typename Accessor, typename... Args>
1382     bool generic_emplace( Accessor && result, Args &&... args ) {
1383         result.release();
1384         node * node_ptr = create_node(base_type::get_allocator(), std::forward<Args>(args)...);
1385         return lookup</*insert*/true>(node_ptr->value().first, nullptr, accessor_location(result), is_write_access_needed(result), &do_not_allocate_node, node_ptr);
1386     }
1387 
1388     // delete item by accessor
1389     bool exclude( const_accessor &item_accessor ) {
1390         __TBB_ASSERT( item_accessor.my_node, nullptr );
1391         node_base *const exclude_node = item_accessor.my_node;
1392         hashcode_type const hash = item_accessor.my_hash;
1393         hashcode_type mask = this->my_mask.load(std::memory_order_acquire);
1394         do {
1395             // get bucket
1396             bucket_accessor b( this, hash & mask, /*writer=*/true );
1397             node_base* prev = nullptr;
1398             node_base* curr = b()->node_list.load(std::memory_order_relaxed);
1399 
1400             while (curr && curr != exclude_node) {
1401                 prev = curr;
1402                 curr = curr->next;
1403             }
1404 
1405             if (curr == nullptr) { // someone else was first
1406                 if (this->check_mask_race(hash, mask))
1407                     continue;
1408                 item_accessor.release();
1409                 return false;
1410             }
1411             __TBB_ASSERT( curr == exclude_node, nullptr );
1412             // remove from container
1413             if (prev == nullptr) {
1414                 b()->node_list.store(curr->next, std::memory_order_relaxed);
1415             } else {
1416                 prev->next = curr->next;
1417             }
1418 
1419             this->my_size--;
1420             break;
1421         } while(true);
1422         if (!item_accessor.is_writer()) { // need to get exclusive lock
1423             item_accessor.upgrade_to_writer(); // return value means nothing here
1424         }
1425 
1426         item_accessor.release();
1427         delete_node(exclude_node); // Only one thread can delete it
1428         return true;
1429     }
1430 
1431     template <typename K>
1432     bool internal_erase( const K& key ) {
1433         node_base *erase_node;
1434         hashcode_type const hash = my_hash_compare.hash(key);
1435         hashcode_type mask = this->my_mask.load(std::memory_order_acquire);
1436     restart:
1437         {//lock scope
1438             // get bucket
1439             bucket_accessor b( this, hash & mask );
1440         search:
1441             node_base* prev = nullptr;
1442             erase_node = b()->node_list.load(std::memory_order_relaxed);
1443             while (this->is_valid(erase_node) && !my_hash_compare.equal(key, static_cast<node*>(erase_node)->value().first ) ) {
1444                 prev = erase_node;
1445                 erase_node = erase_node->next;
1446             }
1447 
1448             if (erase_node == nullptr) { // not found, but mask could be changed
1449                 if (this->check_mask_race(hash, mask))
1450                     goto restart;
1451                 return false;
1452             } else if (!b.is_writer() && !b.upgrade_to_writer()) {
1453                 if (this->check_mask_race(hash, mask)) // contended upgrade, check mask
1454                     goto restart;
1455                 goto search;
1456             }
1457 
1458             // remove from container
1459             if (prev == nullptr) {
1460                 b()->node_list.store(erase_node->next, std::memory_order_relaxed);
1461             } else {
1462                 prev->next = erase_node->next;
1463             }
1464             this->my_size--;
1465         }
1466         {
1467             typename node::scoped_type item_locker( erase_node->mutex, /*write=*/true );
1468         }
1469         // note: there should be no threads pretending to acquire this mutex again, do not try to upgrade const_accessor!
1470         delete_node(erase_node); // Only one thread can delete it due to write lock on the bucket
1471         return true;
1472     }
1473 
1474     // Returns an iterator for an item defined by the key, or for the next item after it (if upper==true)
1475     template <typename K, typename I>
1476     std::pair<I, I> internal_equal_range( const K& key, I end_ ) const {
1477         hashcode_type h = my_hash_compare.hash( key );
1478         hashcode_type m = this->my_mask.load(std::memory_order_relaxed);
1479         __TBB_ASSERT((m&(m+1))==0, "data structure is invalid");
1480         h &= m;
1481         bucket *b = this->get_bucket( h );
1482         while (rehash_required(b->node_list.load(std::memory_order_relaxed))) {
1483             m = ( 1u<<tbb::detail::log2( h ) ) - 1; // get parent mask from the topmost bit
1484             b = this->get_bucket( h &= m );
1485         }
1486         node *n = search_bucket( key, b );
1487         if( !n )
1488             return std::make_pair(end_, end_);
1489         iterator lower(*this, h, b, n), upper(lower);
1490         return std::make_pair(lower, ++upper);
1491     }
1492 
1493     // Copy "source" to *this, where *this must start out empty.
1494     void internal_copy( const concurrent_hash_map& source ) {
1495         hashcode_type mask = source.my_mask.load(std::memory_order_relaxed);
1496         if( this->my_mask.load(std::memory_order_relaxed) == mask ) { // optimized version
1497             this->reserve(source.my_size.load(std::memory_order_relaxed)); // TODO: load_factor?
1498             bucket *dst = 0, *src = 0;
1499             bool rehashing_required = false;
1500             for( hashcode_type k = 0; k <= mask; k++ ) {
1501                 if( k & (k-2) ) ++dst,src++; // not the beginning of a segment
1502                 else { dst = this->get_bucket( k ); src = source.get_bucket( k ); }
1503                 __TBB_ASSERT(!rehash_required(dst->node_list.load(std::memory_order_relaxed)), "Invalid bucket in destination table");
1504                 node *n = static_cast<node*>( src->node_list.load(std::memory_order_relaxed) );
1505                 if (rehash_required(n)) { // source is not rehashed, items are in previous buckets
1506                     rehashing_required = true;
1507                     dst->node_list.store(reinterpret_cast<node_base*>(rehash_req_flag), std::memory_order_relaxed);
1508                 } else for(; n; n = static_cast<node*>( n->next ) ) {
1509                     node* node_ptr = create_node(base_type::get_allocator(), n->value().first, n->value().second);
1510                     this->add_to_bucket( dst, node_ptr);
1511                     this->my_size.fetch_add(1, std::memory_order_relaxed);
1512                 }
1513             }
1514             if( rehashing_required ) rehash();
1515         } else internal_copy(source.begin(), source.end(), source.my_size.load(std::memory_order_relaxed));
1516     }
1517 
1518     template <typename I>
1519     void internal_copy( I first, I last, size_type reserve_size ) {
1520         this->reserve(reserve_size); // TODO: load_factor?
1521         hashcode_type m = this->my_mask.load(std::memory_order_relaxed);
1522         for(; first != last; ++first) {
1523             hashcode_type h = my_hash_compare.hash( (*first).first );
1524             bucket *b = this->get_bucket( h & m );
1525             __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), "Invalid bucket in destination table");
1526             node* node_ptr = create_node(base_type::get_allocator(), (*first).first, (*first).second);
1527             this->add_to_bucket( b, node_ptr );
1528             ++this->my_size; // TODO: replace by non-atomic op
1529         }
1530     }
1531 
1532     void internal_move_construct_with_allocator( concurrent_hash_map&& other, const allocator_type&,
1533                                                 /*is_always_equal=*/std::true_type )
1534     {
1535         this->internal_move(std::move(other));
1536     }
1537 
1538     void internal_move_construct_with_allocator( concurrent_hash_map&& other, const allocator_type& a,
1539                                                 /*is_always_equal=*/std::false_type )
1540     {
1541         if (a == other.get_allocator()){
1542             this->internal_move(std::move(other));
1543         } else {
1544             try_call( [&] {
1545                 internal_copy(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()),
1546                     other.size());
1547             }).on_exception( [&] {
1548                 this->clear();
1549             });
1550         }
1551     }
1552 
1553     void internal_move_assign( concurrent_hash_map&& other,
1554         /*is_always_equal || POCMA = */std::true_type)
1555     {
1556         this->internal_move(std::move(other));
1557     }
1558 
1559     void internal_move_assign(concurrent_hash_map&& other, /*is_always_equal=*/ std::false_type) {
1560         if (this->my_allocator == other.my_allocator) {
1561             this->internal_move(std::move(other));
1562         } else {
1563             //do per element move
1564             internal_copy(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()),
1565                 other.size());
1566         }
1567     }
1568 
1569     void internal_swap(concurrent_hash_map& other, /*is_always_equal || POCS = */ std::true_type) {
1570         this->internal_swap_content(other);
1571     }
1572 
1573     void internal_swap(concurrent_hash_map& other, /*is_always_equal || POCS = */ std::false_type) {
1574         __TBB_ASSERT(this->my_allocator == other.my_allocator, nullptr);
1575         this->internal_swap_content(other);
1576     }
1577 
1578     // Fast find when no concurrent erasure is used. For internal use inside TBB only!
1579     /** Return pointer to item with given key, or nullptr if no such item exists.
1580         Must not be called concurrently with erasure operations. */
1581     const_pointer internal_fast_find( const Key& key ) const {
1582         hashcode_type h = my_hash_compare.hash( key );
1583         hashcode_type m = this->my_mask.load(std::memory_order_acquire);
1584         node *n;
1585     restart:
1586         __TBB_ASSERT((m&(m+1))==0, "data structure is invalid");
1587         bucket *b = this->get_bucket( h & m );
1588         // TODO: actually, notification is unnecessary here, just hiding double-check
1589         if (rehash_required(b->node_list.load(std::memory_order_acquire)))
1590         {
1591             typename bucket::scoped_type lock;
1592             if( lock.try_acquire( b->mutex, /*write=*/true ) ) {
1593                 if (rehash_required(b->node_list.load(std::memory_order_relaxed)))
1594                     const_cast<concurrent_hash_map*>(this)->rehash_bucket( b, h & m ); //recursive rehashing
1595             }
1596             else lock.acquire( b->mutex, /*write=*/false );
1597             __TBB_ASSERT(!rehash_required(b->node_list.load(std::memory_order_relaxed)), nullptr);
1598         }
1599         n = search_bucket( key, b );
1600         if( n )
1601             return n->storage();
1602         else if( this->check_mask_race( h, m ) )
1603             goto restart;
1604         return 0;
1605     }
1606 };
1607 
1608 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1609 template <typename It,
1610           typename HashCompare = d1::tbb_hash_compare<iterator_key_t<It>>,
1611           typename Alloc = tbb_allocator<iterator_alloc_pair_t<It>>,
1612           typename = std::enable_if_t<is_input_iterator_v<It>>,
1613           typename = std::enable_if_t<is_allocator_v<Alloc>>,
1614           typename = std::enable_if_t<!is_allocator_v<HashCompare>>>
1615 concurrent_hash_map( It, It, HashCompare = HashCompare(), Alloc = Alloc() )
1616 -> concurrent_hash_map<iterator_key_t<It>, iterator_mapped_t<It>, HashCompare, Alloc>;
1617 
1618 template <typename It, typename Alloc,
1619           typename = std::enable_if_t<is_input_iterator_v<It>>,
1620           typename = std::enable_if_t<is_allocator_v<Alloc>>>
1621 concurrent_hash_map( It, It, Alloc )
1622 -> concurrent_hash_map<iterator_key_t<It>, iterator_mapped_t<It>, d1::tbb_hash_compare<iterator_key_t<It>>, Alloc>;
1623 
1624 template <typename Key, typename T,
1625           typename HashCompare = d1::tbb_hash_compare<std::remove_const_t<Key>>,
1626           typename Alloc = tbb_allocator<std::pair<const Key, T>>,
1627           typename = std::enable_if_t<is_allocator_v<Alloc>>,
1628           typename = std::enable_if_t<!is_allocator_v<HashCompare>>>
1629 concurrent_hash_map( std::initializer_list<std::pair<Key, T>>, HashCompare = HashCompare(), Alloc = Alloc() )
1630 -> concurrent_hash_map<std::remove_const_t<Key>, T, HashCompare, Alloc>;
1631 
1632 template <typename Key, typename T, typename Alloc,
1633           typename = std::enable_if_t<is_allocator_v<Alloc>>>
1634 concurrent_hash_map( std::initializer_list<std::pair<Key, T>>, Alloc )
1635 -> concurrent_hash_map<std::remove_const_t<Key>, T, d1::tbb_hash_compare<std::remove_const_t<Key>>, Alloc>;
1636 
1637 #endif /* __TBB_CPP17_DEDUCTION_GUIDES_PRESENT */
1638 
1639 template <typename Key, typename T, typename HashCompare, typename A1, typename A2>
1640 inline bool operator==(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b) {
1641     if(a.size() != b.size()) return false;
1642     typename concurrent_hash_map<Key, T, HashCompare, A1>::const_iterator i(a.begin()), i_end(a.end());
1643     typename concurrent_hash_map<Key, T, HashCompare, A2>::const_iterator j, j_end(b.end());
1644     for(; i != i_end; ++i) {
1645         j = b.equal_range(i->first).first;
1646         if( j == j_end || !(i->second == j->second) ) return false;
1647     }
1648     return true;
1649 }
1650 
1651 #if !__TBB_CPP20_COMPARISONS_PRESENT
1652 template <typename Key, typename T, typename HashCompare, typename A1, typename A2>
1653 inline bool operator!=(const concurrent_hash_map<Key, T, HashCompare, A1> &a, const concurrent_hash_map<Key, T, HashCompare, A2> &b)
1654 {    return !(a == b); }
1655 #endif // !__TBB_CPP20_COMPARISONS_PRESENT
1656 
1657 template <typename Key, typename T, typename HashCompare, typename A>
1658 inline void swap(concurrent_hash_map<Key, T, HashCompare, A> &a, concurrent_hash_map<Key, T, HashCompare, A> &b)
1659 {    a.swap( b ); }
1660 
1661 } // namespace d2
1662 } // namespace detail
1663 
1664 inline namespace v1 {
1665     using detail::split;
1666     using detail::d2::concurrent_hash_map;
1667     using detail::d1::tbb_hash_compare;
1668 } // namespace v1
1669 
1670 } // namespace tbb
1671 
1672 #endif /* __TBB_concurrent_hash_map_H */
1673