mirror of git://gcc.gnu.org/git/gcc.git
Implement std::pmr::unsynchronized_pool_resource
Implement std::pmr::unsynchronized_pool_resource * config/abi/pre/gnu.ver: Add new symbols. * include/std/memory_resource (std::pmr::__pool_resource): New class. (std::pmr::unsynchronized_pool_resource): New class. * src/c++17/Makefile.am: Add -fimplicit-templates to flags for memory_resource.cc * src/c++17/Makefile.in: Regenerate. * src/c++17/memory_resource.cc (bitset, chunk, big_block): New internal classes. (__pool_resource::_Pool): Define new class. (munge_options, pool_index, select_num_pools): New internal functions. (__pool_resource::__pool_resource, __pool_resource::~__pool_resource) (__pool_resource::allocate, __pool_resource::deallocate) (__pool_resource::_M_alloc_pools): Define member functions. (unsynchronized_pool_resource::unsynchronized_pool_resource) (unsynchronized_pool_resource::~unsynchronized_pool_resource) (unsynchronized_pool_resource::release) (unsynchronized_pool_resource::_M_find_pool) (unsynchronized_pool_resource::do_allocate) (unsynchronized_pool_resource::do_deallocate): Define member functions. * testsuite/20_util/unsynchronized_pool_resource/allocate.cc: New test. * testsuite/20_util/unsynchronized_pool_resource/is_equal.cc: New test. * testsuite/20_util/unsynchronized_pool_resource/options.cc: New test. * testsuite/20_util/unsynchronized_pool_resource/release.cc: New test. From-SVN: r265853
This commit is contained in:
parent
dd1501c5ab
commit
852a971c26
|
|
@ -1,3 +1,35 @@
|
||||||
|
2018-11-06 Jonathan Wakely <jwakely@redhat.com>
|
||||||
|
|
||||||
|
Implement std::pmr::unsynchronized_pool_resource
|
||||||
|
* config/abi/pre/gnu.ver: Add new symbols.
|
||||||
|
* include/std/memory_resource (std::pmr::__pool_resource): New class.
|
||||||
|
(std::pmr::unsynchronized_pool_resource): New class.
|
||||||
|
* src/c++17/Makefile.am: Add -fimplicit-templates to flags for
|
||||||
|
memory_resource.cc
|
||||||
|
* src/c++17/Makefile.in: Regenerate.
|
||||||
|
* src/c++17/memory_resource.cc (bitset, chunk, big_block): New
|
||||||
|
internal classes.
|
||||||
|
(__pool_resource::_Pool): Define new class.
|
||||||
|
(munge_options, pool_index, select_num_pools): New internal functions.
|
||||||
|
(__pool_resource::__pool_resource, __pool_resource::~__pool_resource)
|
||||||
|
(__pool_resource::allocate, __pool_resource::deallocate)
|
||||||
|
(__pool_resource::_M_alloc_pools): Define member functions.
|
||||||
|
(unsynchronized_pool_resource::unsynchronized_pool_resource)
|
||||||
|
(unsynchronized_pool_resource::~unsynchronized_pool_resource)
|
||||||
|
(unsynchronized_pool_resource::release)
|
||||||
|
(unsynchronized_pool_resource::_M_find_pool)
|
||||||
|
(unsynchronized_pool_resource::do_allocate)
|
||||||
|
(unsynchronized_pool_resource::do_deallocate): Define member
|
||||||
|
functions.
|
||||||
|
* testsuite/20_util/unsynchronized_pool_resource/allocate.cc: New
|
||||||
|
test.
|
||||||
|
* testsuite/20_util/unsynchronized_pool_resource/is_equal.cc: New
|
||||||
|
test.
|
||||||
|
* testsuite/20_util/unsynchronized_pool_resource/options.cc: New
|
||||||
|
test.
|
||||||
|
* testsuite/20_util/unsynchronized_pool_resource/release.cc: New
|
||||||
|
test.
|
||||||
|
|
||||||
2018-11-06 John Bytheway <jbytheway@gmail.com>
|
2018-11-06 John Bytheway <jbytheway@gmail.com>
|
||||||
|
|
||||||
PR libstdc++/87872
|
PR libstdc++/87872
|
||||||
|
|
|
||||||
|
|
@ -2055,6 +2055,15 @@ GLIBCXX_3.4.26 {
|
||||||
_ZNSt13basic_filebufI[cw]St11char_traitsI[cw]EE4openEPKwSt13_Ios_Openmode;
|
_ZNSt13basic_filebufI[cw]St11char_traitsI[cw]EE4openEPKwSt13_Ios_Openmode;
|
||||||
|
|
||||||
_ZN11__gnu_debug25_Safe_local_iterator_base16_M_attach_singleEPNS_19_Safe_sequence_baseEb;
|
_ZN11__gnu_debug25_Safe_local_iterator_base16_M_attach_singleEPNS_19_Safe_sequence_baseEb;
|
||||||
|
|
||||||
|
# <memory_resource> members
|
||||||
|
_ZTINSt3pmr28unsynchronized_pool_resourceE;
|
||||||
|
_ZNSt3pmr28unsynchronized_pool_resourceC[12]ERKNS_12pool_optionsEPNS_15memory_resourceE;
|
||||||
|
_ZNSt3pmr28unsynchronized_pool_resourceD[12]Ev;
|
||||||
|
_ZNSt3pmr28unsynchronized_pool_resource7releaseEv;
|
||||||
|
_ZNSt3pmr28unsynchronized_pool_resource11do_allocateEmm;
|
||||||
|
_ZNSt3pmr28unsynchronized_pool_resource13do_deallocateEPvmm;
|
||||||
|
|
||||||
} GLIBCXX_3.4.25;
|
} GLIBCXX_3.4.25;
|
||||||
|
|
||||||
# Symbols in the support library (libsupc++) have their own tag.
|
# Symbols in the support library (libsupc++) have their own tag.
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@
|
||||||
|
|
||||||
#if __cplusplus >= 201703L
|
#if __cplusplus >= 201703L
|
||||||
|
|
||||||
#include <bit> // __ceil2, __log2p1
|
|
||||||
#include <memory> // align, allocator_arg_t, __uses_alloc
|
#include <memory> // align, allocator_arg_t, __uses_alloc
|
||||||
#include <utility> // pair, index_sequence
|
#include <utility> // pair, index_sequence
|
||||||
|
#include <vector> // vector
|
||||||
#include <cstddef> // size_t, max_align_t
|
#include <cstddef> // size_t, max_align_t
|
||||||
#include <debug/assertions.h>
|
#include <debug/assertions.h>
|
||||||
|
|
||||||
|
|
@ -296,8 +296,107 @@ namespace pmr
|
||||||
size_t largest_required_pool_block = 0;
|
size_t largest_required_pool_block = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO class synchronized_pool_resource;
|
// Common implementation details for unsynchronized/synchronized pool resources.
|
||||||
// TODO class unsynchronized_pool_resource;
|
class __pool_resource
|
||||||
|
{
|
||||||
|
friend class synchronized_pool_resource;
|
||||||
|
friend class unsynchronized_pool_resource;
|
||||||
|
|
||||||
|
__pool_resource(const pool_options& __opts, memory_resource* __upstream);
|
||||||
|
|
||||||
|
~__pool_resource();
|
||||||
|
|
||||||
|
__pool_resource(const __pool_resource&) = delete;
|
||||||
|
__pool_resource& operator=(const __pool_resource&) = delete;
|
||||||
|
|
||||||
|
// Allocate a large unpooled block.
|
||||||
|
void*
|
||||||
|
allocate(size_t __bytes, size_t __alignment);
|
||||||
|
|
||||||
|
// Deallocate a large unpooled block.
|
||||||
|
void
|
||||||
|
deallocate(void* __p, size_t __bytes, size_t __alignment);
|
||||||
|
|
||||||
|
|
||||||
|
// Deallocate unpooled memory.
|
||||||
|
void release() noexcept;
|
||||||
|
|
||||||
|
memory_resource* resource() const noexcept
|
||||||
|
{ return _M_unpooled.get_allocator().resource(); }
|
||||||
|
|
||||||
|
struct _Pool;
|
||||||
|
|
||||||
|
_Pool* _M_alloc_pools();
|
||||||
|
|
||||||
|
const pool_options _M_opts;
|
||||||
|
|
||||||
|
struct _BigBlock;
|
||||||
|
// Collection of blocks too big for any pool, sorted by address.
|
||||||
|
// This also stores the only copy of the upstream memory resource pointer.
|
||||||
|
pmr::vector<_BigBlock> _M_unpooled;
|
||||||
|
|
||||||
|
const int _M_npools;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO class synchronized_pool_resource
|
||||||
|
|
||||||
|
/// A non-thread-safe memory resource that manages pools of fixed-size blocks.
|
||||||
|
class unsynchronized_pool_resource : public memory_resource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[__gnu__::__nonnull__]]
|
||||||
|
unsynchronized_pool_resource(const pool_options& __opts,
|
||||||
|
memory_resource* __upstream);
|
||||||
|
|
||||||
|
unsynchronized_pool_resource()
|
||||||
|
: unsynchronized_pool_resource(pool_options(), get_default_resource())
|
||||||
|
{ }
|
||||||
|
|
||||||
|
[[__gnu__::__nonnull__]]
|
||||||
|
explicit
|
||||||
|
unsynchronized_pool_resource(memory_resource* __upstream)
|
||||||
|
: unsynchronized_pool_resource(pool_options(), __upstream)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
explicit
|
||||||
|
unsynchronized_pool_resource(const pool_options& __opts)
|
||||||
|
: unsynchronized_pool_resource(__opts, get_default_resource()) { }
|
||||||
|
|
||||||
|
unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete;
|
||||||
|
|
||||||
|
virtual ~unsynchronized_pool_resource();
|
||||||
|
|
||||||
|
unsynchronized_pool_resource&
|
||||||
|
operator=(const unsynchronized_pool_resource&) = delete;
|
||||||
|
|
||||||
|
void release();
|
||||||
|
|
||||||
|
[[__gnu__::__returns_nonnull__]]
|
||||||
|
memory_resource*
|
||||||
|
upstream_resource() const noexcept
|
||||||
|
{ return _M_impl.resource(); }
|
||||||
|
|
||||||
|
pool_options options() const noexcept { return _M_impl._M_opts; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void*
|
||||||
|
do_allocate(size_t __bytes, size_t __alignment) override;
|
||||||
|
|
||||||
|
void
|
||||||
|
do_deallocate(void* __p, size_t __bytes, size_t __alignment) override;
|
||||||
|
|
||||||
|
bool
|
||||||
|
do_is_equal(const memory_resource& __other) const noexcept override
|
||||||
|
{ return this == &__other; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
using _Pool = __pool_resource::_Pool;
|
||||||
|
|
||||||
|
auto _M_find_pool(size_t) noexcept;
|
||||||
|
|
||||||
|
__pool_resource _M_impl;
|
||||||
|
_Pool* _M_pools = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
class monotonic_buffer_resource : public memory_resource
|
class monotonic_buffer_resource : public memory_resource
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ AM_CXXFLAGS = \
|
||||||
AM_MAKEFLAGS = \
|
AM_MAKEFLAGS = \
|
||||||
"gxx_include_dir=$(gxx_include_dir)"
|
"gxx_include_dir=$(gxx_include_dir)"
|
||||||
|
|
||||||
|
memory_resource.lo: memory_resource.cc
|
||||||
|
$(LTCXXCOMPILE) -fimplicit-templates -c $< -o $@
|
||||||
|
memory_resource.o: memory_resource.cc
|
||||||
|
$(CXXCOMPILE) -fimplicit-templates -c $< -o $@
|
||||||
|
|
||||||
# Libtool notes
|
# Libtool notes
|
||||||
|
|
||||||
# 1) In general, libtool expects an argument such as `--tag=CXX' when
|
# 1) In general, libtool expects an argument such as `--tag=CXX' when
|
||||||
|
|
|
||||||
|
|
@ -731,6 +731,11 @@ uninstall-am:
|
||||||
|
|
||||||
vpath % $(top_srcdir)/src/c++17
|
vpath % $(top_srcdir)/src/c++17
|
||||||
|
|
||||||
|
memory_resource.lo: memory_resource.cc
|
||||||
|
$(LTCXXCOMPILE) -fimplicit-templates -c $< -o $@
|
||||||
|
memory_resource.o: memory_resource.cc
|
||||||
|
$(CXXCOMPILE) -fimplicit-templates -c $< -o $@
|
||||||
|
|
||||||
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||||
# Otherwise a system limit (for SysV at least) may be exceeded.
|
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||||
.NOEXPORT:
|
.NOEXPORT:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,9 @@
|
||||||
// <http://www.gnu.org/licenses/>.
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
#include <memory_resource>
|
#include <memory_resource>
|
||||||
|
#include <algorithm> // lower_bound, rotate
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <bit> // __ceil2, __log2p1
|
||||||
#include <new>
|
#include <new>
|
||||||
#if ATOMIC_POINTER_LOCK_FREE != 2
|
#if ATOMIC_POINTER_LOCK_FREE != 2
|
||||||
# include <bits/std_mutex.h> // std::mutex, std::lock_guard
|
# include <bits/std_mutex.h> // std::mutex, std::lock_guard
|
||||||
|
|
@ -246,8 +248,788 @@ namespace pmr
|
||||||
_Chunk::release(_M_head, _M_upstream);
|
_Chunk::release(_M_head, _M_upstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper types for synchronized_pool_resource & unsynchronized_pool_resource
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Simple bitset with runtime size. Tracks used blocks in a pooled chunk.
|
||||||
|
struct bitset
|
||||||
|
{
|
||||||
|
using word = uint64_t;
|
||||||
|
using size_type = uint32_t;
|
||||||
|
|
||||||
|
static constexpr unsigned bits_per_word = numeric_limits<word>::digits;
|
||||||
|
|
||||||
|
// The bitset does not own p
|
||||||
|
bitset(void* p, size_type num_blocks)
|
||||||
|
: _M_words(static_cast<word*>(p)), _M_size(num_blocks),
|
||||||
|
_M_next_word(0)
|
||||||
|
{
|
||||||
|
const size_type last_word = num_blocks / bits_per_word;
|
||||||
|
__builtin_memset(_M_words, 0, last_word * sizeof(*_M_words));
|
||||||
|
// Set bits beyond _M_size, so they are not treated as free blocks:
|
||||||
|
if (const size_type extra_bits = num_blocks % bits_per_word)
|
||||||
|
_M_words[last_word] = (word)-1 << extra_bits;
|
||||||
|
__glibcxx_assert( empty() );
|
||||||
|
__glibcxx_assert( free() == num_blocks );
|
||||||
|
}
|
||||||
|
|
||||||
|
bitset() = default;
|
||||||
|
~bitset() = default;
|
||||||
|
|
||||||
|
// Number of blocks
|
||||||
|
size_t size() const noexcept { return _M_size; }
|
||||||
|
|
||||||
|
// Number of unset bits
|
||||||
|
size_t free() const noexcept
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
for (size_type i = _M_next_word; i < nwords(); ++i)
|
||||||
|
n += (bits_per_word - std::__popcount(_M_words[i]));
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if all bits are set
|
||||||
|
bool full() const noexcept { return _M_next_word >= nwords(); }
|
||||||
|
|
||||||
|
// True if size() != 0 and no bits are set.
|
||||||
|
bool empty() const noexcept
|
||||||
|
{
|
||||||
|
if (nwords() == 0)
|
||||||
|
return false;
|
||||||
|
if (_M_next_word != 0)
|
||||||
|
return false;
|
||||||
|
for (size_type i = 0; i < nwords() - 1; ++i)
|
||||||
|
if (_M_words[i] != 0)
|
||||||
|
return false;
|
||||||
|
word last = _M_words[nwords() - 1];
|
||||||
|
if (const size_type extra_bits = size() % bits_per_word)
|
||||||
|
last <<= (bits_per_word - extra_bits);
|
||||||
|
return last == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() noexcept
|
||||||
|
{
|
||||||
|
_M_words = nullptr;
|
||||||
|
_M_size = _M_next_word = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator[](size_type n) const noexcept
|
||||||
|
{
|
||||||
|
__glibcxx_assert( n < _M_size );
|
||||||
|
const size_type wd = n / bits_per_word;
|
||||||
|
const word bit = word(1) << (n % bits_per_word);
|
||||||
|
return _M_words[wd] & bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type find_first_unset() const noexcept
|
||||||
|
{
|
||||||
|
for (size_type i = _M_next_word; i < nwords(); ++i)
|
||||||
|
{
|
||||||
|
const size_type n = std::__countr_one(_M_words[i]);
|
||||||
|
if (n < bits_per_word)
|
||||||
|
return (i * bits_per_word) + n;
|
||||||
|
}
|
||||||
|
return size_type(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type get_first_unset() noexcept
|
||||||
|
{
|
||||||
|
for (size_type i = _M_next_word; i < nwords(); ++i)
|
||||||
|
{
|
||||||
|
const size_type n = std::__countr_one(_M_words[i]);
|
||||||
|
if (n < bits_per_word)
|
||||||
|
{
|
||||||
|
const word bit = word(1) << n;
|
||||||
|
_M_words[i] |= bit;
|
||||||
|
if (i == _M_next_word)
|
||||||
|
{
|
||||||
|
while (_M_words[_M_next_word] == word(-1)
|
||||||
|
&& ++_M_next_word != nwords())
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
return (i * bits_per_word) + n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size_type(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(size_type n) noexcept
|
||||||
|
{
|
||||||
|
__glibcxx_assert( n < _M_size );
|
||||||
|
const size_type wd = n / bits_per_word;
|
||||||
|
const word bit = word(1) << (n % bits_per_word);
|
||||||
|
_M_words[wd] |= bit;
|
||||||
|
if (wd == _M_next_word)
|
||||||
|
{
|
||||||
|
while (_M_words[_M_next_word] == word(-1)
|
||||||
|
&& ++_M_next_word != nwords())
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear(size_type n) noexcept
|
||||||
|
{
|
||||||
|
__glibcxx_assert( n < _M_size );
|
||||||
|
const size_type wd = n / bits_per_word;
|
||||||
|
const word bit = word(1) << (n % bits_per_word);
|
||||||
|
_M_words[wd] &= ~bit;
|
||||||
|
if (wd < _M_next_word)
|
||||||
|
_M_next_word = wd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(bitset& b) noexcept
|
||||||
|
{
|
||||||
|
std::swap(_M_words, b._M_words);
|
||||||
|
size_type tmp = _M_size;
|
||||||
|
_M_size = b._M_size;
|
||||||
|
b._M_size = tmp;
|
||||||
|
tmp = _M_next_word;
|
||||||
|
_M_next_word = b._M_next_word;
|
||||||
|
b._M_next_word = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type nwords() const noexcept
|
||||||
|
{ return (_M_size + bits_per_word - 1) / bits_per_word; }
|
||||||
|
|
||||||
|
// Maximum value that can be stored in bitset::_M_size member (approx 500k)
|
||||||
|
static constexpr size_t max_blocks_per_chunk() noexcept
|
||||||
|
{ return (1ull << _S_size_digits) - 1; }
|
||||||
|
|
||||||
|
word* data() const noexcept { return _M_words; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr unsigned _S_size_digits
|
||||||
|
= (numeric_limits<size_type>::digits
|
||||||
|
+ std::__log2p1(bits_per_word) - 1) / 2;
|
||||||
|
|
||||||
|
word* _M_words = nullptr;
|
||||||
|
// Number of blocks represented by the bitset:
|
||||||
|
size_type _M_size : _S_size_digits;
|
||||||
|
// Index of the first word with unset bits:
|
||||||
|
size_type _M_next_word : numeric_limits<size_type>::digits - _S_size_digits;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A "chunk" belonging to a pool.
|
||||||
|
// A chunk contains many blocks of the same size.
|
||||||
|
// Derived from bitset to reuse its tail-padding.
|
||||||
|
struct chunk : bitset
|
||||||
|
{
|
||||||
|
chunk() = default;
|
||||||
|
|
||||||
|
// p points to the start of a chunk of size bytes in length.
|
||||||
|
// The chunk has space for n blocks, followed by a bitset of size n
|
||||||
|
// that begins at address words.
|
||||||
|
// This object does not own p or words, the caller will free it.
|
||||||
|
chunk(void* p, size_t bytes, void* words, size_t n)
|
||||||
|
: bitset(words, n),
|
||||||
|
_M_bytes(bytes),
|
||||||
|
_M_p(static_cast<std::byte*>(p))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
chunk(chunk&& c) noexcept
|
||||||
|
: bitset(std::move(c)), _M_bytes(c._M_bytes), _M_p(c._M_p)
|
||||||
|
{
|
||||||
|
c._M_bytes = 0;
|
||||||
|
c._M_p = nullptr;
|
||||||
|
c.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk& operator=(chunk&& c) noexcept
|
||||||
|
{
|
||||||
|
swap(c);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocated size of chunk:
|
||||||
|
unsigned _M_bytes = 0;
|
||||||
|
// Start of allocated chunk:
|
||||||
|
std::byte* _M_p = nullptr;
|
||||||
|
|
||||||
|
// True if there are free blocks in this chunk
|
||||||
|
using bitset::full;
|
||||||
|
// Number of blocks in this chunk
|
||||||
|
using bitset::size;
|
||||||
|
|
||||||
|
// Determine if block with address p and size block_size
|
||||||
|
// is contained within this chunk.
|
||||||
|
bool owns(void* p, size_t block_size)
|
||||||
|
{
|
||||||
|
std::less_equal<uintptr_t> less_equal;
|
||||||
|
return less_equal(reinterpret_cast<uintptr_t>(_M_p),
|
||||||
|
reinterpret_cast<uintptr_t>(p))
|
||||||
|
&& less_equal(reinterpret_cast<uintptr_t>(p) + block_size,
|
||||||
|
reinterpret_cast<uintptr_t>(bitset::data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate next available block of block_size bytes from this chunk.
|
||||||
|
void* reserve(size_t block_size) noexcept
|
||||||
|
{
|
||||||
|
const size_type n = get_first_unset();
|
||||||
|
if (n == size_type(-1))
|
||||||
|
return nullptr;
|
||||||
|
return _M_p + (n * block_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate a single block of block_size bytes
|
||||||
|
void release(void* vp, size_t block_size)
|
||||||
|
{
|
||||||
|
__glibcxx_assert( owns(vp, block_size) );
|
||||||
|
const size_t offset = static_cast<std::byte*>(vp) - _M_p;
|
||||||
|
// Pointer is correctly aligned for a block in this chunk:
|
||||||
|
__glibcxx_assert( (offset % block_size) == 0 );
|
||||||
|
// Block has been allocated:
|
||||||
|
__glibcxx_assert( (*this)[offset / block_size] == true );
|
||||||
|
bitset::clear(offset / block_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate a single block if it belongs to this chunk.
|
||||||
|
bool try_release(void* p, size_t block_size)
|
||||||
|
{
|
||||||
|
if (!owns(p, block_size))
|
||||||
|
return false;
|
||||||
|
release(p, block_size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(chunk& c) noexcept
|
||||||
|
{
|
||||||
|
std::swap(_M_bytes, c._M_bytes);
|
||||||
|
std::swap(_M_p, c._M_p);
|
||||||
|
bitset::swap(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const chunk& c) const noexcept
|
||||||
|
{ return std::less<const void*>{}(_M_p, c._M_p); }
|
||||||
|
|
||||||
|
friend void swap(chunk& l, chunk& r) { l.swap(r); }
|
||||||
|
|
||||||
|
friend bool operator<(const void* p, const chunk& c) noexcept
|
||||||
|
{ return std::less<const void*>{}(p, c._M_p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __LP64__
|
||||||
|
// TODO pad up to 4*sizeof(void*) to avoid splitting across cache lines?
|
||||||
|
static_assert(sizeof(chunk) == (3 * sizeof(void*)), "");
|
||||||
|
#else
|
||||||
|
static_assert(sizeof(chunk) == (4 * sizeof(void*)), "");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// An oversized allocation that doesn't fit in a pool.
|
||||||
|
struct big_block
|
||||||
|
{
|
||||||
|
static constexpr unsigned _S_alignbits
|
||||||
|
= std::__log2p1((unsigned)numeric_limits<size_t>::digits) - 1;
|
||||||
|
static constexpr unsigned _S_sizebits
|
||||||
|
= numeric_limits<size_t>::digits - _S_alignbits;
|
||||||
|
// The maximum value that can be stored in _S_size
|
||||||
|
static constexpr size_t all_ones = (1ul << _S_sizebits) - 1u;
|
||||||
|
// The minimum size of a big block
|
||||||
|
static constexpr size_t min = 1u << _S_alignbits;
|
||||||
|
|
||||||
|
big_block(size_t bytes, size_t alignment)
|
||||||
|
: _M_size((bytes + min - 1u) >> _S_alignbits),
|
||||||
|
_M_align_exp(std::__log2p1(alignment) - 1u)
|
||||||
|
{
|
||||||
|
if (__builtin_expect(std::__countl_one(bytes) == _S_sizebits, false))
|
||||||
|
_M_size = all_ones;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* pointer = nullptr;
|
||||||
|
size_t _M_size : numeric_limits<size_t>::digits - _S_alignbits;
|
||||||
|
size_t _M_align_exp : _S_alignbits;
|
||||||
|
|
||||||
|
size_t size() const noexcept
|
||||||
|
{
|
||||||
|
if (__builtin_expect(_M_size == all_ones, false))
|
||||||
|
return (size_t)-1;
|
||||||
|
else
|
||||||
|
return _M_size << _S_alignbits;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t align() const noexcept { return 1ul << _M_align_exp; }
|
||||||
|
|
||||||
|
friend bool operator<(void* p, const big_block& b) noexcept
|
||||||
|
{ return less<void*>{}(p, b.pointer); }
|
||||||
|
|
||||||
|
friend bool operator<(const big_block& b, void* p) noexcept
|
||||||
|
{ return less<void*>{}(b.pointer, p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(big_block) == (2 * sizeof(void*)));
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// A pool that serves blocks of a particular size.
|
||||||
|
// Each pool manages a number of chunks.
|
||||||
|
// When a pool is full it is replenished by allocating another chunk.
|
||||||
|
struct __pool_resource::_Pool
|
||||||
|
{
|
||||||
|
// Smallest supported block size
|
||||||
|
static constexpr unsigned _S_min_block
|
||||||
|
= std::max(sizeof(void*), alignof(bitset::word));
|
||||||
|
|
||||||
|
_Pool(size_t __block_size, size_t __blocks_per_chunk)
|
||||||
|
: _M_chunks(),
|
||||||
|
_M_block_sz(__block_size),
|
||||||
|
_M_blocks_per_chunk(__blocks_per_chunk)
|
||||||
|
{
|
||||||
|
__glibcxx_assert(block_size() == __block_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must call release(r) before destruction!
|
||||||
|
~_Pool() { __glibcxx_assert(_M_chunks.empty()); }
|
||||||
|
|
||||||
|
_Pool(_Pool&&) noexcept = default;
|
||||||
|
_Pool& operator=(_Pool&&) noexcept = default;
|
||||||
|
|
||||||
|
// Size of blocks in this pool
|
||||||
|
size_t block_size() const noexcept
|
||||||
|
#if POW2_BLKSZ
|
||||||
|
{ return _S_min_block << _M_blksize_mul; }
|
||||||
|
#else
|
||||||
|
{ return _M_block_sz; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Allocate a block if the pool is not full, otherwise return null.
|
||||||
|
void* try_allocate() noexcept
|
||||||
|
{
|
||||||
|
const size_t blocksz = block_size();
|
||||||
|
if (!_M_chunks.empty())
|
||||||
|
{
|
||||||
|
auto& last = _M_chunks.back();
|
||||||
|
if (void* p = last.reserve(blocksz))
|
||||||
|
return p;
|
||||||
|
// TODO last is full, so move another chunk to the back instead?
|
||||||
|
for (auto it = _M_chunks.begin(); it != &last; ++it)
|
||||||
|
if (void* p = it->reserve(blocksz))
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a block from the pool, replenishing from upstream if needed.
|
||||||
|
void* allocate(memory_resource* r, const pool_options& opts)
|
||||||
|
{
|
||||||
|
if (void* p = try_allocate())
|
||||||
|
return p;
|
||||||
|
replenish(r, opts);
|
||||||
|
return _M_chunks.back().reserve(block_size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a block to the pool.
|
||||||
|
bool deallocate(memory_resource*, void* p)
|
||||||
|
{
|
||||||
|
const size_t blocksz = block_size();
|
||||||
|
if (__builtin_expect(!_M_chunks.empty(), true))
|
||||||
|
{
|
||||||
|
auto& last = _M_chunks.back();
|
||||||
|
if (last.try_release(p, blocksz))
|
||||||
|
return true;
|
||||||
|
auto it = std::upper_bound(_M_chunks.begin(), &last, p);
|
||||||
|
if (it != _M_chunks.begin())
|
||||||
|
{
|
||||||
|
it--;
|
||||||
|
if (it->try_release(p, blocksz))
|
||||||
|
// If chunk is empty could return to upstream, but we don't
|
||||||
|
// currently do that. Pools only increase in size.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void replenish(memory_resource* __r, const pool_options& __opts)
|
||||||
|
{
|
||||||
|
using word = chunk::word;
|
||||||
|
const size_t __blocks
|
||||||
|
= std::min<size_t>(__opts.max_blocks_per_chunk, _M_blocks_per_chunk);
|
||||||
|
const auto __bits = chunk::bits_per_word;
|
||||||
|
const size_t __words = (__blocks + __bits - 1) / __bits;
|
||||||
|
const size_t __block_size = block_size();
|
||||||
|
size_t __bytes = __blocks * __block_size + __words * sizeof(word);
|
||||||
|
size_t __alignment = std::__ceil2(__block_size);
|
||||||
|
void* __p = __r->allocate(__bytes, __alignment);
|
||||||
|
__try
|
||||||
|
{
|
||||||
|
size_t __n = __blocks * __block_size;
|
||||||
|
void* __pwords = static_cast<char*>(__p) + __n;
|
||||||
|
_M_chunks.insert(chunk(__p, __bytes, __pwords, __blocks), __r);
|
||||||
|
}
|
||||||
|
__catch (...)
|
||||||
|
{
|
||||||
|
__r->deallocate(__p, __bytes, __alignment);
|
||||||
|
}
|
||||||
|
if (_M_blocks_per_chunk < __opts.max_blocks_per_chunk)
|
||||||
|
_M_blocks_per_chunk *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(memory_resource* __r)
|
||||||
|
{
|
||||||
|
const size_t __alignment = std::__ceil2(block_size());
|
||||||
|
for (auto& __c : _M_chunks)
|
||||||
|
if (__c._M_p)
|
||||||
|
__r->deallocate(__c._M_p, __c._M_bytes, __alignment);
|
||||||
|
_M_chunks.clear(__r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A "resourceless vector" instead of pmr::vector, to save space.
|
||||||
|
// All resize operations need to be passed a memory resource, which
|
||||||
|
// obviously needs to be the same one every time.
|
||||||
|
// Chunks are kept sorted by address of their first block, except for
|
||||||
|
// the most recently-allocated Chunk which is at the end of the vector.
|
||||||
|
struct vector
|
||||||
|
{
|
||||||
|
using value_type = chunk;
|
||||||
|
using size_type = unsigned;
|
||||||
|
using iterator = value_type*;
|
||||||
|
|
||||||
|
// A vector owns its data pointer but not memory held by its elements.
|
||||||
|
chunk* data = nullptr;
|
||||||
|
size_type size = 0;
|
||||||
|
size_type capacity = 0;
|
||||||
|
|
||||||
|
vector() = default;
|
||||||
|
|
||||||
|
vector(size_type __n, memory_resource* __r)
|
||||||
|
: data(polymorphic_allocator<value_type>(__r).allocate(__n)),
|
||||||
|
capacity(__n)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
// Must call clear(r) before destruction!
|
||||||
|
~vector() { __glibcxx_assert(data == nullptr); }
|
||||||
|
|
||||||
|
vector(vector&& __rval) noexcept
|
||||||
|
: data(__rval.data), size(__rval.size), capacity(__rval.capacity)
|
||||||
|
{
|
||||||
|
__rval.data = nullptr;
|
||||||
|
__rval.capacity = __rval.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector& operator=(vector&& __rval) noexcept
|
||||||
|
{
|
||||||
|
__glibcxx_assert(data == nullptr);
|
||||||
|
data = __rval.data;
|
||||||
|
size = __rval.size;
|
||||||
|
capacity = __rval.capacity;
|
||||||
|
__rval.data = nullptr;
|
||||||
|
__rval.capacity = __rval.size = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void resize(size_type __n, memory_resource* __r);
|
||||||
|
// void reserve(size_type __n, memory_resource* __r);
|
||||||
|
|
||||||
|
void clear(memory_resource* __r)
|
||||||
|
{
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
// Chunks must be individually freed before clearing the vector.
|
||||||
|
std::destroy(begin(), end());
|
||||||
|
polymorphic_allocator<value_type>(__r).deallocate(data, capacity);
|
||||||
|
data = nullptr;
|
||||||
|
capacity = size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort existing elements then insert new one at the end.
|
||||||
|
iterator insert(chunk&& c, memory_resource* r)
|
||||||
|
{
|
||||||
|
if (size < capacity)
|
||||||
|
{
|
||||||
|
if (size > 1)
|
||||||
|
{
|
||||||
|
auto mid = end() - 1;
|
||||||
|
std::rotate(std::lower_bound(begin(), mid, *mid), mid, end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (size > 0)
|
||||||
|
{
|
||||||
|
polymorphic_allocator<value_type> __alloc(r);
|
||||||
|
auto __mid = std::lower_bound(begin(), end() - 1, back());
|
||||||
|
auto __p = __alloc.allocate(capacity * 1.5);
|
||||||
|
// move [begin,__mid) to new storage
|
||||||
|
auto __p2 = std::move(begin(), __mid, __p);
|
||||||
|
// move end-1 to new storage
|
||||||
|
*__p2 = std::move(back());
|
||||||
|
// move [__mid,end-1) to new storage
|
||||||
|
std::move(__mid, end() - 1, ++__p2);
|
||||||
|
std::destroy(begin(), end());
|
||||||
|
__alloc.deallocate(data, capacity);
|
||||||
|
data = __p;
|
||||||
|
capacity *= 1.5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
polymorphic_allocator<value_type> __alloc(r);
|
||||||
|
data = __alloc.allocate(capacity = 8);
|
||||||
|
}
|
||||||
|
auto back = ::new (data + size) chunk(std::move(c));
|
||||||
|
__glibcxx_assert(std::is_sorted(begin(), back));
|
||||||
|
++size;
|
||||||
|
return back;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator begin() const { return data; }
|
||||||
|
iterator end() const { return data + size; }
|
||||||
|
|
||||||
|
bool empty() const noexcept { return size == 0; }
|
||||||
|
|
||||||
|
value_type& back() { return data[size - 1]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
vector _M_chunks;
|
||||||
|
unsigned _M_block_sz; // size of blocks allocated from this pool
|
||||||
|
unsigned _M_blocks_per_chunk; // number of blocks to allocate next
|
||||||
|
};
|
||||||
|
|
||||||
|
// An oversized allocation that doesn't fit in a pool.
|
||||||
|
struct __pool_resource::_BigBlock : big_block
|
||||||
|
{
|
||||||
|
using big_block::big_block;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
pool_options
|
||||||
|
munge_options(pool_options opts)
|
||||||
|
{
|
||||||
|
// The values in the returned struct may differ from those supplied
|
||||||
|
// to the pool resource constructor in that values of zero will be
|
||||||
|
// replaced with implementation-defined defaults, and sizes may be
|
||||||
|
// rounded to unspecified granularity.
|
||||||
|
|
||||||
|
// Absolute maximum. Each pool might have a smaller maximum.
|
||||||
|
if (opts.max_blocks_per_chunk == 0)
|
||||||
|
{
|
||||||
|
opts.max_blocks_per_chunk = 1024 * 10; // TODO a good default?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO round to preferred granularity ?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.max_blocks_per_chunk > chunk::max_blocks_per_chunk())
|
||||||
|
{
|
||||||
|
opts.max_blocks_per_chunk = chunk::max_blocks_per_chunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute minimum. Likely to be much larger in practice.
|
||||||
|
if (opts.largest_required_pool_block == 0)
|
||||||
|
{
|
||||||
|
opts.largest_required_pool_block = 4096; // TODO a good default?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO round to preferred granularity ?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.largest_required_pool_block < big_block::min)
|
||||||
|
{
|
||||||
|
opts.largest_required_pool_block = big_block::min;
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t pool_sizes[] = {
|
||||||
|
8, 16, 24,
|
||||||
|
32, 48,
|
||||||
|
64, 80, 96, 112,
|
||||||
|
128, 192,
|
||||||
|
256, 320, 384, 448,
|
||||||
|
512, 768,
|
||||||
|
1024, 1536,
|
||||||
|
2048, 3072,
|
||||||
|
1<<12, 1<<13, 1<<14, 1<<15, 1<<16, 1<<17,
|
||||||
|
1<<20, 1<<21, 1<<22 // 4MB should be enough for anybody
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int
|
||||||
|
pool_index(size_t block_size, int npools)
|
||||||
|
{
|
||||||
|
auto p = std::lower_bound(pool_sizes, pool_sizes + npools, block_size);
|
||||||
|
int n = p - pool_sizes;
|
||||||
|
if (n != npools)
|
||||||
|
return n;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int
|
||||||
|
select_num_pools(const pool_options& opts)
|
||||||
|
{
|
||||||
|
auto p = std::lower_bound(std::begin(pool_sizes), std::end(pool_sizes),
|
||||||
|
opts.largest_required_pool_block);
|
||||||
|
if (int npools = p - std::begin(pool_sizes))
|
||||||
|
return npools;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
__pool_resource::
|
||||||
|
__pool_resource(const pool_options& opts, memory_resource* upstream)
|
||||||
|
: _M_opts(munge_options(opts)), _M_unpooled(upstream),
|
||||||
|
_M_npools(select_num_pools(_M_opts))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
__pool_resource::~__pool_resource() { release(); }
|
||||||
|
|
||||||
|
void
|
||||||
|
__pool_resource::release() noexcept
|
||||||
|
{
|
||||||
|
memory_resource* res = resource();
|
||||||
|
// deallocate oversize allocations
|
||||||
|
for (auto& b : _M_unpooled)
|
||||||
|
res->deallocate(b.pointer, b.size(), b.align());
|
||||||
|
pmr::vector<_BigBlock>{res}.swap(_M_unpooled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void*
|
||||||
|
__pool_resource::allocate(size_t bytes, size_t alignment)
|
||||||
|
{
|
||||||
|
auto& b = _M_unpooled.emplace_back(bytes, alignment);
|
||||||
|
__try {
|
||||||
|
void* p = resource()->allocate(b.size(), alignment);
|
||||||
|
b.pointer = p;
|
||||||
|
if (_M_unpooled.size() > 1)
|
||||||
|
{
|
||||||
|
const auto mid = _M_unpooled.end() - 1;
|
||||||
|
// move to right position in vector
|
||||||
|
std::rotate(std::lower_bound(_M_unpooled.begin(), mid, p),
|
||||||
|
mid, _M_unpooled.end());
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
} __catch(...) {
|
||||||
|
_M_unpooled.pop_back();
|
||||||
|
__throw_exception_again;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
__pool_resource::deallocate(void* p, size_t bytes [[maybe_unused]],
|
||||||
|
size_t alignment [[maybe_unused]])
|
||||||
|
{
|
||||||
|
const auto it
|
||||||
|
= std::lower_bound(_M_unpooled.begin(), _M_unpooled.end(), p);
|
||||||
|
__glibcxx_assert(it != _M_unpooled.end() && it->pointer == p);
|
||||||
|
if (it != _M_unpooled.end() && it->pointer == p) // [[likely]]
|
||||||
|
{
|
||||||
|
const auto b = *it;
|
||||||
|
__glibcxx_assert(b.size() == bytes);
|
||||||
|
__glibcxx_assert(b.align() == alignment);
|
||||||
|
_M_unpooled.erase(it);
|
||||||
|
resource()->deallocate(p, b.size(), b.align());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create array of pools, allocated from upstream resource.
|
||||||
|
auto
|
||||||
|
__pool_resource::_M_alloc_pools()
|
||||||
|
-> _Pool*
|
||||||
|
{
|
||||||
|
polymorphic_allocator<_Pool> alloc{resource()};
|
||||||
|
_Pool* p = alloc.allocate(_M_npools);
|
||||||
|
for (int i = 0; i < _M_npools; ++i)
|
||||||
|
{
|
||||||
|
const size_t block_size = pool_sizes[i];
|
||||||
|
// Decide on initial number of blocks per chunk.
|
||||||
|
// Always have at least 16 blocks per chunk:
|
||||||
|
const size_t min_blocks_per_chunk = 16;
|
||||||
|
// But for smaller blocks, use a larger initial size:
|
||||||
|
size_t blocks_per_chunk
|
||||||
|
= std::max(1024 / block_size, min_blocks_per_chunk);
|
||||||
|
// But don't exceed the requested max_blocks_per_chunk:
|
||||||
|
blocks_per_chunk
|
||||||
|
= std::min(blocks_per_chunk, _M_opts.max_blocks_per_chunk);
|
||||||
|
// Allow space for bitset to track which blocks are used/unused:
|
||||||
|
blocks_per_chunk *= 1 - 1.0 / (__CHAR_BIT__ * block_size);
|
||||||
|
// Construct a _Pool for the given block size and initial chunk size:
|
||||||
|
alloc.construct(p + i, block_size, blocks_per_chunk);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsynchronized_pool_resource member functions
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
unsynchronized_pool_resource::
|
||||||
|
unsynchronized_pool_resource(const pool_options& opts,
|
||||||
|
memory_resource* upstream)
|
||||||
|
: _M_impl(opts, upstream), _M_pools(_M_impl._M_alloc_pools())
|
||||||
|
{ }
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
unsynchronized_pool_resource::~unsynchronized_pool_resource()
|
||||||
|
{ release(); }
|
||||||
|
|
||||||
|
// Return all memory to upstream resource.
|
||||||
|
void
|
||||||
|
unsynchronized_pool_resource::release()
|
||||||
|
{
|
||||||
|
// release pooled memory
|
||||||
|
if (_M_pools)
|
||||||
|
{
|
||||||
|
memory_resource* res = upstream_resource();
|
||||||
|
polymorphic_allocator<_Pool> alloc{res};
|
||||||
|
for (int i = 0; i < _M_impl._M_npools; ++i)
|
||||||
|
{
|
||||||
|
_M_pools[i].release(res);
|
||||||
|
alloc.destroy(_M_pools + i);
|
||||||
|
}
|
||||||
|
alloc.deallocate(_M_pools, _M_impl._M_npools);
|
||||||
|
_M_pools = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// release unpooled memory
|
||||||
|
_M_impl.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the right pool for a block of size block_size.
|
||||||
|
auto
|
||||||
|
unsynchronized_pool_resource::_M_find_pool(size_t block_size) noexcept
|
||||||
|
{
|
||||||
|
__pool_resource::_Pool* pool = nullptr;
|
||||||
|
if (_M_pools) // [[likely]]
|
||||||
|
{
|
||||||
|
int index = pool_index(block_size, _M_impl._M_npools);
|
||||||
|
if (index != -1)
|
||||||
|
pool = _M_pools + index;
|
||||||
|
}
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override for memory_resource::do_allocate
|
||||||
|
void*
|
||||||
|
unsynchronized_pool_resource::do_allocate(size_t bytes, size_t alignment)
|
||||||
|
{
|
||||||
|
const auto block_size = std::max(bytes, alignment);
|
||||||
|
if (block_size <= _M_impl._M_opts.largest_required_pool_block)
|
||||||
|
{
|
||||||
|
// Recreate pools if release() has been called:
|
||||||
|
if (__builtin_expect(_M_pools == nullptr, false))
|
||||||
|
_M_pools = _M_impl._M_alloc_pools();
|
||||||
|
if (auto pool = _M_find_pool(block_size))
|
||||||
|
return pool->allocate(upstream_resource(), _M_impl._M_opts);
|
||||||
|
}
|
||||||
|
return _M_impl.allocate(bytes, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override for memory_resource::do_deallocate
|
||||||
|
void
|
||||||
|
unsynchronized_pool_resource::
|
||||||
|
do_deallocate(void* p, size_t bytes, size_t alignment)
|
||||||
|
{
|
||||||
|
size_t block_size = std::max(bytes, alignment);
|
||||||
|
if (block_size <= _M_impl._M_opts.largest_required_pool_block)
|
||||||
|
{
|
||||||
|
if (auto pool = _M_find_pool(block_size))
|
||||||
|
{
|
||||||
|
pool->deallocate(upstream_resource(), p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_M_impl.deallocate(p, bytes, alignment);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pmr
|
} // namespace pmr
|
||||||
_GLIBCXX_END_NAMESPACE_VERSION
|
_GLIBCXX_END_NAMESPACE_VERSION
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright (C) 2018 Free Software Foundation, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of the GNU ISO C++ Library. This library is free
|
||||||
|
// software; you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation; either version 3, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this library; see the file COPYING3. If not see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// { dg-options "-std=gnu++17" }
|
||||||
|
// { dg-do run { target c++17 } }
|
||||||
|
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <cstring>
|
||||||
|
#include <testsuite_allocator.h>
|
||||||
|
#include <testsuite_hooks.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
test01()
|
||||||
|
{
|
||||||
|
__gnu_test::memory_resource test_mr;
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
void* p1 = r.allocate(1, 1);
|
||||||
|
VERIFY( p1 != nullptr );
|
||||||
|
auto n = test_mr.number_of_active_allocations();
|
||||||
|
VERIFY( n > 0 );
|
||||||
|
// Ensure memory region can be written to (without corrupting heap!)
|
||||||
|
std::memset(p1, 0xff, 1);
|
||||||
|
void* p2 = r.allocate(1, 1);
|
||||||
|
VERIFY( p2 != nullptr );
|
||||||
|
VERIFY( p2 != p1 );
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n );
|
||||||
|
std::memset(p1, 0xff, 1);
|
||||||
|
r.deallocate(p1, 1, 1);
|
||||||
|
// Returning single blocks to the pool doesn't return them upstream:
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n );
|
||||||
|
r.deallocate(p2, 1, 1);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n );
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test02()
|
||||||
|
{
|
||||||
|
struct nullable_memory_resource : public std::pmr::memory_resource
|
||||||
|
{
|
||||||
|
void*
|
||||||
|
do_allocate(std::size_t bytes, std::size_t alignment) override
|
||||||
|
{ return upstream->allocate(bytes, alignment); }
|
||||||
|
|
||||||
|
void
|
||||||
|
do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override
|
||||||
|
{ upstream->deallocate(p, bytes, alignment); }
|
||||||
|
|
||||||
|
bool
|
||||||
|
do_is_equal(const memory_resource& r) const noexcept override
|
||||||
|
{ return &r == this; }
|
||||||
|
|
||||||
|
std::pmr::memory_resource* upstream = std::pmr::get_default_resource();
|
||||||
|
};
|
||||||
|
|
||||||
|
nullable_memory_resource test_mr;
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
void* p1 = r.allocate(8, 1);
|
||||||
|
VERIFY( p1 != nullptr );
|
||||||
|
std::memset(p1, 0xff, 8);
|
||||||
|
test_mr.upstream = nullptr;
|
||||||
|
void* p2 = r.allocate(8, 1); //should not need to replenish
|
||||||
|
VERIFY( p2 != nullptr );
|
||||||
|
VERIFY( p2 != p1 );
|
||||||
|
std::memset(p1, 0xff, 8);
|
||||||
|
r.deallocate(p1, 8, 1); // should not use upstream
|
||||||
|
r.deallocate(p2, 8, 1); // should not use upstream
|
||||||
|
|
||||||
|
// Destructor will return memory upstream, so restore the upstream resource:
|
||||||
|
test_mr.upstream = std::pmr::get_default_resource();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test03()
|
||||||
|
{
|
||||||
|
__gnu_test::memory_resource test_mr;
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r({10, 16}, &test_mr);
|
||||||
|
std::size_t largest_pool = r.options().largest_required_pool_block;
|
||||||
|
void* p1 = r.allocate(2 * largest_pool);
|
||||||
|
VERIFY( p1 != nullptr );
|
||||||
|
const std::size_t n = test_mr.number_of_active_allocations();
|
||||||
|
// Allocation of pools + allocation of pmr::vector + oversize allocation:
|
||||||
|
VERIFY( n >= 1 );
|
||||||
|
std::memset(p1, 0xff, 2 * largest_pool);
|
||||||
|
void* p2 = r.allocate(3 * largest_pool);
|
||||||
|
VERIFY( p2 != nullptr );
|
||||||
|
VERIFY( p2 != p1 );
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n + 1 );
|
||||||
|
std::memset(p2, 0xff, 3 * largest_pool);
|
||||||
|
r.deallocate(p1, 2 * largest_pool);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n );
|
||||||
|
r.deallocate(p2, 3 * largest_pool);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == n - 1 );
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r({16, 16}, &test_mr);
|
||||||
|
(void) r.allocate(2);
|
||||||
|
(void) r.allocate(8);
|
||||||
|
(void) r.allocate(16);
|
||||||
|
(void) r.allocate(2);
|
||||||
|
(void) r.allocate(8);
|
||||||
|
(void) r.allocate(16);
|
||||||
|
(void) r.allocate(2 * r.options().largest_required_pool_block);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() != 0 );
|
||||||
|
// Destructor calls release()
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test04()
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r({256, 256});
|
||||||
|
// Check alignment
|
||||||
|
void* p1 = r.allocate(2, 64);
|
||||||
|
VERIFY( (std::uintptr_t)p1 % 64 == 0 );
|
||||||
|
void* p2 = r.allocate(2, 128);
|
||||||
|
VERIFY( (std::uintptr_t)p2 % 128 == 0 );
|
||||||
|
void* p3 = r.allocate(2, 256);
|
||||||
|
VERIFY( (std::uintptr_t)p3 % 256 == 0 );
|
||||||
|
const std::size_t largest_pool = r.options().largest_required_pool_block;
|
||||||
|
void* p4 = r.allocate(2 * largest_pool, 1024);
|
||||||
|
VERIFY( (std::uintptr_t)p4 % 1024 == 0 );
|
||||||
|
r.deallocate(p1, 2, 64);
|
||||||
|
r.deallocate(p2, 2, 128);
|
||||||
|
r.deallocate(p3, 2, 256);
|
||||||
|
r.deallocate(p4, 2 * largest_pool, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
test01();
|
||||||
|
test02();
|
||||||
|
test03();
|
||||||
|
test04();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (C) 2018 Free Software Foundation, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of the GNU ISO C++ Library. This library is free
|
||||||
|
// software; you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation; either version 3, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this library; see the file COPYING3. If not see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// { dg-options "-std=gnu++17" }
|
||||||
|
// { dg-do run { target c++17 } }
|
||||||
|
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <testsuite_hooks.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
test01()
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r1;
|
||||||
|
VERIFY( r1 == r1 );
|
||||||
|
std::pmr::unsynchronized_pool_resource r2;
|
||||||
|
VERIFY( r1 != r2 );
|
||||||
|
VERIFY( r2 != r1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
test01();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (C) 2018 Free Software Foundation, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of the GNU ISO C++ Library. This library is free
|
||||||
|
// software; you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation; either version 3, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this library; see the file COPYING3. If not see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// { dg-options "-std=gnu++17" }
|
||||||
|
// { dg-do run { target c++17 } }
|
||||||
|
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <testsuite_hooks.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
test01()
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r0;
|
||||||
|
const std::pmr::pool_options opts = r0.options();
|
||||||
|
VERIFY( opts.max_blocks_per_chunk != 0 );
|
||||||
|
VERIFY( opts.largest_required_pool_block != 0 );
|
||||||
|
|
||||||
|
std::pmr::unsynchronized_pool_resource r1(opts);
|
||||||
|
auto [max_blocks_per_chunk, largest_required_pool_block ] = r1.options();
|
||||||
|
VERIFY( max_blocks_per_chunk == opts.max_blocks_per_chunk );
|
||||||
|
VERIFY( largest_required_pool_block == opts.largest_required_pool_block );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
test01();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright (C) 2018 Free Software Foundation, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of the GNU ISO C++ Library. This library is free
|
||||||
|
// software; you can redistribute it and/or modify it under the
|
||||||
|
// terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation; either version 3, or (at your option)
|
||||||
|
// any later version.
|
||||||
|
|
||||||
|
// This library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License along
|
||||||
|
// with this library; see the file COPYING3. If not see
|
||||||
|
// <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// { dg-options "-std=gnu++17" }
|
||||||
|
// { dg-do run { target c++17 } }
|
||||||
|
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <testsuite_allocator.h>
|
||||||
|
#include <testsuite_hooks.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
test01()
|
||||||
|
{
|
||||||
|
__gnu_test::memory_resource test_mr;
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
r.release();
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
r.release();
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
(void) r.allocate(1);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() != 0 );
|
||||||
|
r.release();
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
r.release();
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test02()
|
||||||
|
{
|
||||||
|
struct nullable_memory_resource : public std::pmr::memory_resource
|
||||||
|
{
|
||||||
|
void*
|
||||||
|
do_allocate(std::size_t bytes, std::size_t alignment) override
|
||||||
|
{ return upstream->allocate(bytes, alignment); }
|
||||||
|
|
||||||
|
void
|
||||||
|
do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override
|
||||||
|
{ upstream->deallocate(p, bytes, alignment); }
|
||||||
|
|
||||||
|
bool
|
||||||
|
do_is_equal(const memory_resource& r) const noexcept override
|
||||||
|
{ return &r == this; }
|
||||||
|
|
||||||
|
std::pmr::memory_resource* upstream = std::pmr::get_default_resource();
|
||||||
|
};
|
||||||
|
|
||||||
|
nullable_memory_resource test_mr;
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
r.release();
|
||||||
|
test_mr.upstream = nullptr;
|
||||||
|
r.release(); // should not need to call anything through upstream pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
test03()
|
||||||
|
{
|
||||||
|
__gnu_test::memory_resource test_mr;
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
// Destructor calls release()
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r(&test_mr);
|
||||||
|
(void) r.allocate(1);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() != 0 );
|
||||||
|
// Destructor calls release()
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r({10, 16}, &test_mr);
|
||||||
|
(void) r.allocate(2 * r.options().largest_required_pool_block);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() != 0 );
|
||||||
|
// Destructor calls release()
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
{
|
||||||
|
std::pmr::unsynchronized_pool_resource r({16, 16}, &test_mr);
|
||||||
|
(void) r.allocate(2);
|
||||||
|
(void) r.allocate(8);
|
||||||
|
(void) r.allocate(16);
|
||||||
|
(void) r.allocate(2);
|
||||||
|
(void) r.allocate(8);
|
||||||
|
(void) r.allocate(16);
|
||||||
|
(void) r.allocate(2 * r.options().largest_required_pool_block);
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() != 0 );
|
||||||
|
// Destructor calls release()
|
||||||
|
}
|
||||||
|
VERIFY( test_mr.number_of_active_allocations() == 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
test01();
|
||||||
|
test02();
|
||||||
|
test03();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue