1 // <memory_resource> implementation -*- C++ -*- 2 3 // Copyright (C) 2018-2022 Free Software Foundation, Inc. 4 // 5 // This file is part of the GNU ISO C++ Library. This library is free 6 // software; you can redistribute it and/or modify it under the 7 // terms of the GNU General Public License as published by the 8 // Free Software Foundation; either version 3, or (at your option) 9 // any later version. 10 11 // This library is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 16 // Under Section 7 of GPL version 3, you are granted additional 17 // permissions described in the GCC Runtime Library Exception, version 18 // 3.1, as published by the Free Software Foundation. 19 20 // You should have received a copy of the GNU General Public License and 21 // a copy of the GCC Runtime Library Exception along with this program; 22 // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23 // <http://www.gnu.org/licenses/>. 24 25 #include <memory_resource> 26 #include <algorithm> // lower_bound, rotate 27 #include <atomic> 28 #include <bit> // has_single_bit, bit_ceil, bit_width 29 #include <new> 30 #if ATOMIC_POINTER_LOCK_FREE != 2 31 # include <bits/std_mutex.h> // std::mutex, std::lock_guard 32 # include <bits/move.h> // std::__exchange 33 #endif 34 35 #if __has_cpp_attribute(clang::require_constant_initialization) 36 # define __constinit [[clang::require_constant_initialization]] 37 #endif 38 39 namespace std _GLIBCXX_VISIBILITY(default) 40 { 41 _GLIBCXX_BEGIN_NAMESPACE_VERSION 42 namespace pmr 43 { 44 // This was defined inline in 9.1 and 9.2 so code compiled by those 45 // versions will not use this symbol. 46 memory_resource::~memory_resource() = default; 47 48 namespace 49 { 50 class newdel_res_t final : public memory_resource 51 { 52 void* 53 do_allocate(size_t __bytes, size_t __alignment) override 54 { return ::operator new(__bytes, std::align_val_t(__alignment)); } 55 56 void 57 do_deallocate(void* __p, size_t __bytes, size_t __alignment) noexcept 58 override 59 { ::operator delete(__p, __bytes, std::align_val_t(__alignment)); } 60 61 bool 62 do_is_equal(const memory_resource& __other) const noexcept override 63 { return &__other == this; } 64 }; 65 66 class null_res_t final : public memory_resource 67 { 68 void* 69 do_allocate(size_t, size_t) override 70 { std::__throw_bad_alloc(); } 71 72 void 73 do_deallocate(void*, size_t, size_t) noexcept override 74 { } 75 76 bool 77 do_is_equal(const memory_resource& __other) const noexcept override 78 { return &__other == this; } 79 }; 80 81 template<typename T> 82 struct constant_init 83 { 84 union { 85 unsigned char unused; 86 T obj; 87 }; 88 constexpr constant_init() : obj() { } 89 90 template<typename U> 91 explicit constexpr constant_init(U arg) : obj(arg) { } 92 93 ~constant_init() { /* do nothing, union member is not destroyed */ } 94 }; 95 96 __constinit constant_init<newdel_res_t> newdel_res{}; 97 __constinit constant_init<null_res_t> null_res{}; 98 #if ATOMIC_POINTER_LOCK_FREE == 2 99 using atomic_mem_res = atomic<memory_resource*>; 100 # define _GLIBCXX_ATOMIC_MEM_RES_CAN_BE_CONSTANT_INITIALIZED 101 #elif defined(_GLIBCXX_HAS_GTHREADS) 102 // Can't use pointer-width atomics, define a type using a mutex instead: 103 struct atomic_mem_res 104 { 105 # ifdef __GTHREAD_MUTEX_INIT 106 # define _GLIBCXX_ATOMIC_MEM_RES_CAN_BE_CONSTANT_INITIALIZED 107 // std::mutex has constexpr constructor 108 constexpr 109 # endif 110 atomic_mem_res(memory_resource* r) : val(r) { } 111 112 mutex mx; 113 memory_resource* val; 114 115 memory_resource* load() 116 { 117 lock_guard<mutex> lock(mx); 118 return val; 119 } 120 121 memory_resource* exchange(memory_resource* r) 122 { 123 lock_guard<mutex> lock(mx); 124 return std::__exchange(val, r); 125 } 126 }; 127 #else 128 # define _GLIBCXX_ATOMIC_MEM_RES_CAN_BE_CONSTANT_INITIALIZED 129 // Single-threaded, no need for synchronization 130 struct atomic_mem_res 131 { 132 constexpr 133 atomic_mem_res(memory_resource* r) : val(r) { } 134 135 memory_resource* val; 136 137 memory_resource* load() const 138 { 139 return val; 140 } 141 142 memory_resource* exchange(memory_resource* r) 143 { 144 return std::__exchange(val, r); 145 } 146 }; 147 #endif // ATOMIC_POINTER_LOCK_FREE == 2 148 149 #ifdef _GLIBCXX_ATOMIC_MEM_RES_CAN_BE_CONSTANT_INITIALIZED 150 __constinit constant_init<atomic_mem_res> default_res{&newdel_res.obj}; 151 #else 152 # include "default_resource.h" 153 #endif 154 } // namespace 155 156 memory_resource* 157 new_delete_resource() noexcept 158 { return &newdel_res.obj; } 159 160 memory_resource* 161 null_memory_resource() noexcept 162 { return &null_res.obj; } 163 164 memory_resource* 165 set_default_resource(memory_resource* r) noexcept 166 { 167 if (r == nullptr) 168 r = new_delete_resource(); 169 return default_res.obj.exchange(r); 170 } 171 172 memory_resource* 173 get_default_resource() noexcept 174 { return default_res.obj.load(); } 175 176 // Member functions for std::pmr::monotonic_buffer_resource 177 178 // This was defined inline in 9.1 and 9.2 so code compiled by those 179 // versions will not use this symbol. 180 monotonic_buffer_resource::~monotonic_buffer_resource() { release(); } 181 182 namespace { 183 184 // aligned_size<N> stores the size and alignment of a memory allocation. 185 // The size must be a multiple of N, leaving the low log2(N) bits free 186 // to store the base-2 logarithm of the alignment. 187 // For example, allocate(1024, 32) is stored as 1024 + log2(32) = 1029. 188 template<unsigned N> 189 struct aligned_size 190 { 191 // N must be a power of two 192 static_assert( std::__popcount(N) == 1 ); 193 194 static constexpr size_t _S_align_mask = N - 1; 195 static constexpr size_t _S_size_mask = ~_S_align_mask; 196 197 constexpr 198 aligned_size(size_t sz, size_t align) noexcept 199 : value(sz | (std::__bit_width(align) - 1u)) 200 { 201 __glibcxx_assert(size() == sz); // sz must be a multiple of N 202 } 203 204 constexpr size_t 205 size() const noexcept 206 { return value & _S_size_mask; } 207 208 constexpr size_t 209 alignment() const noexcept 210 { return size_t(1) << (value & _S_align_mask); } 211 212 size_t value; // size | log2(alignment) 213 }; 214 215 // Round n up to a multiple of alignment, which must be a power of two. 216 constexpr size_t aligned_ceil(size_t n, size_t alignment) 217 { 218 return (n + alignment - 1) & ~(alignment - 1); 219 } 220 221 } // namespace 222 223 // Memory allocated by the upstream resource is managed in a linked list 224 // of _Chunk objects. A _Chunk object recording the size and alignment of 225 // the allocated block and a pointer to the previous chunk is placed 226 // at end of the block. 227 class monotonic_buffer_resource::_Chunk 228 { 229 public: 230 // Return the address and size of a block of memory allocated from __r, 231 // of at least __size bytes and aligned to __align. 232 // Add a new _Chunk to the front of the linked list at __head. 233 static pair<void*, size_t> 234 allocate(memory_resource* __r, size_t __size, size_t __align, 235 _Chunk*& __head) 236 { 237 const size_t __orig_size = __size; 238 239 // Add space for the _Chunk object and round up to 64 bytes. 240 __size = aligned_ceil(__size + sizeof(_Chunk), 64); 241 242 // Check for unsigned wraparound 243 if (__size < __orig_size) [[unlikely]] 244 { 245 // monotonic_buffer_resource::do_allocate is not allowed to throw. 246 // If the required size is too large for size_t then ask the 247 // upstream resource for an impossibly large size and alignment. 248 __size = -1; 249 __align = ~(size_t(-1) >> 1); 250 } 251 252 void* __p = __r->allocate(__size, __align); 253 254 // Add a chunk defined by (__p, __size, __align) to linked list __head. 255 // We know the end of the buffer is suitably-aligned for a _Chunk 256 // because the caller ensured __align is at least alignof(max_align_t). 257 void* const __back = (char*)__p + __size - sizeof(_Chunk); 258 __head = ::new(__back) _Chunk(__size, __align, __head); 259 return { __p, __size - sizeof(_Chunk) }; 260 } 261 262 // Return every chunk in linked list __head to resource __r. 263 static void 264 release(_Chunk*& __head, memory_resource* __r) noexcept 265 { 266 _Chunk* __next = __head; 267 __head = nullptr; 268 while (__next) 269 { 270 _Chunk* __ch = __next; 271 __next = __ch->_M_next; 272 size_t __size = __ch->_M_size.size(); 273 size_t __align = __ch->_M_size.alignment(); 274 void* __start = (char*)(__ch + 1) - __size; 275 __r->deallocate(__start, __size, __align); 276 } 277 } 278 279 private: 280 _Chunk(size_t __size, size_t __align, _Chunk* __next) noexcept 281 : _M_size(__size, __align), _M_next(__next) 282 { } 283 284 aligned_size<64> _M_size; 285 _Chunk* _M_next; 286 }; 287 288 void 289 monotonic_buffer_resource::_M_new_buffer(size_t bytes, size_t alignment) 290 { 291 const size_t n = std::max(bytes, _M_next_bufsiz); 292 const size_t m = aligned_ceil(alignment, alignof(std::max_align_t)); 293 auto [p, size] = _Chunk::allocate(_M_upstream, n, m, _M_head); 294 _M_current_buf = p; 295 _M_avail = size; 296 _M_next_bufsiz *= _S_growth_factor; 297 } 298 299 void 300 monotonic_buffer_resource::_M_release_buffers() noexcept 301 { 302 _Chunk::release(_M_head, _M_upstream); 303 } 304 305 // Helper types for synchronized_pool_resource & unsynchronized_pool_resource 306 307 namespace { 308 309 // Simple bitset with runtime size. 310 // Tracks which blocks in a pool chunk are used/unused. 311 struct bitset 312 { 313 using word = uint64_t; 314 using size_type // unsigned integer type with no more than 32 bits 315 = conditional_t<numeric_limits<size_t>::digits <= 32, size_t, uint32_t>; 316 317 static constexpr unsigned bits_per_word = numeric_limits<word>::digits; 318 319 // The bitset does not own p 320 bitset(void* p, size_type num_blocks) 321 : _M_words(static_cast<word*>(p)), _M_size(num_blocks), 322 _M_next_word(0) 323 { 324 const size_type last_word = num_blocks / bits_per_word; 325 __builtin_memset(_M_words, 0, last_word * sizeof(*_M_words)); 326 // Set bits beyond _M_size, so they are not treated as free blocks: 327 if (const size_type extra_bits = num_blocks % bits_per_word) 328 _M_words[last_word] = word(-1) << extra_bits; 329 __glibcxx_assert( empty() ); 330 __glibcxx_assert( free() == num_blocks ); 331 } 332 333 bitset() = default; 334 ~bitset() = default; 335 336 // Number of blocks 337 size_type size() const noexcept { return _M_size; } 338 339 // Number of free blocks (unset bits) 340 size_type free() const noexcept 341 { 342 size_type n = 0; 343 for (size_type i = _M_next_word; i < nwords(); ++i) 344 n += (bits_per_word - std::__popcount(_M_words[i])); 345 return n; 346 } 347 348 // True if there are no free blocks (all bits are set) 349 bool full() const noexcept 350 { 351 if (_M_next_word >= nwords()) 352 return true; 353 // For a bitset with size() > (max_blocks_per_chunk() - 64) we will 354 // have nwords() == (max_word_index() + 1) and so _M_next_word will 355 // never be equal to nwords(). 356 // In that case, check if the last word is full: 357 if (_M_next_word == max_word_index()) 358 return _M_words[_M_next_word] == word(-1); 359 return false; 360 } 361 362 // True if size() != 0 and all blocks are free (no bits are set). 363 bool empty() const noexcept 364 { 365 if (nwords() == 0) 366 return false; 367 if (_M_next_word != 0) 368 return false; 369 for (size_type i = 0; i < nwords() - 1; ++i) 370 if (_M_words[i] != 0) 371 return false; 372 word last = _M_words[nwords() - 1]; 373 if (const size_type extra_bits = size() % bits_per_word) 374 last <<= (bits_per_word - extra_bits); 375 return last == 0; 376 } 377 378 void reset() noexcept 379 { 380 _M_words = nullptr; 381 _M_size = _M_next_word = 0; 382 } 383 384 bool operator[](size_type n) const noexcept 385 { 386 __glibcxx_assert( n < _M_size ); 387 const size_type wd = n / bits_per_word; 388 const word bit = word(1) << (n % bits_per_word); 389 return _M_words[wd] & bit; 390 } 391 392 size_type get_first_unset() noexcept 393 { 394 const size_type wd = _M_next_word; 395 if (wd < nwords()) 396 { 397 const size_type n = std::__countr_one(_M_words[wd]); 398 if (n < bits_per_word) 399 { 400 const word bit = word(1) << n; 401 _M_words[wd] |= bit; 402 update_next_word(); 403 return (wd * bits_per_word) + n; 404 } 405 } 406 return size_type(-1); 407 } 408 409 void set(size_type n) noexcept 410 { 411 __glibcxx_assert( n < _M_size ); 412 const size_type wd = n / bits_per_word; 413 const word bit = word(1) << (n % bits_per_word); 414 _M_words[wd] |= bit; 415 if (wd == _M_next_word) 416 update_next_word(); 417 } 418 419 void clear(size_type n) noexcept 420 { 421 __glibcxx_assert( n < _M_size ); 422 const size_type wd = n / bits_per_word; 423 const word bit = word(1) << (n % bits_per_word); 424 _M_words[wd] &= ~bit; 425 if (wd < _M_next_word) 426 _M_next_word = wd; 427 } 428 429 // Update _M_next_word to refer to the next word with an unset bit. 430 // The size of the _M_next_word bit-field means it cannot represent 431 // the maximum possible nwords() value. To avoid wraparound to zero 432 // this function saturates _M_next_word at max_word_index(). 433 void update_next_word() noexcept 434 { 435 size_type next = _M_next_word; 436 while (_M_words[next] == word(-1) && ++next < nwords()) 437 { } 438 _M_next_word = std::min(next, max_word_index()); 439 } 440 441 void swap(bitset& b) noexcept 442 { 443 std::swap(_M_words, b._M_words); 444 size_type tmp = _M_size; 445 _M_size = b._M_size; 446 b._M_size = tmp; 447 tmp = _M_next_word; 448 _M_next_word = b._M_next_word; 449 b._M_next_word = tmp; 450 } 451 452 size_type nwords() const noexcept 453 { return (_M_size + bits_per_word - 1) / bits_per_word; } 454 455 // Maximum value that can be stored in bitset::_M_size member (approx 500k) 456 static constexpr size_type max_blocks_per_chunk() noexcept 457 { return (size_type(1) << _S_size_digits) - 1; } 458 459 // Maximum value that can be stored in bitset::_M_next_word member (8191). 460 static constexpr size_type max_word_index() noexcept 461 { return (max_blocks_per_chunk() + bits_per_word - 1) / bits_per_word; } 462 463 word* data() const noexcept { return _M_words; } 464 465 private: 466 static constexpr unsigned _S_size_digits 467 = (numeric_limits<size_type>::digits 468 + std::__bit_width(bits_per_word) - 1) / 2; 469 470 word* _M_words = nullptr; 471 // Number of blocks represented by the bitset: 472 size_type _M_size : _S_size_digits; 473 // Index of the first word with unset bits: 474 size_type _M_next_word : numeric_limits<size_type>::digits - _S_size_digits; 475 }; 476 477 // A "chunk" belonging to a pool. 478 // A chunk contains many blocks of the same size. 479 // Derived from bitset to reuse its tail-padding. 480 struct chunk : bitset 481 { 482 chunk() = default; 483 484 // p points to the start of a chunk of size bytes in length. 485 // The chunk has space for n blocks, followed by a bitset of size n 486 // that begins at address words. 487 // This object does not own p or words, the caller will free it. 488 chunk(void* p, uint32_t bytes, void* words, size_t n) 489 : bitset(words, n), 490 _M_bytes(bytes), 491 _M_p(static_cast<std::byte*>(p)) 492 { __glibcxx_assert(bytes <= chunk::max_bytes_per_chunk()); } 493 494 chunk(chunk&& c) noexcept 495 : bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p) 496 { 497 c._M_bytes = 0; 498 c._M_p = nullptr; 499 c.reset(); 500 } 501 502 chunk& operator=(chunk&& c) noexcept 503 { 504 swap(c); 505 return *this; 506 } 507 508 // Allocated size of chunk: 509 bitset::size_type _M_bytes = 0; 510 // Start of allocated chunk: 511 std::byte* _M_p = nullptr; 512 513 // True if there are free blocks in this chunk 514 using bitset::full; 515 // Number of blocks in this chunk 516 using bitset::size; 517 518 static constexpr uint32_t max_bytes_per_chunk() noexcept 519 { return numeric_limits<decltype(_M_bytes)>::max(); } 520 521 // Determine if block with address p and size block_size 522 // is contained within this chunk. 523 bool owns(void* p, size_t block_size) 524 { 525 std::less_equal<uintptr_t> less_equal; 526 return less_equal(reinterpret_cast<uintptr_t>(_M_p), 527 reinterpret_cast<uintptr_t>(p)) 528 && less_equal(reinterpret_cast<uintptr_t>(p) + block_size, 529 reinterpret_cast<uintptr_t>(bitset::data())); 530 } 531 532 // Allocate next available block of block_size bytes from this chunk. 533 void* reserve(size_t block_size) noexcept 534 { 535 const size_type n = get_first_unset(); 536 if (n == size_type(-1)) 537 return nullptr; 538 return _M_p + (n * block_size); 539 } 540 541 // Deallocate a single block of block_size bytes 542 void release(void* vp, size_t block_size) 543 { 544 __glibcxx_assert( owns(vp, block_size) ); 545 const size_t offset = static_cast<std::byte*>(vp) - _M_p; 546 // Pointer is correctly aligned for a block in this chunk: 547 __glibcxx_assert( (offset % block_size) == 0 ); 548 // Block has been allocated: 549 __glibcxx_assert( (*this)[offset / block_size] == true ); 550 bitset::clear(offset / block_size); 551 } 552 553 // Deallocate a single block if it belongs to this chunk. 554 bool try_release(void* p, size_t block_size) 555 { 556 if (!owns(p, block_size)) 557 return false; 558 release(p, block_size); 559 return true; 560 } 561 562 void swap(chunk& c) noexcept 563 { 564 std::swap(_M_bytes, c._M_bytes); 565 std::swap(_M_p, c._M_p); 566 bitset::swap(c); 567 } 568 569 bool operator<(const chunk& c) const noexcept 570 { return std::less<const void*>{}(_M_p, c._M_p); } 571 572 friend void swap(chunk& l, chunk& r) { l.swap(r); } 573 574 friend bool operator<(const void* p, const chunk& c) noexcept 575 { return std::less<const void*>{}(p, c._M_p); } 576 }; 577 578 // For 64-bit pointers this is the size of three pointers i.e. 24 bytes. 579 // For 32-bit and 20-bit pointers it's four pointers (16 bytes). 580 // For 16-bit pointers it's five pointers (10 bytes). 581 // TODO pad 64-bit to 4*sizeof(void*) to avoid splitting across cache lines? 582 static_assert(sizeof(chunk) 583 == 2 * sizeof(bitset::size_type) + 2 * sizeof(void*)); 584 585 // An oversized allocation that doesn't fit in a pool. 586 struct big_block 587 { 588 // The minimum size of a big block. 589 // All big_block allocations will be a multiple of this value. 590 // Use bit_ceil to get a power of two even for e.g. 20-bit size_t. 591 static constexpr size_t min = __bit_ceil(numeric_limits<size_t>::digits); 592 593 constexpr 594 big_block(size_t bytes, size_t alignment) 595 : _M_size(alloc_size(bytes), alignment) 596 { 597 // Check for unsigned wraparound 598 if (size() < bytes) [[unlikely]] 599 { 600 // (sync|unsync)_pool_resource::do_allocate is not allowed to throw. 601 // If the required size is too large for size_t then ask the 602 // upstream resource for an impossibly large size and alignment. 603 _M_size.value = -1; 604 } 605 } 606 607 void* pointer = nullptr; 608 aligned_size<min> _M_size; 609 610 constexpr size_t size() const noexcept 611 { 612 if (_M_size.value == size_t(-1)) [[unlikely]] 613 return size_t(-1); 614 return _M_size.size(); 615 } 616 617 size_t align() const noexcept 618 { return _M_size.alignment(); } 619 620 // Calculate size to be allocated instead of requested number of bytes. 621 // The requested value will be rounded up to a multiple of big_block::min, 622 // so the low bits are all zero and can be used to hold the alignment. 623 static constexpr size_t alloc_size(size_t bytes) noexcept 624 { return aligned_ceil(bytes, min); } 625 626 friend bool operator<(void* p, const big_block& b) noexcept 627 { return less<void*>{}(p, b.pointer); } 628 629 friend bool operator<(const big_block& b, void* p) noexcept 630 { return less<void*>{}(b.pointer, p); } 631 }; 632 633 static_assert(sizeof(big_block) == (2 * sizeof(void*))); 634 635 } // namespace 636 637 // A pool that serves blocks of a particular size. 638 // Each pool manages a number of chunks. 639 // When a pool is full it is replenished by allocating another chunk. 640 struct __pool_resource::_Pool 641 { 642 // Smallest supported block size 643 static constexpr unsigned _S_min_block 644 = std::max(sizeof(void*), alignof(bitset::word)); 645 646 _Pool(size_t __block_size, size_t __blocks_per_chunk) 647 : _M_chunks(), 648 _M_block_sz(__block_size), 649 _M_blocks_per_chunk(__blocks_per_chunk) 650 { } 651 652 // Must call release(r) before destruction! 653 ~_Pool() { __glibcxx_assert(_M_chunks.empty()); } 654 655 _Pool(_Pool&&) noexcept = default; 656 _Pool& operator=(_Pool&&) noexcept = default; 657 658 // Size of blocks in this pool 659 size_t block_size() const noexcept 660 { return _M_block_sz; } 661 662 // Allocate a block if the pool is not full, otherwise return null. 663 void* try_allocate() noexcept 664 { 665 const size_t blocksz = block_size(); 666 if (!_M_chunks.empty()) 667 { 668 auto& last = _M_chunks.back(); 669 if (void* p = last.reserve(blocksz)) 670 return p; 671 // TODO last is full, so move another chunk to the back instead? 672 for (auto it = _M_chunks.begin(); it != &last; ++it) 673 if (void* p = it->reserve(blocksz)) 674 return p; 675 } 676 return nullptr; 677 } 678 679 // Allocate a block from the pool, replenishing from upstream if needed. 680 void* allocate(memory_resource* r, const pool_options& opts) 681 { 682 if (void* p = try_allocate()) 683 return p; 684 replenish(r, opts); 685 return _M_chunks.back().reserve(block_size()); 686 } 687 688 // Return a block to the pool. 689 bool deallocate(memory_resource*, void* p) 690 { 691 const size_t blocksz = block_size(); 692 if (__builtin_expect(!_M_chunks.empty(), true)) 693 { 694 auto& last = _M_chunks.back(); 695 if (last.try_release(p, blocksz)) 696 return true; 697 auto it = std::upper_bound(_M_chunks.begin(), &last, p); 698 if (it != _M_chunks.begin()) 699 { 700 it--; 701 if (it->try_release(p, blocksz)) 702 // If chunk is empty could return to upstream, but we don't 703 // currently do that. Pools only increase in size. 704 return true; 705 } 706 } 707 return false; 708 } 709 710 void replenish(memory_resource* __r, const pool_options& __opts) 711 { 712 using word = chunk::word; 713 const size_t __blocks = _M_blocks_per_chunk; 714 const auto __bits = chunk::bits_per_word; 715 const size_t __words = (__blocks + __bits - 1) / __bits; 716 const size_t __block_size = block_size(); 717 size_t __bytes = __blocks * __block_size + __words * sizeof(word); 718 size_t __alignment = std::__bit_ceil(__block_size); 719 void* __p = __r->allocate(__bytes, __alignment); 720 __try 721 { 722 size_t __n = __blocks * __block_size; 723 void* __pwords = static_cast<char*>(__p) + __n; 724 _M_chunks.insert(chunk(__p, __bytes, __pwords, __blocks), __r); 725 } 726 __catch (...) 727 { 728 __r->deallocate(__p, __bytes, __alignment); 729 } 730 if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk) 731 { 732 const size_t max_blocks 733 = (chunk::max_bytes_per_chunk() - sizeof(word)) 734 / (__block_size + 0.125); 735 _M_blocks_per_chunk = std::min({ 736 max_blocks, 737 __opts.max_blocks_per_chunk, 738 size_t(_M_blocks_per_chunk * 2) 739 }); 740 } 741 } 742 743 void release(memory_resource* __r) 744 { 745 const size_t __alignment = std::__bit_ceil(block_size()); 746 for (auto& __c : _M_chunks) 747 if (__c._M_p) 748 __r->deallocate(__c._M_p, __c._M_bytes, __alignment); 749 _M_chunks.clear(__r); 750 } 751 752 // A "resourceless vector" instead of pmr::vector, to save space. 753 // All resize operations need to be passed a memory resource, which 754 // obviously needs to be the same one every time. 755 // Chunks are kept sorted by address of their first block, except for 756 // the most recently-allocated Chunk which is at the end of the vector. 757 struct vector 758 { 759 using value_type = chunk; 760 using size_type = unsigned; 761 using iterator = value_type*; 762 763 // A vector owns its data pointer but not memory held by its elements. 764 chunk* data = nullptr; 765 size_type size = 0; 766 size_type capacity = 0; 767 768 vector() = default; 769 770 vector(size_type __n, memory_resource* __r) 771 : data(polymorphic_allocator<value_type>(__r).allocate(__n)), 772 capacity(__n) 773 { } 774 775 // Must call clear(r) before destruction! 776 ~vector() { __glibcxx_assert(data == nullptr); } 777 778 vector(vector&& __rval) noexcept 779 : data(__rval.data), size(__rval.size), capacity(__rval.capacity) 780 { 781 __rval.data = nullptr; 782 __rval.capacity = __rval.size = 0; 783 } 784 785 vector& operator=(vector&& __rval) noexcept 786 { 787 __glibcxx_assert(data == nullptr); 788 data = __rval.data; 789 size = __rval.size; 790 capacity = __rval.capacity; 791 __rval.data = nullptr; 792 __rval.capacity = __rval.size = 0; 793 return *this; 794 } 795 796 // void resize(size_type __n, memory_resource* __r); 797 // void reserve(size_type __n, memory_resource* __r); 798 799 void clear(memory_resource* __r) 800 { 801 if (!data) 802 return; 803 // Chunks must be individually freed before clearing the vector. 804 std::destroy(begin(), end()); 805 polymorphic_allocator<value_type>(__r).deallocate(data, capacity); 806 data = nullptr; 807 capacity = size = 0; 808 } 809 810 // Sort existing elements then insert new one at the end. 811 iterator insert(chunk&& c, memory_resource* r) 812 { 813 if (size < capacity) 814 { 815 if (size > 1) 816 { 817 auto mid = end() - 1; 818 std::rotate(std::lower_bound(begin(), mid, *mid), mid, end()); 819 } 820 } 821 else if (size > 0) 822 { 823 polymorphic_allocator<value_type> __alloc(r); 824 auto __mid = std::lower_bound(begin(), end() - 1, back()); 825 auto __p = __alloc.allocate(capacity * 1.5); 826 // move [begin,__mid) to new storage 827 auto __p2 = std::move(begin(), __mid, __p); 828 // move end-1 to new storage 829 *__p2 = std::move(back()); 830 // move [__mid,end-1) to new storage 831 std::move(__mid, end() - 1, ++__p2); 832 std::destroy(begin(), end()); 833 __alloc.deallocate(data, capacity); 834 data = __p; 835 capacity *= 1.5; 836 } 837 else 838 { 839 polymorphic_allocator<value_type> __alloc(r); 840 data = __alloc.allocate(capacity = 8); 841 } 842 auto back = ::new (data + size) chunk(std::move(c)); 843 __glibcxx_assert(std::is_sorted(begin(), back)); 844 ++size; 845 return back; 846 } 847 848 iterator begin() const { return data; } 849 iterator end() const { return data + size; } 850 851 bool empty() const noexcept { return size == 0; } 852 853 value_type& back() { return data[size - 1]; } 854 }; 855 856 vector _M_chunks; 857 unsigned _M_block_sz; // size of blocks allocated from this pool 858 unsigned _M_blocks_per_chunk; // number of blocks to allocate next 859 }; 860 861 // An oversized allocation that doesn't fit in a pool. 862 struct __pool_resource::_BigBlock : big_block 863 { 864 using big_block::big_block; 865 }; 866 867 namespace { 868 869 constexpr size_t pool_sizes[] = { 870 8, 16, 24, 871 32, 48, 872 64, 80, 96, 112, 873 128, 192, 874 256, 320, 384, 448, 875 512, 768, 876 #if __SIZE_WIDTH__ > 16 877 // Use bigger pools if size_t has at least 20 bits. 878 1024, 1536, 879 2048, 3072, 880 #if __INT_WIDTH__ >= 32 881 // Use even bigger pools if int has at least 32 bits. 882 1<<12, 1<<13, 1<<14, 883 1<<15, 1<<16, 1<<17, 884 1<<20, 1<<21, 1<<22 // 4MB should be enough for anybody 885 #endif 886 #endif 887 }; 888 889 pool_options 890 munge_options(pool_options opts) 891 { 892 // The values in the returned struct may differ from those supplied 893 // to the pool resource constructor in that values of zero will be 894 // replaced with implementation-defined defaults, and sizes may be 895 // rounded to unspecified granularity. 896 897 // max_blocks_per_chunk sets the absolute maximum for the pool resource. 898 // Each pool might have a smaller maximum, because pools for very large 899 // objects might impose smaller limit. 900 if (opts.max_blocks_per_chunk == 0) 901 { 902 // Pick a default that depends on the number of bits in size_t. 903 opts.max_blocks_per_chunk = __SIZE_WIDTH__ << 8; 904 } 905 else 906 { 907 // Round to preferred granularity. 908 if (opts.max_blocks_per_chunk < size_t(-4)) 909 { 910 // round up 911 opts.max_blocks_per_chunk 912 = aligned_ceil(opts.max_blocks_per_chunk, 4); 913 } 914 else 915 { 916 // round down 917 opts.max_blocks_per_chunk &= ~size_t(3); 918 } 919 } 920 921 if (opts.max_blocks_per_chunk > chunk::max_blocks_per_chunk()) 922 { 923 opts.max_blocks_per_chunk = chunk::max_blocks_per_chunk(); 924 } 925 926 // largest_required_pool_block specifies the largest block size that will 927 // be allocated from a pool. Larger allocations will come directly from 928 // the upstream resource and so will not be pooled. 929 if (opts.largest_required_pool_block == 0) 930 { 931 // Pick a sensible default that depends on the number of bits in size_t 932 // (pools with larger block sizes must be explicitly requested by 933 // using a non-zero value for largest_required_pool_block). 934 opts.largest_required_pool_block = __SIZE_WIDTH__ << 6; 935 } 936 else 937 { 938 // Round to preferred granularity 939 static_assert(std::__has_single_bit(pool_sizes[0])); 940 opts.largest_required_pool_block 941 = aligned_ceil(opts.largest_required_pool_block, pool_sizes[0]); 942 } 943 944 if (opts.largest_required_pool_block < big_block::min) 945 { 946 opts.largest_required_pool_block = big_block::min; 947 } 948 else if (opts.largest_required_pool_block > std::end(pool_sizes)[-1]) 949 { 950 // Setting _M_opts to the largest pool allows users to query it: 951 opts.largest_required_pool_block = std::end(pool_sizes)[-1]; 952 } 953 return opts; 954 } 955 956 inline int 957 pool_index(size_t block_size, int npools) 958 { 959 auto p = std::lower_bound(pool_sizes, pool_sizes + npools, block_size); 960 int n = p - pool_sizes; 961 if (n != npools) 962 return n; 963 return -1; 964 } 965 966 inline int 967 select_num_pools(const pool_options& opts) 968 { 969 auto p = std::lower_bound(std::begin(pool_sizes), std::end(pool_sizes), 970 opts.largest_required_pool_block); 971 const int n = p - std::begin(pool_sizes); 972 if (p == std::end(pool_sizes)) 973 return n; 974 return n + 1; 975 } 976 977 #ifdef _GLIBCXX_HAS_GTHREADS 978 using shared_lock = std::shared_lock<shared_mutex>; 979 using exclusive_lock = lock_guard<shared_mutex>; 980 #endif 981 982 } // namespace 983 984 __pool_resource:: 985 __pool_resource(const pool_options& opts, memory_resource* upstream) 986 : _M_opts(munge_options(opts)), _M_unpooled(upstream), 987 _M_npools(select_num_pools(_M_opts)) 988 { } 989 990 __pool_resource::~__pool_resource() { release(); } 991 992 void 993 __pool_resource::release() noexcept 994 { 995 memory_resource* res = resource(); 996 // deallocate oversize allocations 997 for (auto& b : _M_unpooled) 998 res->deallocate(b.pointer, b.size(), b.align()); 999 pmr::vector<_BigBlock>{res}.swap(_M_unpooled); 1000 } 1001 1002 void* 1003 __pool_resource::allocate(size_t bytes, size_t alignment) 1004 { 1005 auto& b = _M_unpooled.emplace_back(bytes, alignment); 1006 __try { 1007 // N.B. need to allocate b.size(), which might be larger than bytes. 1008 // Also use b.align() instead of alignment parameter, which will be 1009 // an impossibly large value if (bytes+bookkeeping) > SIZE_MAX. 1010 void* p = resource()->allocate(b.size(), b.align()); 1011 b.pointer = p; 1012 if (_M_unpooled.size() > 1) 1013 { 1014 const auto mid = _M_unpooled.end() - 1; 1015 // move to right position in vector 1016 std::rotate(std::lower_bound(_M_unpooled.begin(), mid, p), 1017 mid, _M_unpooled.end()); 1018 } 1019 return p; 1020 } __catch(...) { 1021 _M_unpooled.pop_back(); 1022 __throw_exception_again; 1023 } 1024 } 1025 1026 void 1027 __pool_resource::deallocate(void* p, size_t bytes [[maybe_unused]], 1028 size_t alignment [[maybe_unused]]) 1029 { 1030 const auto it 1031 = std::lower_bound(_M_unpooled.begin(), _M_unpooled.end(), p); 1032 __glibcxx_assert(it != _M_unpooled.end() && it->pointer == p); 1033 if (it != _M_unpooled.end() && it->pointer == p) // [[likely]] 1034 { 1035 const auto b = *it; 1036 __glibcxx_assert(b.size() == b.alloc_size(bytes)); 1037 __glibcxx_assert(b.align() == alignment); 1038 _M_unpooled.erase(it); 1039 // N.B. need to deallocate b.size(), which might be larger than bytes. 1040 resource()->deallocate(p, b.size(), b.align()); 1041 } 1042 } 1043 1044 // Create array of pools, allocated from upstream resource. 1045 auto 1046 __pool_resource::_M_alloc_pools() 1047 -> _Pool* 1048 { 1049 polymorphic_allocator<_Pool> alloc{resource()}; 1050 _Pool* p = alloc.allocate(_M_npools); 1051 for (int i = 0; i < _M_npools; ++i) 1052 { 1053 // For last pool use largest_required_pool_block 1054 const size_t block_size = (i+1 == _M_npools) 1055 ? _M_opts.largest_required_pool_block 1056 : pool_sizes[i]; 1057 1058 // Decide on initial number of blocks per chunk. 1059 // At least 16 blocks per chunk seems reasonable, 1060 // more for smaller blocks: 1061 size_t blocks_per_chunk = 1024 / block_size; 1062 blocks_per_chunk = std::max(size_t(16), blocks_per_chunk); 1063 // But don't exceed the requested max_blocks_per_chunk: 1064 blocks_per_chunk 1065 = std::min(blocks_per_chunk, _M_opts.max_blocks_per_chunk); 1066 // Allow space for bitset to track which blocks are used/unused: 1067 blocks_per_chunk *= 1 - 1.0 / (__CHAR_BIT__ * block_size); 1068 // Construct a _Pool for the given block size and initial chunk size: 1069 alloc.construct(p + i, block_size, blocks_per_chunk); 1070 } 1071 return p; 1072 } 1073 1074 #ifdef _GLIBCXX_HAS_GTHREADS 1075 // synchronized_pool_resource members. 1076 1077 /* Notes on implementation and thread safety: 1078 * 1079 * Each synchronized_pool_resource manages an linked list of N+1 _TPools 1080 * objects, where N is the number of threads using the pool resource. 1081 * Each _TPools object has its own set of pools, with their own chunks. 1082 * The first element of the list, _M_tpools[0], can be used by any thread. 1083 * The rest of the list contains a _TPools object for each thread, 1084 * accessed via the thread-specific key _M_key (and referred to for 1085 * exposition as _M_tpools[_M_key]). 1086 * The first element, _M_tpools[0], contains "orphaned chunks" which were 1087 * allocated by a thread which has since exited, and so there is no 1088 * _M_tpools[_M_key] for that thread. Orphaned chunks are never reused, 1089 * they're only held in _M_tpools[0] so they can be deallocated. 1090 * A thread can access its own thread-specific set of pools via _M_key 1091 * while holding a shared lock on _M_mx. Accessing _M_impl._M_unpooled 1092 * or _M_tpools[0] or any other thread's _M_tpools[_M_key] requires an 1093 * exclusive lock. 1094 * The upstream_resource() pointer can be obtained without a lock, but 1095 * any dereference of that pointer requires an exclusive lock. 1096 * The _M_impl._M_opts and _M_impl._M_npools members are immutable, 1097 * and can safely be accessed concurrently. 1098 * 1099 * In a single-threaded program (i.e. __gthread_active_p() == false) 1100 * the pool resource only needs one set of pools and never has orphaned 1101 * chunks, so just uses _M_tpools[0] directly, and _M_tpools->next is null. 1102 */ 1103 1104 extern "C" { 1105 static void destroy_TPools(void*); 1106 } 1107 1108 struct synchronized_pool_resource::_TPools 1109 { 1110 // Exclusive lock must be held in the thread where this constructor runs. 1111 explicit 1112 _TPools(synchronized_pool_resource& owner, exclusive_lock&) 1113 : owner(owner), pools(owner._M_impl._M_alloc_pools()) 1114 { 1115 // __builtin_printf("%p constructing\n", this); 1116 __glibcxx_assert(pools); 1117 } 1118 1119 // Exclusive lock must be held in the thread where this destructor runs. 1120 ~_TPools() 1121 { 1122 __glibcxx_assert(pools); 1123 if (pools) 1124 { 1125 memory_resource* r = owner.upstream_resource(); 1126 for (int i = 0; i < owner._M_impl._M_npools; ++i) 1127 pools[i].release(r); 1128 std::destroy_n(pools, owner._M_impl._M_npools); 1129 polymorphic_allocator<__pool_resource::_Pool> a(r); 1130 a.deallocate(pools, owner._M_impl._M_npools); 1131 } 1132 if (prev) 1133 prev->next = next; 1134 if (next) 1135 next->prev = prev; 1136 } 1137 1138 // Exclusive lock must be held in the thread where this function runs. 1139 void move_nonempty_chunks() 1140 { 1141 __glibcxx_assert(pools); 1142 __glibcxx_assert(__gthread_active_p()); 1143 if (!pools) 1144 return; 1145 memory_resource* const r = owner.upstream_resource(); 1146 auto* const shared = owner._M_tpools->pools; 1147 // move all non-empty chunks to the shared _TPools 1148 for (int i = 0; i < owner._M_impl._M_npools; ++i) 1149 for (auto& c : pools[i]._M_chunks) 1150 if (!c.empty()) 1151 shared[i]._M_chunks.insert(std::move(c), r); 1152 } 1153 1154 synchronized_pool_resource& owner; 1155 __pool_resource::_Pool* pools = nullptr; 1156 _TPools* prev = nullptr; 1157 _TPools* next = nullptr; 1158 1159 static void destroy(_TPools* p) 1160 { 1161 exclusive_lock l(p->owner._M_mx); 1162 // __glibcxx_assert(p != p->owner._M_tpools); 1163 p->move_nonempty_chunks(); 1164 polymorphic_allocator<_TPools> a(p->owner.upstream_resource()); 1165 p->~_TPools(); 1166 a.deallocate(p, 1); 1167 } 1168 }; 1169 1170 // Called when a thread exits 1171 extern "C" { 1172 static void destroy_TPools(void* p) 1173 { 1174 using _TPools = synchronized_pool_resource::_TPools; 1175 _TPools::destroy(static_cast<_TPools*>(p)); 1176 } 1177 } 1178 1179 // Constructor 1180 synchronized_pool_resource:: 1181 synchronized_pool_resource(const pool_options& opts, 1182 memory_resource* upstream) 1183 : _M_impl(opts, upstream) 1184 { 1185 if (__gthread_active_p()) 1186 if (int err = __gthread_key_create(&_M_key, destroy_TPools)) 1187 __throw_system_error(err); 1188 exclusive_lock l(_M_mx); 1189 _M_tpools = _M_alloc_shared_tpools(l); 1190 } 1191 1192 // Destructor 1193 synchronized_pool_resource::~synchronized_pool_resource() 1194 { 1195 release(); 1196 if (__gthread_active_p()) 1197 __gthread_key_delete(_M_key); // does not run destroy_TPools 1198 } 1199 1200 void 1201 synchronized_pool_resource::release() 1202 { 1203 exclusive_lock l(_M_mx); 1204 if (_M_tpools) 1205 { 1206 if (__gthread_active_p()) 1207 { 1208 __gthread_key_delete(_M_key); // does not run destroy_TPools 1209 __gthread_key_create(&_M_key, destroy_TPools); 1210 } 1211 polymorphic_allocator<_TPools> a(upstream_resource()); 1212 // destroy+deallocate each _TPools 1213 do 1214 { 1215 _TPools* p = _M_tpools; 1216 _M_tpools = _M_tpools->next; 1217 p->~_TPools(); 1218 a.deallocate(p, 1); 1219 } 1220 while (_M_tpools); 1221 } 1222 // release unpooled memory 1223 _M_impl.release(); 1224 } 1225 1226 // Caller must hold shared or exclusive lock to ensure the pointer 1227 // isn't invalidated before it can be used. 1228 auto 1229 synchronized_pool_resource::_M_thread_specific_pools() noexcept 1230 { 1231 __pool_resource::_Pool* pools = nullptr; 1232 __glibcxx_assert(__gthread_active_p()); 1233 if (auto tp = static_cast<_TPools*>(__gthread_getspecific(_M_key))) 1234 { 1235 pools = tp->pools; 1236 // __glibcxx_assert(tp->pools); 1237 } 1238 return pools; 1239 } 1240 1241 // Override for memory_resource::do_allocate 1242 void* 1243 synchronized_pool_resource:: 1244 do_allocate(size_t bytes, size_t alignment) 1245 { 1246 const auto block_size = std::max(bytes, alignment); 1247 const pool_options opts = _M_impl._M_opts; 1248 if (block_size <= opts.largest_required_pool_block) 1249 { 1250 const ptrdiff_t index = pool_index(block_size, _M_impl._M_npools); 1251 if (__gthread_active_p()) 1252 { 1253 // Try to allocate from the thread-specific pool. 1254 shared_lock l(_M_mx); 1255 if (auto pools = _M_thread_specific_pools()) // [[likely]] 1256 { 1257 // Need exclusive lock to replenish so use try_allocate: 1258 if (void* p = pools[index].try_allocate()) 1259 return p; 1260 // Need to take exclusive lock and replenish pool. 1261 } 1262 // Need to allocate or replenish thread-specific pools using 1263 // upstream resource, so need to hold exclusive lock. 1264 } 1265 else // single-threaded 1266 { 1267 if (!_M_tpools) // [[unlikely]] 1268 { 1269 exclusive_lock dummy(_M_mx); 1270 _M_tpools = _M_alloc_shared_tpools(dummy); 1271 } 1272 return _M_tpools->pools[index].allocate(upstream_resource(), opts); 1273 } 1274 1275 // N.B. Another thread could call release() now lock is not held. 1276 exclusive_lock excl(_M_mx); 1277 if (!_M_tpools) // [[unlikely]] 1278 _M_tpools = _M_alloc_shared_tpools(excl); 1279 auto pools = _M_thread_specific_pools(); 1280 if (!pools) 1281 pools = _M_alloc_tpools(excl)->pools; 1282 return pools[index].allocate(upstream_resource(), opts); 1283 } 1284 exclusive_lock l(_M_mx); 1285 return _M_impl.allocate(bytes, alignment); // unpooled allocation 1286 } 1287 1288 // Override for memory_resource::do_deallocate 1289 void 1290 synchronized_pool_resource:: 1291 do_deallocate(void* p, size_t bytes, size_t alignment) 1292 { 1293 size_t block_size = std::max(bytes, alignment); 1294 if (block_size <= _M_impl._M_opts.largest_required_pool_block) 1295 { 1296 const ptrdiff_t index = pool_index(block_size, _M_impl._M_npools); 1297 __glibcxx_assert(index != -1); 1298 if (__gthread_active_p()) 1299 { 1300 shared_lock l(_M_mx); 1301 if (auto pools = _M_thread_specific_pools()) 1302 { 1303 // No need to lock here, no other thread is accessing this pool. 1304 if (pools[index].deallocate(upstream_resource(), p)) 1305 return; 1306 } 1307 // Block might have come from a different thread's pool, 1308 // take exclusive lock and check every pool. 1309 } 1310 else // single-threaded 1311 { 1312 __glibcxx_assert(_M_tpools != nullptr); 1313 if (_M_tpools) // [[likely]] 1314 _M_tpools->pools[index].deallocate(upstream_resource(), p); 1315 return; 1316 } 1317 1318 // TODO store {p, bytes, alignment} somewhere and defer returning 1319 // the block to the correct thread-specific pool until we next 1320 // take the exclusive lock. 1321 1322 exclusive_lock excl(_M_mx); 1323 auto my_pools = _M_thread_specific_pools(); 1324 for (_TPools* t = _M_tpools; t != nullptr; t = t->next) 1325 { 1326 if (t->pools != my_pools) 1327 if (t->pools) // [[likely]] 1328 { 1329 if (t->pools[index].deallocate(upstream_resource(), p)) 1330 return; 1331 } 1332 } 1333 // Not necessarily an error to reach here, release() could have been 1334 // called on another thread between releasing the shared lock and 1335 // acquiring the exclusive lock. 1336 return; 1337 } 1338 exclusive_lock l(_M_mx); 1339 _M_impl.deallocate(p, bytes, alignment); 1340 } 1341 1342 // Allocate a thread-specific _TPools object and add it to the linked list. 1343 auto 1344 synchronized_pool_resource::_M_alloc_tpools(exclusive_lock& l) 1345 -> _TPools* 1346 { 1347 __glibcxx_assert(_M_tpools != nullptr); 1348 __glibcxx_assert(__gthread_active_p()); 1349 // dump_list(_M_tpools); 1350 polymorphic_allocator<_TPools> a(upstream_resource()); 1351 _TPools* p = a.allocate(1); 1352 bool constructed = false; 1353 __try 1354 { 1355 a.construct(p, *this, l); 1356 constructed = true; 1357 // __glibcxx_assert(__gthread_getspecific(_M_key) == nullptr); 1358 if (int err = __gthread_setspecific(_M_key, p)) 1359 __throw_system_error(err); 1360 } 1361 __catch(...) 1362 { 1363 if (constructed) 1364 a.destroy(p); 1365 a.deallocate(p, 1); 1366 __throw_exception_again; 1367 } 1368 p->prev = _M_tpools; 1369 p->next = _M_tpools->next; 1370 _M_tpools->next = p; 1371 if (p->next) 1372 p->next->prev = p; 1373 return p; 1374 } 1375 1376 // Allocate the shared _TPools object, _M_tpools[0] 1377 auto 1378 synchronized_pool_resource::_M_alloc_shared_tpools(exclusive_lock& l) 1379 -> _TPools* 1380 { 1381 __glibcxx_assert(_M_tpools == nullptr); 1382 polymorphic_allocator<_TPools> a(upstream_resource()); 1383 _TPools* p = a.allocate(1); 1384 __try 1385 { 1386 a.construct(p, *this, l); 1387 } 1388 __catch(...) 1389 { 1390 a.deallocate(p, 1); 1391 __throw_exception_again; 1392 } 1393 // __glibcxx_assert(p->next == nullptr); 1394 // __glibcxx_assert(p->prev == nullptr); 1395 return p; 1396 } 1397 #endif // _GLIBCXX_HAS_GTHREADS 1398 1399 // unsynchronized_pool_resource member functions 1400 1401 // Constructor 1402 unsynchronized_pool_resource:: 1403 unsynchronized_pool_resource(const pool_options& opts, 1404 memory_resource* upstream) 1405 : _M_impl(opts, upstream), _M_pools(_M_impl._M_alloc_pools()) 1406 { } 1407 1408 // Destructor 1409 unsynchronized_pool_resource::~unsynchronized_pool_resource() 1410 { release(); } 1411 1412 // Return all memory to upstream resource. 1413 void 1414 unsynchronized_pool_resource::release() 1415 { 1416 // release pooled memory 1417 if (_M_pools) 1418 { 1419 memory_resource* res = upstream_resource(); 1420 polymorphic_allocator<_Pool> alloc{res}; 1421 for (int i = 0; i < _M_impl._M_npools; ++i) 1422 { 1423 _M_pools[i].release(res); 1424 alloc.destroy(_M_pools + i); 1425 } 1426 alloc.deallocate(_M_pools, _M_impl._M_npools); 1427 _M_pools = nullptr; 1428 } 1429 1430 // release unpooled memory 1431 _M_impl.release(); 1432 } 1433 1434 // Find the right pool for a block of size block_size. 1435 auto 1436 unsynchronized_pool_resource::_M_find_pool(size_t block_size) noexcept 1437 { 1438 __pool_resource::_Pool* pool = nullptr; 1439 if (_M_pools) // [[likely]] 1440 { 1441 int index = pool_index(block_size, _M_impl._M_npools); 1442 if (index != -1) 1443 pool = _M_pools + index; 1444 } 1445 return pool; 1446 } 1447 1448 // Override for memory_resource::do_allocate 1449 void* 1450 unsynchronized_pool_resource::do_allocate(size_t bytes, size_t alignment) 1451 { 1452 const auto block_size = std::max(bytes, alignment); 1453 if (block_size <= _M_impl._M_opts.largest_required_pool_block) 1454 { 1455 // Recreate pools if release() has been called: 1456 if (__builtin_expect(_M_pools == nullptr, false)) 1457 _M_pools = _M_impl._M_alloc_pools(); 1458 if (auto pool = _M_find_pool(block_size)) 1459 return pool->allocate(upstream_resource(), _M_impl._M_opts); 1460 } 1461 return _M_impl.allocate(bytes, alignment); 1462 } 1463 1464 // Override for memory_resource::do_deallocate 1465 void 1466 unsynchronized_pool_resource:: 1467 do_deallocate(void* p, size_t bytes, size_t alignment) 1468 { 1469 size_t block_size = std::max(bytes, alignment); 1470 if (block_size <= _M_impl._M_opts.largest_required_pool_block) 1471 { 1472 if (auto pool = _M_find_pool(block_size)) 1473 { 1474 pool->deallocate(upstream_resource(), p); 1475 return; 1476 } 1477 } 1478 _M_impl.deallocate(p, bytes, alignment); 1479 } 1480 1481 } // namespace pmr 1482 _GLIBCXX_END_NAMESPACE_VERSION 1483 } // namespace std 1484