1 /*
2     Copyright (c) 2005-2022 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 segment_to_delete = self()->nullify_segment(get_table(), seg_index);
188         if (segment_to_delete == segment_allocation_failure_tag) {
189             return;
190         }
191 
192         segment_to_delete += segment_base(seg_index);
193 
194         // Deallocate the segment
195         self()->destroy_segment(segment_to_delete, seg_index);
196     }
197 
198     size_type number_of_segments( segment_table_type table ) const {
199         // Check for an active table, if it is embedded table - return the number of embedded segments
200         // Otherwise - return the maximum number of segments
201         return table == my_embedded_table ? pointers_per_embedded_table : pointers_per_long_table;
202     }
203 
204     size_type capacity() const noexcept {
205         segment_table_type table = get_table();
206         size_type num_segments = number_of_segments(table);
207         for (size_type seg_index = 0; seg_index < num_segments; ++seg_index) {
208             // Check if the pointer is valid (allocated)
209             if (table[seg_index].load(std::memory_order_relaxed) <= segment_allocation_failure_tag) {
210                 return segment_base(seg_index);
211             }
212         }
213         return segment_base(num_segments);
214     }
215 
216     size_type find_last_allocated_segment( segment_table_type table ) const noexcept {
217         size_type end = 0;
218         size_type num_segments = number_of_segments(table);
219         for (size_type seg_index = 0; seg_index < num_segments; ++seg_index) {
220             // Check if the pointer is valid (allocated)
221             if (table[seg_index].load(std::memory_order_relaxed) > segment_allocation_failure_tag) {
222                 end = seg_index + 1;
223             }
224         }
225         return end;
226     }
227 
228     void reserve( size_type n ) {
229         if (n > allocator_traits_type::max_size(my_segment_table_allocator)) {
230             throw_exception(exception_id::reservation_length_error);
231         }
232 
233         size_type size = my_size.load(std::memory_order_relaxed);
234         segment_index_type start_seg_idx = size == 0 ? 0 : segment_index_of(size - 1) + 1;
235         for (segment_index_type seg_idx = start_seg_idx; segment_base(seg_idx) < n; ++seg_idx) {
236                 size_type first_index = segment_base(seg_idx);
237                 internal_subscript<true>(first_index);
238         }
239     }
240 
241     void clear() {
242         clear_segments();
243         clear_table();
244         my_size.store(0, std::memory_order_relaxed);
245         my_first_block.store(0, std::memory_order_relaxed);
246     }
247 
248     void clear_segments() {
249         segment_table_type current_segment_table = get_table();
250         for (size_type i = number_of_segments(current_segment_table); i != 0; --i) {
251             if (current_segment_table[i - 1].load(std::memory_order_relaxed) != nullptr) {
252                 // If the segment was enabled - disable and deallocate it
253                 delete_segment(i - 1);
254             }
255         }
256     }
257 
258     void clear_table() {
259         segment_table_type current_segment_table = get_table();
260         if (current_segment_table != my_embedded_table) {
261             // If the active table is not the embedded one - deallocate the active table
262             for (size_type i = 0; i != pointers_per_long_table; ++i) {
263                 segment_table_allocator_traits::destroy(my_segment_table_allocator, &current_segment_table[i]);
264             }
265 
266             segment_table_allocator_traits::deallocate(my_segment_table_allocator, current_segment_table, pointers_per_long_table);
267             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
268             zero_table(my_embedded_table, pointers_per_embedded_table);
269         }
270     }
271 
272     void extend_table_if_necessary(segment_table_type& table, size_type start_index, size_type end_index) {
273         // extend_segment_table if an active table is an embedded table
274         // and the requested index is not in the embedded table
275         if (table == my_embedded_table && end_index > embedded_table_size) {
276             if (start_index <= embedded_table_size) {
277                 try_call([&] {
278                     table = self()->allocate_long_table(my_embedded_table, start_index);
279                     // It is possible that the table was extended by the thread that allocated first_block.
280                     // In this case it is necessary to re-read the current table.
281 
282                     if (table) {
283                         my_segment_table.store(table, std::memory_order_release);
284                     } else {
285                         table = my_segment_table.load(std::memory_order_acquire);
286                     }
287                 }).on_exception([&] {
288                     my_segment_table_allocation_failed.store(true, std::memory_order_relaxed);
289                 });
290             } else {
291                 atomic_backoff backoff;
292                 do {
293                     if (my_segment_table_allocation_failed.load(std::memory_order_relaxed)) {
294                         throw_exception(exception_id::bad_alloc);
295                     }
296                     backoff.pause();
297                     table = my_segment_table.load(std::memory_order_acquire);
298                 } while (table == my_embedded_table);
299             }
300         }
301     }
302 
303     // Return the segment where index is stored
304     static constexpr segment_index_type segment_index_of( size_type index ) {
305         return size_type(tbb::detail::log2(uintptr_t(index|1)));
306     }
307 
308     // Needed to calculate the offset in segment
309     static constexpr size_type segment_base( size_type index ) {
310         return size_type(1) << index & ~size_type(1);
311     }
312 
313     // Return size of the segment
314     static constexpr size_type segment_size( size_type index ) {
315         return index == 0 ? 2 : size_type(1) << index;
316     }
317 
318 private:
319 
320     derived_type* self() {
321         return static_cast<derived_type*>(this);
322     }
323 
324     struct copy_segment_body_type {
325         void operator()( segment_index_type index, segment_type from, segment_type to ) const {
326             my_instance.self()->copy_segment(index, from, to);
327         }
328         segment_table& my_instance;
329     };
330 
331     struct move_segment_body_type {
332         void operator()( segment_index_type index, segment_type from, segment_type to ) const {
333             my_instance.self()->move_segment(index, from, to);
334         }
335         segment_table& my_instance;
336     };
337 
338     // Transgers all segments from the other table
339     template <typename TransferBody>
340     void internal_transfer( const segment_table& other, TransferBody transfer_segment ) {
341         static_cast<derived_type*>(this)->destroy_elements();
342 
343         assign_first_block_if_necessary(other.my_first_block.load(std::memory_order_relaxed));
344         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
345 
346         segment_table_type other_table = other.get_table();
347         size_type end_segment_size = segment_size(other.find_last_allocated_segment(other_table));
348 
349         // If an exception occurred in other, then the size may be greater than the size of the end segment.
350         size_type other_size = end_segment_size < other.my_size.load(std::memory_order_relaxed) ?
351             other.my_size.load(std::memory_order_relaxed) : end_segment_size;
352         other_size = my_segment_table_allocation_failed ? embedded_table_size : other_size;
353 
354         for (segment_index_type i = 0; segment_base(i) < other_size; ++i) {
355             // If the segment in other table is enabled - transfer it
356             if (other_table[i].load(std::memory_order_relaxed) == segment_allocation_failure_tag)
357             {
358                     my_size = segment_base(i);
359                     break;
360             } else if (other_table[i].load(std::memory_order_relaxed) != nullptr) {
361                 internal_subscript<true>(segment_base(i));
362                 transfer_segment(i, other.get_table()[i].load(std::memory_order_relaxed) + segment_base(i),
363                                 get_table()[i].load(std::memory_order_relaxed) + segment_base(i));
364             }
365         }
366     }
367 
368     // Moves the other segment table
369     // Only equal allocators are allowed
370     void internal_move( segment_table&& other ) {
371         // NOTE: allocators should be equal
372         clear();
373         my_first_block.store(other.my_first_block.load(std::memory_order_relaxed), std::memory_order_relaxed);
374         my_size.store(other.my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
375         // If an active table in other is embedded - restore all of the embedded segments
376         if (other.get_table() == other.my_embedded_table) {
377             for ( size_type i = 0; i != pointers_per_embedded_table; ++i ) {
378                 segment_type other_segment = other.my_embedded_table[i].load(std::memory_order_relaxed);
379                 my_embedded_table[i].store(other_segment, std::memory_order_relaxed);
380                 other.my_embedded_table[i].store(nullptr, std::memory_order_relaxed);
381             }
382             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
383         } else {
384             my_segment_table.store(other.my_segment_table, std::memory_order_relaxed);
385             other.my_segment_table.store(other.my_embedded_table, std::memory_order_relaxed);
386             zero_table(other.my_embedded_table, pointers_per_embedded_table);
387         }
388         other.my_size.store(0, std::memory_order_relaxed);
389     }
390 
391     // Move construct the segment table with the allocator object
392     // if any instances of allocator_type are always equal
393     void internal_move_construct_with_allocator( segment_table&& other, const allocator_type&,
394                                                  /*is_always_equal = */ std::true_type ) {
395         internal_move(std::move(other));
396     }
397 
398     // Move construct the segment table with the allocator object
399     // if any instances of allocator_type are always equal
400     void internal_move_construct_with_allocator( segment_table&& other, const allocator_type& alloc,
401                                                  /*is_always_equal = */ std::false_type ) {
402         if (other.my_segment_table_allocator == alloc) {
403             // If allocators are equal - restore pointers
404             internal_move(std::move(other));
405         } else {
406             // If allocators are not equal - perform per element move with reallocation
407             try_call( [&] {
408                 internal_transfer(other, move_segment_body_type{*this});
409             } ).on_exception( [&] {
410                 clear();
411             });
412         }
413     }
414 
415     // Move assigns the segment table to other is any instances of allocator_type are always equal
416     // or propagate_on_container_move_assignment is true
417     void internal_move_assign( segment_table&& other, /*is_always_equal || POCMA = */ std::true_type ) {
418         internal_move(std::move(other));
419     }
420 
421     // Move assigns the segment table to other is any instances of allocator_type are not always equal
422     // and propagate_on_container_move_assignment is false
423     void internal_move_assign( segment_table&& other, /*is_always_equal || POCMA = */ std::false_type ) {
424         if (my_segment_table_allocator == other.my_segment_table_allocator) {
425             // If allocators are equal - restore pointers
426             internal_move(std::move(other));
427         } else {
428             // If allocators are not equal - perform per element move with reallocation
429             internal_transfer(other, move_segment_body_type{*this});
430         }
431     }
432 
433     // Swaps two segment tables if any instances of allocator_type are always equal
434     // or propagate_on_container_swap is true
435     void internal_swap( segment_table& other, /*is_always_equal || POCS = */ std::true_type ) {
436         internal_swap_fields(other);
437     }
438 
439     // Swaps two segment tables if any instances of allocator_type are not always equal
440     // and propagate_on_container_swap is false
441     // According to the C++ standard, swapping of two containers with unequal allocators
442     // is an undefined behavior scenario
443     void internal_swap( segment_table& other, /*is_always_equal || POCS = */ std::false_type ) {
444         __TBB_ASSERT(my_segment_table_allocator == other.my_segment_table_allocator,
445                      "Swapping with unequal allocators is not allowed");
446         internal_swap_fields(other);
447     }
448 
449     void internal_swap_fields( segment_table& other ) {
450         // If an active table in either *this segment table or other is an embedded one - swaps the embedded tables
451         if (get_table() == my_embedded_table ||
452             other.get_table() == other.my_embedded_table) {
453 
454             for (size_type i = 0; i != pointers_per_embedded_table; ++i) {
455                 segment_type current_segment = my_embedded_table[i].load(std::memory_order_relaxed);
456                 segment_type other_segment = other.my_embedded_table[i].load(std::memory_order_relaxed);
457 
458                 my_embedded_table[i].store(other_segment, std::memory_order_relaxed);
459                 other.my_embedded_table[i].store(current_segment, std::memory_order_relaxed);
460             }
461         }
462 
463         segment_table_type current_segment_table = get_table();
464         segment_table_type other_segment_table = other.get_table();
465 
466         // If an active table is an embedded one -
467         // store an active table in other to the embedded one from other
468         if (current_segment_table == my_embedded_table) {
469             other.my_segment_table.store(other.my_embedded_table, std::memory_order_relaxed);
470         } else {
471             // Otherwise - store it to the active segment table
472             other.my_segment_table.store(current_segment_table, std::memory_order_relaxed);
473         }
474 
475         // If an active table in other segment table is an embedded one -
476         // store an active table in other to the embedded one from *this
477         if (other_segment_table == other.my_embedded_table) {
478             my_segment_table.store(my_embedded_table, std::memory_order_relaxed);
479         } else {
480             // Otherwise - store it to the active segment table in other
481             my_segment_table.store(other_segment_table, std::memory_order_relaxed);
482         }
483         auto first_block = other.my_first_block.load(std::memory_order_relaxed);
484         other.my_first_block.store(my_first_block.load(std::memory_order_relaxed), std::memory_order_relaxed);
485         my_first_block.store(first_block, std::memory_order_relaxed);
486 
487         auto size = other.my_size.load(std::memory_order_relaxed);
488         other.my_size.store(my_size.load(std::memory_order_relaxed), std::memory_order_relaxed);
489         my_size.store(size, std::memory_order_relaxed);
490     }
491 
492 protected:
493     // A flag indicates that an exception was throws during segment allocations
494     const segment_type segment_allocation_failure_tag = reinterpret_cast<segment_type>(1);
495     static constexpr size_type embedded_table_size = segment_size(pointers_per_embedded_table);
496 
497     template <bool allow_out_of_range_access>
498     value_type& internal_subscript( size_type index ) {
499         segment_index_type seg_index = segment_index_of(index);
500         segment_table_type table = my_segment_table.load(std::memory_order_acquire);
501         segment_type segment = nullptr;
502 
503         if (allow_out_of_range_access) {
504             if (derived_type::allow_table_extending) {
505                 extend_table_if_necessary(table, index, index + 1);
506             }
507 
508             segment = table[seg_index].load(std::memory_order_acquire);
509             // If the required segment is disabled - enable it
510             if (segment == nullptr) {
511                 enable_segment(segment, table, seg_index, index);
512             }
513             // Check if an exception was thrown during segment allocation
514             if (segment == segment_allocation_failure_tag) {
515                 throw_exception(exception_id::bad_alloc);
516             }
517         } else {
518             segment = table[seg_index].load(std::memory_order_acquire);
519         }
520         __TBB_ASSERT(segment != nullptr, nullptr);
521 
522         return segment[index];
523     }
524 
525     void assign_first_block_if_necessary(segment_index_type index) {
526         size_type zero = 0;
527         if (this->my_first_block.load(std::memory_order_relaxed) == zero) {
528             this->my_first_block.compare_exchange_strong(zero, index);
529         }
530     }
531 
532     void zero_table( segment_table_type table, size_type count ) {
533         for (size_type i = 0; i != count; ++i) {
534             table[i].store(nullptr, std::memory_order_relaxed);
535         }
536     }
537 
538     segment_table_type get_table() const {
539         return my_segment_table.load(std::memory_order_acquire);
540     }
541 
542     segment_table_allocator_type my_segment_table_allocator;
543     std::atomic<segment_table_type> my_segment_table;
544     atomic_segment my_embedded_table[pointers_per_embedded_table];
545     // Number of segments in first block
546     std::atomic<size_type> my_first_block;
547     // Number of elements in table
548     std::atomic<size_type> my_size;
549     // Flag to indicate failed extend table
550     std::atomic<bool> my_segment_table_allocation_failed;
551 }; // class segment_table
552 
553 } // namespace d1
554 } // namespace detail
555 } // namespace tbb
556 
557 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
558 #pragma warning(pop) // warning 4127 is back
559 #endif
560 
561 #endif // __TBB_detail__segment_table_H
562