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, ¤t_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