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_detail__segment_table_H
18 #define __TBB_detail__segment_table_H
19 
20 #include "_config.h"
21 #include "_allocator_traits.h"
22 #include "_template_helpers.h"
23 #include "_utils.h"
24 #include "_assert.h"
25 #include "_exception.h"
26 #include <atomic>
27 #include <type_traits>
28 #include <memory>
29 #include <cstring>
30 
31 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
32 #pragma warning(push)
33 #pragma warning(disable: 4127) // warning C4127: conditional expression is constant
34 #endif
35 
36 namespace tbb {
37 namespace detail {
38 namespace d1 {
39 
40 template <typename T, typename Allocator, typename DerivedType, std::size_t PointersPerEmbeddedTable>
41 class segment_table {
42 public:
43     using value_type = T;
44     using segment_type = T*;
45     using atomic_segment = std::atomic<segment_type>;
46     using segment_table_type = atomic_segment*;
47 
48     using size_type = std::size_t;
49     using segment_index_type = std::size_t;
50 
51     using allocator_type = Allocator;
52 
53     using allocator_traits_type = tbb::detail::allocator_traits<allocator_type>;
54     using segment_table_allocator_type = typename allocator_traits_type::template rebind_alloc<atomic_segment>;
55 protected:
56     using segment_table_allocator_traits = tbb::detail::allocator_traits<segment_table_allocator_type>;
57     using derived_type = DerivedType;
58 
59     static constexpr size_type pointers_per_embedded_table = PointersPerEmbeddedTable;
60     static constexpr size_type pointers_per_long_table = sizeof(size_type) * 8;
61 public:
62     segment_table( const allocator_type& alloc = allocator_type() )
63         : my_segment_table_allocator(alloc), my_segment_table(my_embedded_table)
64         , my_first_block{}, my_size{}, my_segment_table_allocation_failed{}
65     {
66         zero_table(my_embedded_table, pointers_per_embedded_table);
67     }
68 
69     segment_table( const segment_table& other )
70         : my_segment_table_allocator(segment_table_allocator_traits::
71                                      select_on_container_copy_construction(other.my_segment_table_allocator))
72         , my_segment_table(my_embedded_table), my_first_block{}, my_size{}, my_segment_table_allocation_failed{}
73     {
74         zero_table(my_embedded_table, pointers_per_embedded_table);
75         try_call( [&] {
76             internal_transfer(other, copy_segment_body_type{*this});
77         } ).on_exception( [&] {
78             clear();
79         });
80     }
81 
82     segment_table( const segment_table& other, const allocator_type& alloc )
83         : my_segment_table_allocator(alloc), my_segment_table(my_embedded_table)
84         , my_first_block{}, my_size{}, my_segment_table_allocation_failed{}
85     {
86         zero_table(my_embedded_table, pointers_per_embedded_table);
87         try_call( [&] {
88             internal_transfer(other, copy_segment_body_type{*this});
89         } ).on_exception( [&] {
90             clear();
91         });
92     }
93 
94     segment_table( segment_table&& other )
95         : my_segment_table_allocator(std::move(other.my_segment_table_allocator)), my_segment_table(my_embedded_table)
96         , my_first_block{}, my_size{}, my_segment_table_allocation_failed{}
97     {
98         zero_table(my_embedded_table, pointers_per_embedded_table);
99         internal_move(std::move(other));
100     }
101 
102     segment_table( segment_table&& other, const allocator_type& alloc )
103         : my_segment_table_allocator(alloc), my_segment_table(my_embedded_table), my_first_block{}
104         , my_size{}, my_segment_table_allocation_failed{}
105     {
106         zero_table(my_embedded_table, pointers_per_embedded_table);
107         using is_equal_type = typename segment_table_allocator_traits::is_always_equal;
108         internal_move_construct_with_allocator(std::move(other), alloc, is_equal_type());
109     }
110 
111     ~segment_table() {
112         clear();
113     }
114 
115     segment_table& operator=( const segment_table& other ) {
116         if (this != &other) {
117             copy_assign_allocators(my_segment_table_allocator, other.my_segment_table_allocator);
118             internal_transfer(other, copy_segment_body_type{*this});
119         }
120         return *this;
121     }
122 
123     segment_table& operator=( segment_table&& other )
124         noexcept(derived_type::is_noexcept_assignment)
125     {
126         using pocma_type = typename segment_table_allocator_traits::propagate_on_container_move_assignment;
127         using is_equal_type = typename segment_table_allocator_traits::is_always_equal;
128 
129         if (this != &other) {
130             move_assign_allocators(my_segment_table_allocator, other.my_segment_table_allocator);
131             internal_move_assign(std::move(other), tbb::detail::disjunction<is_equal_type, pocma_type>());
132         }
133         return *this;
134     }
135 
136     void swap( segment_table& other )
137         noexcept(derived_type::is_noexcept_swap)
138     {
139         using is_equal_type = typename segment_table_allocator_traits::is_always_equal;
140         using pocs_type = typename segment_table_allocator_traits::propagate_on_container_swap;
141 
142         if (this != &other) {
143             swap_allocators(my_segment_table_allocator, other.my_segment_table_allocator);
144             internal_swap(other, tbb::detail::disjunction<is_equal_type, pocs_type>());
145         }
146     }
147 
148     segment_type get_segment( segment_index_type index ) const {
149         return get_table()[index] + segment_base(index);
150     }
151 
152     value_type& operator[]( size_type index ) {
153         return internal_subscript<true>(index);
154     }
155 
156     const value_type& operator[]( size_type index ) const {
157         return const_cast<segment_table*>(this)->internal_subscript<true>(index);
158     }
159 
160     const segment_table_allocator_type& get_allocator() const {
161         return my_segment_table_allocator;
162     }
163 
164     segment_table_allocator_type& get_allocator() {
165         return my_segment_table_allocator;
166     }
167 
168     void enable_segment( segment_type& segment, segment_table_type table, segment_index_type seg_index, size_type index ) {
169         // Allocate new segment
170         segment_type new_segment = self()->create_segment(table, seg_index, index);
171         if (new_segment != nullptr) {
172             // Store (new_segment - segment_base) into the segment table to allow access to the table by index via
173             // my_segment_table[segment_index_of(index)][index]
174             segment_type disabled_segment = nullptr;
175             if (!table[seg_index].compare_exchange_strong(disabled_segment, new_segment - segment_base(seg_index))) {
176                 // compare_exchange failed => some other thread has already enabled this segment
177                 // Deallocate the memory
178                 self()->deallocate_segment(new_segment, seg_index);
179             }
180         }
181 
182         segment = table[seg_index].load(std::memory_order_acquire);
183         __TBB_ASSERT(segment != nullptr, "If create_segment returned nullptr, the element should be stored in the table");
184     }
185 
186     void delete_segment( segment_index_type seg_index ) {
187         segment_type disabled_segment = nullptr;
188         // Set the pointer to the segment to NULL in the table
189         segment_type segment_to_delete = get_table()[seg_index].exchange(disabled_segment);
190         if (segment_to_delete == segment_allocation_failure_tag) {
191             return;
192         }
193 
194         segment_to_delete += segment_base(seg_index);
195 
196         // Deallocate the segment
197         self()->destroy_segment(segment_to_delete, seg_index);
198     }
199 
200     size_type number_of_segments( segment_table_type table ) const {
201         // Check for an active table, if it is embedded table - return the number of embedded segments
202         // Otherwise - return the maximum number of segments
203         return table == my_embedded_table ? pointers_per_embedded_table : pointers_per_long_table;
204     }
205 
206     size_type capacity() const noexcept {
207         segment_table_type table = get_table();
208         size_type num_segments = number_of_segments(table);
209         for (size_type seg_index = 0; seg_index < num_segments; ++seg_index) {
210             // Check if the pointer is valid (allocated)
211             if (table[seg_index].load(std::memory_order_relaxed) <= segment_allocation_failure_tag) {
212                 return segment_base(seg_index);
213             }
214         }
215         return segment_base(num_segments);
216     }
217 
218     size_type find_last_allocated_segment( segment_table_type table ) const noexcept {
219         size_type end = 0;
220         size_type num_segments = number_of_segments(table);
221         for (size_type seg_index = 0; seg_index < num_segments; ++seg_index) {
222             // Check if the pointer is valid (allocated)
223             if (table[seg_index].load(std::memory_order_relaxed) > segment_allocation_failure_tag) {
224                 end = seg_index + 1;
225             }
226         }
227         return end;
228     }
229 
230     void reserve( size_type n ) {
231         if (n > allocator_traits_type::max_size(my_segment_table_allocator)) {
232             throw_exception(exception_id::reservation_length_error);
233         }
234 
235         size_type size = my_size.load(std::memory_order_relaxed);
236         segment_index_type start_seg_idx = size == 0 ? 0 : segment_index_of(size - 1) + 1;
237         for (segment_index_type seg_idx = start_seg_idx; segment_base(seg_idx) < n; ++seg_idx) {
238                 size_type first_index = segment_base(seg_idx);
239                 internal_subscript<true>(first_index);
240         }
241     }
242 
243     void clear() {
244         clear_segments();
245         clear_table();
246         my_size.store(0, std::memory_order_relaxed);
247         my_first_block.store(0, std::memory_order_relaxed);
248     }
249 
250     void clear_segments() {
251         segment_table_type current_segment_table = get_table();
252         for (size_type i = number_of_segments(current_segment_table); i != 0; --i) {
253             if (current_segment_table[i - 1].load(std::memory_order_relaxed) != nullptr) {
254                 // If the segment was enabled - disable and deallocate it
255                 delete_segment(i - 1);
256             }
257         }
258     }
259 
260     void clear_table() {
261         segment_table_type current_segment_table = get_table();
262         if (current_segment_table != my_embedded_table) {
263             // If the active table is not the embedded one - deallocate the active table
264             for (size_type i = 0; i != pointers_per_long_table; ++i) {
265                 segment_table_allocator_traits::destroy(my_segment_table_allocator, &current_segment_table[i]);
266             }
267 
268             segment_table_allocator_traits::deallocate(my_segment_table_allocator, current_segment_table, pointers_per_long_table);
269             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
270             zero_table(my_embedded_table, pointers_per_embedded_table);
271         }
272     }
273 
274     void extend_table_if_necessary(segment_table_type& table, size_type start_index, size_type end_index) {
275         // extend_segment_table if an active table is an embedded table
276         // and the requested index is not in the embedded table
277         if (table == my_embedded_table && end_index > embedded_table_size) {
278             if (start_index <= embedded_table_size) {
279                 try_call([&] {
280                     table = self()->allocate_long_table(my_embedded_table, start_index);
281                     // It is possible that the table was extended by the thread that allocated first_block.
282                     // In this case it is necessary to re-read the current table.
283 
284                     if (table) {
285                         my_segment_table.store(table, std::memory_order_release);
286                     } else {
287                         table = my_segment_table.load(std::memory_order_acquire);
288                     }
289                 }).on_exception([&] {
290                     my_segment_table_allocation_failed.store(true, std::memory_order_relaxed);
291                 });
292             } else {
293                 atomic_backoff backoff;
294                 do {
295                     if (my_segment_table_allocation_failed.load(std::memory_order_relaxed)) {
296                         throw_exception(exception_id::bad_alloc);
297                     }
298                     backoff.pause();
299                     table = my_segment_table.load(std::memory_order_acquire);
300                 } while (table == my_embedded_table);
301             }
302         }
303     }
304 
305     // Return the segment where index is stored
306     static constexpr segment_index_type segment_index_of( size_type index ) {
307         return size_type(tbb::detail::log2(uintptr_t(index|1)));
308     }
309 
310     // Needed to calculate the offset in segment
311     static constexpr size_type segment_base( size_type index ) {
312         return size_type(1) << index & ~size_type(1);
313     }
314 
315     // Return size of the segment
316     static constexpr size_type segment_size( size_type index ) {
317         return index == 0 ? 2 : size_type(1) << index;
318     }
319 
320 private:
321 
322     derived_type* self() {
323         return static_cast<derived_type*>(this);
324     }
325 
326     struct copy_segment_body_type {
327         void operator()( segment_index_type index, segment_type from, segment_type to ) const {
328             my_instance.self()->copy_segment(index, from, to);
329         }
330         segment_table& my_instance;
331     };
332 
333     struct move_segment_body_type {
334         void operator()( segment_index_type index, segment_type from, segment_type to ) const {
335             my_instance.self()->move_segment(index, from, to);
336         }
337         segment_table& my_instance;
338     };
339 
340     // Transgers all segments from the other table
341     template <typename TransferBody>
342     void internal_transfer( const segment_table& other, TransferBody transfer_segment ) {
343         static_cast<derived_type*>(this)->destroy_elements();
344 
345         assign_first_block_if_necessary(other.my_first_block.load(std::memory_order_relaxed));
346         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
347 
348         segment_table_type other_table = other.get_table();
349         size_type end_segment_size = segment_size(other.find_last_allocated_segment(other_table));
350 
351         // If an exception occurred in other, then the size may be greater than the size of the end segment.
352         size_type other_size = end_segment_size < other.my_size.load(std::memory_order_relaxed) ?
353             other.my_size.load(std::memory_order_relaxed) : end_segment_size;
354         other_size = my_segment_table_allocation_failed ? embedded_table_size : other_size;
355 
356         for (segment_index_type i = 0; segment_base(i) < other_size; ++i) {
357             // If the segment in other table is enabled - transfer it
358             if (other_table[i].load(std::memory_order_relaxed) == segment_allocation_failure_tag)
359             {
360                     my_size = segment_base(i);
361                     break;
362             } else if (other_table[i].load(std::memory_order_relaxed) != nullptr) {
363                 internal_subscript<true>(segment_base(i));
364                 transfer_segment(i, other.get_table()[i].load(std::memory_order_relaxed) + segment_base(i),
365                                 get_table()[i].load(std::memory_order_relaxed) + segment_base(i));
366             }
367         }
368     }
369 
370     // Moves the other segment table
371     // Only equal allocators are allowed
372     void internal_move( segment_table&& other ) {
373         // NOTE: allocators should be equal
374         clear();
375         my_first_block.store(other.my_first_block.load(std::memory_order_relaxed), std::memory_order_relaxed);
376         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
377         // If an active table in other is embedded - restore all of the embedded segments
378         if (other.get_table() == other.my_embedded_table) {
379             for ( size_type i = 0; i != pointers_per_embedded_table; ++i ) {
380                 segment_type other_segment = other.my_embedded_table[i].load(std::memory_order_relaxed);
381                 my_embedded_table[i].store(other_segment, std::memory_order_relaxed);
382                 other.my_embedded_table[i].store(nullptr, std::memory_order_relaxed);
383             }
384             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
385         } else {
386             my_segment_table.store(other.my_segment_table, std::memory_order_relaxed);
387             other.my_segment_table.store(other.my_embedded_table, std::memory_order_relaxed);
388             zero_table(other.my_embedded_table, pointers_per_embedded_table);
389         }
390         other.my_size.store(0, std::memory_order_relaxed);
391     }
392 
393     // Move construct the segment table with the allocator object
394     // if any instances of allocator_type are always equal
395     void internal_move_construct_with_allocator( segment_table&& other, const allocator_type&,
396                                                  /*is_always_equal = */ std::true_type ) {
397         internal_move(std::move(other));
398     }
399 
400     // Move construct the segment table with the allocator object
401     // if any instances of allocator_type are always equal
402     void internal_move_construct_with_allocator( segment_table&& other, const allocator_type& alloc,
403                                                  /*is_always_equal = */ std::false_type ) {
404         if (other.my_segment_table_allocator == alloc) {
405             // If allocators are equal - restore pointers
406             internal_move(std::move(other));
407         } else {
408             // If allocators are not equal - perform per element move with reallocation
409             try_call( [&] {
410                 internal_transfer(other, move_segment_body_type{*this});
411             } ).on_exception( [&] {
412                 clear();
413             });
414         }
415     }
416 
417     // Move assigns the segment table to other is any instances of allocator_type are always equal
418     // or propagate_on_container_move_assignment is true
419     void internal_move_assign( segment_table&& other, /*is_always_equal || POCMA = */ std::true_type ) {
420         internal_move(std::move(other));
421     }
422 
423     // Move assigns the segment table to other is any instances of allocator_type are not always equal
424     // and propagate_on_container_move_assignment is false
425     void internal_move_assign( segment_table&& other, /*is_always_equal || POCMA = */ std::false_type ) {
426         if (my_segment_table_allocator == other.my_segment_table_allocator) {
427             // If allocators are equal - restore pointers
428             internal_move(std::move(other));
429         } else {
430             // If allocators are not equal - perform per element move with reallocation
431             internal_transfer(other, move_segment_body_type{*this});
432         }
433     }
434 
435     // Swaps two segment tables if any instances of allocator_type are always equal
436     // or propagate_on_container_swap is true
437     void internal_swap( segment_table& other, /*is_always_equal || POCS = */ std::true_type ) {
438         internal_swap_fields(other);
439     }
440 
441     // Swaps two segment tables if any instances of allocator_type are not always equal
442     // and propagate_on_container_swap is false
443     // According to the C++ standard, swapping of two containers with unequal allocators
444     // is an undefined behavior scenario
445     void internal_swap( segment_table& other, /*is_always_equal || POCS = */ std::false_type ) {
446         __TBB_ASSERT(my_segment_table_allocator == other.my_segment_table_allocator,
447                      "Swapping with unequal allocators is not allowed");
448         internal_swap_fields(other);
449     }
450 
451     void internal_swap_fields( segment_table& other ) {
452         // If an active table in either *this segment table or other is an embedded one - swaps the embedded tables
453         if (get_table() == my_embedded_table ||
454             other.get_table() == other.my_embedded_table) {
455 
456             for (size_type i = 0; i != pointers_per_embedded_table; ++i) {
457                 segment_type current_segment = my_embedded_table[i].load(std::memory_order_relaxed);
458                 segment_type other_segment = other.my_embedded_table[i].load(std::memory_order_relaxed);
459 
460                 my_embedded_table[i].store(other_segment, std::memory_order_relaxed);
461                 other.my_embedded_table[i].store(current_segment, std::memory_order_relaxed);
462             }
463         }
464 
465         segment_table_type current_segment_table = get_table();
466         segment_table_type other_segment_table = other.get_table();
467 
468         // If an active table is an embedded one -
469         // store an active table in other to the embedded one from other
470         if (current_segment_table == my_embedded_table) {
471             other.my_segment_table.store(other.my_embedded_table, std::memory_order_relaxed);
472         } else {
473             // Otherwise - store it to the active segment table
474             other.my_segment_table.store(current_segment_table, std::memory_order_relaxed);
475         }
476 
477         // If an active table in other segment table is an embedded one -
478         // store an active table in other to the embedded one from *this
479         if (other_segment_table == other.my_embedded_table) {
480             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
481         } else {
482             // Otherwise - store it to the active segment table in other
483             my_segment_table.store(other_segment_table, std::memory_order_relaxed);
484         }
485         auto first_block = other.my_first_block.load(std::memory_order_relaxed);
486         other.my_first_block.store(my_first_block.load(std::memory_order_relaxed), std::memory_order_relaxed);
487         my_first_block.store(first_block, std::memory_order_relaxed);
488 
489         auto size = other.my_size.load(std::memory_order_relaxed);
490         other.my_size.store(my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
491         my_size.store(size, std::memory_order_relaxed);
492     }
493 
494 protected:
495     // A flag indicates that an exception was throws during segment allocations
496     const segment_type segment_allocation_failure_tag = reinterpret_cast<segment_type>(1);
497     static constexpr size_type embedded_table_size = segment_size(pointers_per_embedded_table);
498 
499     template <bool allow_out_of_range_access>
500     value_type& internal_subscript( size_type index ) {
501         segment_index_type seg_index = segment_index_of(index);
502         segment_table_type table = my_segment_table.load(std::memory_order_acquire);
503         segment_type segment = nullptr;
504 
505         if (allow_out_of_range_access) {
506             if (derived_type::allow_table_extending) {
507                 extend_table_if_necessary(table, index, index + 1);
508             }
509 
510             segment = table[seg_index].load(std::memory_order_acquire);
511             // If the required segment is disabled - enable it
512             if (segment == nullptr) {
513                 enable_segment(segment, table, seg_index, index);
514             }
515             // Check if an exception was thrown during segment allocation
516             if (segment == segment_allocation_failure_tag) {
517                 throw_exception(exception_id::bad_alloc);
518             }
519         } else {
520             segment = table[seg_index].load(std::memory_order_acquire);
521         }
522         __TBB_ASSERT(segment != nullptr, nullptr);
523 
524         return segment[index];
525     }
526 
527     void assign_first_block_if_necessary(segment_index_type index) {
528         size_type zero = 0;
529         if (this->my_first_block.load(std::memory_order_relaxed) == zero) {
530             this->my_first_block.compare_exchange_strong(zero, index);
531         }
532     }
533 
534     void zero_table( segment_table_type table, size_type count ) {
535         for (size_type i = 0; i != count; ++i) {
536             table[i].store(nullptr, std::memory_order_relaxed);
537         }
538     }
539 
540     segment_table_type get_table() const {
541         return my_segment_table.load(std::memory_order_acquire);
542     }
543 
544     segment_table_allocator_type my_segment_table_allocator;
545     std::atomic<segment_table_type> my_segment_table;
546     atomic_segment my_embedded_table[pointers_per_embedded_table];
547     // Number of segments in first block
548     std::atomic<size_type> my_first_block;
549     // Number of elements in table
550     std::atomic<size_type> my_size;
551     // Flag to indicate failed extend table
552     std::atomic<bool> my_segment_table_allocation_failed;
553 }; // class segment_table
554 
555 } // namespace d1
556 } // namespace detail
557 } // namespace tbb
558 
559 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
560 #pragma warning(pop) // warning 4127 is back
561 #endif
562 
563 #endif // __TBB_detail__segment_table_H
564