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