STL/stl/inc/memory_resource

757 строки
34 KiB
C++

// memory_resource standard header
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef _MEMORY_RESOURCE_
#define _MEMORY_RESOURCE_
#include <yvals.h>
#if _STL_COMPILER_PREPROCESSOR
#if !_HAS_CXX17
_EMIT_STL_WARNING(STL4038, "The contents of <memory_resource> are available only with C++17 or later.");
#else // ^^^ !_HAS_CXX17 / _HAS_CXX17 vvv
#include <vector>
#include <xbit_ops.h>
#include <xpolymorphic_allocator.h>
#include <xutility>
#ifndef _M_CEE_PURE
#include <mutex>
#endif // !defined(_M_CEE_PURE)
#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new
_STD_BEGIN
namespace pmr {
extern "C" _CRT_SATELLITE_1 memory_resource* __cdecl _Aligned_set_default_resource(memory_resource*) noexcept;
extern "C" _CRT_SATELLITE_1 memory_resource* __cdecl _Unaligned_set_default_resource(memory_resource*) noexcept;
_EXPORT_STD inline memory_resource* set_default_resource(memory_resource* const _Resource) noexcept {
#ifdef __cpp_aligned_new
return _Aligned_set_default_resource(_Resource);
#else // ^^^ defined(__cpp_aligned_new) / !defined(__cpp_aligned_new) vvv
return _Unaligned_set_default_resource(_Resource);
#endif // ^^^ !defined(__cpp_aligned_new) ^^^
}
_EXPORT_STD extern "C" _NODISCARD _CRT_SATELLITE_1 memory_resource* __cdecl null_memory_resource() noexcept;
class _Identity_equal_resource : public memory_resource {
protected:
bool do_is_equal(const memory_resource& _That) const noexcept override {
return this == &_That;
}
};
class _Unaligned_new_delete_resource_impl final
: public _Identity_equal_resource { // implementation for new_delete_resource with /Zc:alignedNew-
void* do_allocate(const size_t _Bytes, const size_t _Align) override {
if (_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
_Xbad_alloc();
}
return ::operator new(_Bytes);
}
void do_deallocate(void* const _Ptr, const size_t _Bytes, size_t) noexcept override /* strengthened */ {
::operator delete(_Ptr, _Bytes);
}
};
extern "C" _CRT_SATELLITE_1 _Unaligned_new_delete_resource_impl* __cdecl _Unaligned_new_delete_resource() noexcept;
#ifdef __cpp_aligned_new
class _Aligned_new_delete_resource_impl final
: public _Identity_equal_resource { // implementation for new_delete_resource with aligned new support
void* do_allocate(const size_t _Bytes, const size_t _Align) override {
if (_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
return ::operator new(_Bytes, align_val_t{_Align});
}
return ::operator new(_Bytes);
}
void do_deallocate(void* const _Ptr, const size_t _Bytes, const size_t _Align) noexcept override
/* strengthened */ {
if (_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) {
return ::operator delete(_Ptr, _Bytes, align_val_t{_Align});
}
::operator delete(_Ptr, _Bytes);
}
};
extern "C" _CRT_SATELLITE_1 _Aligned_new_delete_resource_impl* __cdecl _Aligned_new_delete_resource() noexcept;
_EXPORT_STD _NODISCARD inline memory_resource* new_delete_resource() noexcept {
return _Aligned_new_delete_resource();
}
#else // ^^^ defined(__cpp_aligned_new) / !defined(__cpp_aligned_new) vvv
_EXPORT_STD _NODISCARD inline memory_resource* new_delete_resource() noexcept {
return _Unaligned_new_delete_resource();
}
#endif // ^^^ !defined(__cpp_aligned_new) ^^^
template <class _Tag = void>
struct _Double_link { // base class for intrusive doubly-linked structures
_Double_link* _Next;
_Double_link* _Prev;
};
template <class _Ty, class _Tag = void>
struct _Intrusive_list { // intrusive circular list of _Ty, which must derive from _Double_link<_Tag>
using _Link_type = _Double_link<_Tag>;
_STL_INTERNAL_STATIC_ASSERT(is_base_of_v<_Link_type, _Ty>);
constexpr _Intrusive_list() noexcept { // TRANSITION, VSO-517878
// initialize this list to the empty state
}
_Intrusive_list(const _Intrusive_list&) = delete;
_Intrusive_list& operator=(const _Intrusive_list&) = delete;
static constexpr _Link_type* _As_link(_Ty* const _Ptr) noexcept {
// extract the link from the item denoted by _Ptr
return static_cast<_Link_type*>(_Ptr);
}
static constexpr _Ty* _As_item(_Link_type* const _Ptr) noexcept { // get the item whose link is denoted by _Ptr
return static_cast<_Ty*>(_Ptr);
}
constexpr void _Push_front(_Ty* const _Item) noexcept { // insert _Item at the head of this list
const auto _Ptr = static_cast<_Link_type*>(_Item);
_Ptr->_Next = _Head._Next;
_Head._Next->_Prev = _Ptr;
_Ptr->_Prev = &_Head;
_Head._Next = _Ptr;
}
static constexpr void _Remove(_Ty* const _Item) noexcept { // unlink _Item from this list
const auto _Ptr = static_cast<_Link_type*>(_Item);
_Ptr->_Next->_Prev = _Ptr->_Prev;
_Ptr->_Prev->_Next = _Ptr->_Next;
}
constexpr void _Clear() noexcept { // make this list empty
_Head._Next = &_Head;
_Head._Prev = &_Head;
}
_Link_type _Head{&_Head, &_Head};
};
template <class _Tag = void>
struct _Single_link { // base class for intrusive singly-linked structures
_Single_link* _Next;
};
template <class _Ty, class _Tag = void>
struct _Intrusive_stack { // intrusive stack of _Ty, which must derive from _Single_link<_Tag>
using _Link_type = _Single_link<_Tag>;
_STL_INTERNAL_STATIC_ASSERT(is_base_of_v<_Link_type, _Ty>);
constexpr _Intrusive_stack() noexcept = default;
constexpr _Intrusive_stack(_Intrusive_stack&& _That) noexcept : _Head{_That._Head} {
_That._Head = nullptr;
}
constexpr _Intrusive_stack& operator=(_Intrusive_stack&& _That) noexcept {
_Head = _That._Head;
_That._Head = nullptr;
return *this;
}
static constexpr _Link_type* _As_link(_Ty* const _Ptr) noexcept {
return static_cast<_Link_type*>(_Ptr);
}
static constexpr _Ty* _As_item(_Link_type* const _Ptr) noexcept {
return static_cast<_Ty*>(_Ptr);
}
constexpr bool _Empty() const noexcept {
return _Head == nullptr;
}
constexpr _Ty* _Top() const noexcept {
return _As_item(_Head);
}
constexpr void _Push(_Ty* const _Item) noexcept {
const auto _Ptr = _As_link(_Item);
_Ptr->_Next = _Head;
_Head = _Ptr;
}
constexpr _Ty* _Pop() noexcept { // pre: _Head != nullptr
const auto _Result = _Head;
_Head = _Head->_Next;
return _As_item(_Result);
}
constexpr void _Remove(_Ty* const _Item) noexcept {
const auto _Ptr = _As_link(_Item);
for (_Link_type** _Pnext = &_Head; *_Pnext; _Pnext = &(*_Pnext)->_Next) {
if (*_Pnext == _Ptr) {
*_Pnext = _Ptr->_Next;
break;
}
}
}
_Link_type* _Head = nullptr;
};
_EXPORT_STD struct pool_options {
size_t max_blocks_per_chunk = 0;
size_t largest_required_pool_block = 0;
};
inline void _Check_alignment(void* const _Ptr, const size_t _Align) noexcept {
_STL_ASSERT((reinterpret_cast<uintptr_t>(_Ptr) & (_Align - 1)) == 0,
"Upstream resource did not respect alignment requirement.");
(void) _Ptr;
(void) _Align;
}
_EXPORT_STD struct unsynchronized_pool_resource : _Identity_equal_resource {
unsynchronized_pool_resource() noexcept { // initialize pool with default options and default upstream
_Setup_options();
}
unsynchronized_pool_resource(
const pool_options& _Opts, memory_resource* const _Resource) noexcept // strengthened
: _Options(_Opts), _Pools{_Resource} { // initialize pool with options _Opts and upstream _Resource
_STL_ASSERT(_Resource, "Upstream memory resource must be a valid resource (N4950 [mem.res.pool.ctor]/1)");
_Setup_options();
}
explicit unsynchronized_pool_resource(memory_resource* const _Resource) noexcept // strengthened
: _Pools{_Resource} { // initialize pool with default options and upstream _Resource
_STL_ASSERT(_Resource, "Upstream memory resource must be a valid resource (N4950 [mem.res.pool.ctor]/1)");
_Setup_options();
}
explicit unsynchronized_pool_resource(const pool_options& _Opts) noexcept // strengthened
: _Options(_Opts) { // initialize pool with options _Opts and default upstream
_Setup_options();
}
unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete;
unsynchronized_pool_resource& operator=(const unsynchronized_pool_resource&) = delete;
~unsynchronized_pool_resource() noexcept override {
// destroy this pool resource, releasing all allocations back upstream
release();
}
_NODISCARD memory_resource* upstream_resource() const noexcept /* strengthened */ {
// retrieve this pool resource's upstream resource
return _Pools.get_allocator().resource();
}
_NODISCARD pool_options options() const noexcept /* strengthened */ {
// retrieve the adjusted/actual option values
return _Options;
}
void release() noexcept /* strengthened */ {
// release all allocations back upstream
for (auto& _Al : _Pools) {
_Al._Clear(*this);
}
_Pools.clear();
_Pools.shrink_to_fit();
auto _Ptr = _Chunks._Head._Next;
_Chunks._Clear();
memory_resource* const _Resource = upstream_resource();
while (_Ptr != &_Chunks._Head) {
const auto _Chunk = _Chunks._As_item(_Ptr);
_Ptr = _Ptr->_Next;
_Resource->deallocate(_Chunk->_Base_address(), _Chunk->_Size, _Chunk->_Align);
}
}
protected:
void* do_allocate(size_t _Bytes, const size_t _Align) override {
// allocate a block from the appropriate pool, or directly from upstream if too large
if (_Bytes <= _Options.largest_required_pool_block) {
auto _Result = _Find_pool(_Bytes, _Align);
if (_Result.first == _Pools.end() || _Result.first->_Log_of_size != _Result.second) {
_Result.first = _Pools.emplace(_Result.first, _Result.second);
}
return _Result.first->_Allocate(*this);
}
return _Allocate_oversized(_Bytes, _Align);
}
void do_deallocate(void* const _Ptr, const size_t _Bytes, const size_t _Align) override {
// deallocate a block from the appropriate pool, or directly from upstream if too large
if (_Bytes <= _Options.largest_required_pool_block) {
const auto _Result = _Find_pool(_Bytes, _Align);
if (_Result.first != _Pools.end() && _Result.first->_Log_of_size == _Result.second) {
_Result.first->_Deallocate(*this, _Ptr);
}
} else {
_Deallocate_oversized(_Ptr, _Bytes, _Align);
}
}
private:
struct _Oversized_header : _Double_link<> {
// tracks an allocation that was obtained directly from the upstream resource
size_t _Size;
size_t _Align;
void* _Base_address() const { // headers are stored at the end of the allocated memory block
return const_cast<char*>(reinterpret_cast<const char*>(this + 1) - _Size);
}
};
static_assert(alignof(_Oversized_header) == alignof(void*));
static_assert(sizeof(_Oversized_header) == 4 * sizeof(void*));
static constexpr bool _Prepare_oversized(size_t& _Bytes, size_t& _Align) noexcept {
// adjust size and alignment to allow for an _Oversized_header
_Align = (_STD max)(_Align, alignof(_Oversized_header));
if (_Bytes > SIZE_MAX - sizeof(_Oversized_header) - alignof(_Oversized_header) + 1) {
// no room for header + alignment padding
return false;
}
// adjust _Bytes to the smallest multiple of alignof(_Oversized_header) that is >=
// _Bytes + sizeof(_Oversized_header), which guarantees that the end of the allocated space
// is properly aligned for an _Oversized_header.
_Bytes = (_Bytes + sizeof(_Oversized_header) + alignof(_Oversized_header) - 1)
& ~(alignof(_Oversized_header) - 1);
return true;
}
void* _Allocate_oversized(size_t _Bytes, size_t _Align) {
// allocate a block directly from the upstream resource
if (!_Prepare_oversized(_Bytes, _Align)) { // no room for header + alignment padding
_Xbad_alloc();
}
memory_resource* const _Resource = upstream_resource();
void* const _Ptr = _Resource->allocate(_Bytes, _Align);
_Check_alignment(_Ptr, _Align);
_Oversized_header* const _Hdr = reinterpret_cast<_Oversized_header*>(static_cast<char*>(_Ptr) + _Bytes) - 1;
_Hdr->_Size = _Bytes;
_Hdr->_Align = _Align;
_Chunks._Push_front(_Hdr);
return _Ptr;
}
void _Deallocate_oversized(void* _Ptr, size_t _Bytes, size_t _Align) noexcept {
// deallocate a block directly from the upstream resource
if (!_Prepare_oversized(_Bytes, _Align)) {
// no room for header + alignment padding; this memory WAS NOT allocated by this pool resource
#ifdef _DEBUG
_STL_REPORT_ERROR("Cannot deallocate memory not allocated by this memory pool.");
#endif // defined(_DEBUG)
}
_Oversized_header* _Hdr = reinterpret_cast<_Oversized_header*>(static_cast<char*>(_Ptr) + _Bytes) - 1;
_STL_ASSERT(_Hdr->_Size == _Bytes && _Hdr->_Align == _Align,
"Cannot deallocate memory not allocated by this memory pool.");
_Chunks._Remove(_Hdr);
upstream_resource()->deallocate(_Ptr, _Bytes, _Align);
}
struct _Pool { // manager for a collection of chunks comprised of blocks of a single size
struct _Chunk : _Single_link<> {
// a memory allocation consisting of a number of fixed-size blocks to be parceled out
_Intrusive_stack<_Single_link<>> _Free_blocks{}; // list of free blocks
size_t _Free_count; // # of unallocated blocks
size_t _Capacity; // total # of blocks
char* _Base; // address of first block
size_t _Next_available = 0; // index of first never-allocated block
size_t _Id; // unique identifier; increasing order of allocation
_Chunk(_Pool& _Al, void* const _Base_, const size_t _Capacity_) noexcept
: _Free_count{_Capacity_}, _Capacity{_Capacity_}, _Base{static_cast<char*>(_Base_)},
_Id{_Al._All_chunks._Empty() ? 0 : _Al._All_chunks._Top()->_Id + 1} {
// initialize a chunk of _Capacity blocks, all initially free
}
_Chunk(const _Chunk&) = delete;
_Chunk& operator=(const _Chunk&) = delete;
};
_Chunk* _Unfull_chunk = nullptr; // largest _Chunk with free blocks
_Intrusive_stack<_Chunk> _All_chunks{}; // all chunks (ordered by decreasing _Id)
size_t _Next_capacity = _Default_next_capacity; // # of blocks to allocate in next _Chunk
// in (1, (PTRDIFF_MAX - sizeof(_Chunk)) >> _Log_of_size]
size_t _Block_size; // size of allocated blocks
size_t _Log_of_size; // _Block_size == 1 << _Log_of_size
_Chunk* _Empty_chunk = nullptr; // only _Chunk with all free blocks
static constexpr size_t _Default_next_capacity = 4;
static_assert(_Default_next_capacity > 1);
explicit _Pool(const size_t _Log_of_size_) noexcept
: _Block_size{size_t{1} << _Log_of_size_}, _Log_of_size{_Log_of_size_} {
// initialize a pool that manages blocks of the indicated size
}
_Pool(_Pool&& _That) noexcept
: _Unfull_chunk{_STD exchange(_That._Unfull_chunk, nullptr)}, _All_chunks{_STD move(_That._All_chunks)},
_Next_capacity{_STD exchange(_That._Next_capacity, _Default_next_capacity)},
_Block_size{_That._Block_size}, _Log_of_size{_That._Log_of_size},
_Empty_chunk{_STD exchange(_That._Empty_chunk, nullptr)} {}
_Pool& operator=(_Pool&& _That) noexcept {
_Unfull_chunk = _STD exchange(_That._Unfull_chunk, nullptr);
_All_chunks = _STD move(_That._All_chunks);
_Next_capacity = _STD exchange(_That._Next_capacity, _Default_next_capacity);
_Block_size = _That._Block_size;
_Log_of_size = _That._Log_of_size;
_Empty_chunk = _STD exchange(_That._Empty_chunk, nullptr);
return *this;
}
void _Clear(unsynchronized_pool_resource& _Pool_resource) noexcept {
// release all chunks in the pool back upstream
_Intrusive_stack<_Chunk> _Tmp{};
_STD swap(_Tmp, _All_chunks);
memory_resource* const _Resource = _Pool_resource.upstream_resource();
while (!_Tmp._Empty()) {
const auto _Ptr = _Tmp._Pop();
_Resource->deallocate(_Ptr->_Base, _Size_for_capacity(_Ptr->_Capacity), _Block_size);
}
_Unfull_chunk = nullptr;
_Next_capacity = _Default_next_capacity;
_Empty_chunk = nullptr;
}
void* _Allocate(unsynchronized_pool_resource& _Pool_resource) { // allocate a block from this pool
for (;; _Unfull_chunk = _All_chunks._As_item(_Unfull_chunk->_Next)) {
if (!_Unfull_chunk) {
_Increase_capacity(_Pool_resource);
} else if (!_Unfull_chunk->_Free_blocks._Empty()) {
if (_Unfull_chunk == _Empty_chunk) { // this chunk is no longer empty
_Empty_chunk = nullptr;
}
--_Unfull_chunk->_Free_count;
return _Unfull_chunk->_Free_blocks._Pop();
}
if (_Unfull_chunk->_Next_available < _Unfull_chunk->_Capacity) {
if (_Unfull_chunk == _Empty_chunk) { // this chunk is no longer empty
_Empty_chunk = nullptr;
}
--_Unfull_chunk->_Free_count;
char* const _Block = _Unfull_chunk->_Base + _Unfull_chunk->_Next_available * _Block_size;
++_Unfull_chunk->_Next_available;
*(reinterpret_cast<_Chunk**>(_Block + _Block_size) - 1) = _Unfull_chunk;
return _Block;
}
}
}
void _Deallocate(unsynchronized_pool_resource& _Pool_resource, void* const _Ptr) noexcept {
// return a block to this pool
_Chunk* _Current = *(reinterpret_cast<_Chunk**>(static_cast<char*>(_Ptr) + _Block_size) - 1);
_Current->_Free_blocks._Push(::new (_Ptr) _Single_link<>);
if (_Current->_Free_count++ == 0) {
// prefer to allocate from newer/larger chunks...
if (!_Unfull_chunk || _Unfull_chunk->_Id < _Current->_Id) {
_Unfull_chunk = _Current;
}
return;
}
if (_Current->_Free_count < _Current->_Capacity) {
return;
}
if (!_Empty_chunk) {
_Empty_chunk = _Current;
return;
}
// ...and release older/smaller chunks to keep the list lengths short.
if (_Empty_chunk->_Id < _Current->_Id) {
_STD swap(_Current, _Empty_chunk);
}
_All_chunks._Remove(_Current);
_Pool_resource.upstream_resource()->deallocate(
_Current->_Base, _Size_for_capacity(_Current->_Capacity), _Block_size);
}
size_t _Size_for_capacity(const size_t _Capacity) const noexcept {
// return the size of a chunk that holds _Capacity blocks
return (_Capacity << _Log_of_size) + sizeof(_Chunk);
}
void _Increase_capacity(unsynchronized_pool_resource& _Pool_resource) {
// this pool has no free blocks; get a new chunk from upstream
if (_Next_capacity > _Pool_resource._Options.max_blocks_per_chunk) {
// This is a fresh pool, _Next_capacity hasn't yet been bounded by max_blocks_per_chunk:
_Next_capacity = _Pool_resource._Options.max_blocks_per_chunk;
}
const size_t _Size = _Size_for_capacity(_Next_capacity);
memory_resource* const _Resource = _Pool_resource.upstream_resource();
void* const _Ptr = _Resource->allocate(_Size, _Block_size);
_Check_alignment(_Ptr, _Block_size);
void* const _Tmp = static_cast<char*>(_Ptr) + _Size - sizeof(_Chunk);
_Unfull_chunk = ::new (_Tmp) _Chunk{*this, _Ptr, _Next_capacity};
_Empty_chunk = _Unfull_chunk;
_All_chunks._Push(_Unfull_chunk);
// scale _Next_capacity by 2, saturating so that _Size_for_capacity(_Next_capacity) cannot overflow
_Next_capacity =
(_STD min)(_Next_capacity << 1, (_STD min)((PTRDIFF_MAX - sizeof(_Chunk)) >> _Log_of_size,
_Pool_resource._Options.max_blocks_per_chunk));
}
};
void _Setup_options() noexcept { // configure pool options
constexpr auto _Max_blocks_per_chunk_limit = static_cast<size_t>(PTRDIFF_MAX);
constexpr auto _Largest_required_pool_block_limit =
static_cast<size_t>((PTRDIFF_MAX >> 4) + 1); // somewhat arbitrary power of 2
static_assert(_Is_pow_2(_Largest_required_pool_block_limit));
if (_Options.max_blocks_per_chunk - 1 >= _Max_blocks_per_chunk_limit) {
_Options.max_blocks_per_chunk = _Max_blocks_per_chunk_limit;
}
if (_Options.largest_required_pool_block - 1 < sizeof(void*)) {
_Options.largest_required_pool_block = sizeof(void*);
} else if (_Options.largest_required_pool_block - 1 >= _Largest_required_pool_block_limit) {
_Options.largest_required_pool_block = _Largest_required_pool_block_limit;
} else {
_Options.largest_required_pool_block = static_cast<size_t>(1)
<< _Ceiling_of_log_2(_Options.largest_required_pool_block);
}
}
pair<pmr::vector<_Pool>::iterator, unsigned char> _Find_pool(
const size_t _Bytes, const size_t _Align) noexcept {
// find the pool from which to allocate a block with size _Bytes and alignment _Align
const size_t _Size = (_STD max)(_Bytes + sizeof(void*), _Align);
const auto _Log_of_size = static_cast<unsigned char>(_Ceiling_of_log_2(_Size));
return {_STD lower_bound(_Pools.begin(), _Pools.end(), _Log_of_size,
[](const _Pool& _Al, const unsigned char _Log)
_STATIC_CALL_OPERATOR { return _Al._Log_of_size < _Log; }),
_Log_of_size};
}
pool_options _Options{}; // parameters that control the behavior of this pool resource
_Intrusive_list<_Oversized_header> _Chunks{}; // list of oversized allocations obtained directly from upstream
pmr::vector<_Pool> _Pools{}; // pools in order of increasing block size
};
#ifndef _M_CEE_PURE
_EXPORT_STD class synchronized_pool_resource : public unsynchronized_pool_resource {
public:
using unsynchronized_pool_resource::unsynchronized_pool_resource;
void release() noexcept /* strengthened */ {
lock_guard<mutex> _Guard{_Mtx};
unsynchronized_pool_resource::release();
}
protected:
void* do_allocate(const size_t _Bytes, const size_t _Align) override {
lock_guard<mutex> _Guard{_Mtx};
return unsynchronized_pool_resource::do_allocate(_Bytes, _Align);
}
void do_deallocate(void* const _Ptr, const size_t _Bytes, const size_t _Align) override {
lock_guard<mutex> _Guard{_Mtx};
unsynchronized_pool_resource::do_deallocate(_Ptr, _Bytes, _Align);
}
private:
mutable mutex _Mtx;
};
#endif // !defined(_M_CEE_PURE)
_EXPORT_STD class monotonic_buffer_resource : public _Identity_equal_resource {
public:
explicit monotonic_buffer_resource(memory_resource* const _Upstream) noexcept // strengthened
: _Resource{_Upstream} {} // initialize this resource with upstream
monotonic_buffer_resource(const size_t _Initial_size, memory_resource* const _Upstream) noexcept // strengthened
: _Next_buffer_size(_Round(_Initial_size)), _Resource{_Upstream} {
// initialize this resource with upstream and initial allocation size
}
monotonic_buffer_resource(void* const _Buffer, const size_t _Buffer_size,
memory_resource* const _Upstream) noexcept // strengthened
: _Current_buffer(_Buffer), _Space_available(_Buffer_size),
_Next_buffer_size(_Buffer_size ? _Scale(_Buffer_size) : _Min_allocation), _Resource{_Upstream} {
// initialize this resource with upstream and initial buffer
}
monotonic_buffer_resource() = default;
explicit monotonic_buffer_resource(const size_t _Initial_size) noexcept // strengthened
: _Next_buffer_size(_Round(_Initial_size)) {} // initialize this resource with initial allocation size
monotonic_buffer_resource(void* const _Buffer, const size_t _Buffer_size) noexcept // strengthened
: _Current_buffer(_Buffer), _Space_available(_Buffer_size),
_Next_buffer_size(_Buffer_size ? _Scale(_Buffer_size) : _Min_allocation) {
// initialize this resource with initial buffer
}
~monotonic_buffer_resource() noexcept override {
release();
}
monotonic_buffer_resource(const monotonic_buffer_resource&) = delete;
monotonic_buffer_resource& operator=(const monotonic_buffer_resource&) = delete;
void release() noexcept /* strengthened */ {
if (_Chunks._Empty()) {
// nothing to release; potentially continues to use an initial block provided at construction
return;
}
_Current_buffer = nullptr;
_Space_available = 0;
// unscale _Next_buffer_size so the next allocation will be the same size as the most recent allocation
// (keep synchronized with monotonic_buffer_resource::_Scale)
const size_t _Unscaled = (_Next_buffer_size / 3 * 2 + alignof(_Header) - 1) & _Max_allocation;
_Next_buffer_size = (_STD max)(_Unscaled, _Min_allocation);
_Intrusive_stack<_Header> _Tmp{};
_STD swap(_Tmp, _Chunks);
while (!_Tmp._Empty()) {
const auto _Ptr = _Tmp._Pop();
_Resource->deallocate(_Ptr->_Base_address(), _Ptr->_Size, _Ptr->_Align);
}
}
_NODISCARD memory_resource* upstream_resource() const noexcept /* strengthened */ {
// retrieve the upstream resource
return _Resource;
}
protected:
void* do_allocate(const size_t _Bytes, const size_t _Align) override {
// allocate from the current buffer or a new larger buffer from upstream
if (!_STD align(_Align, _Bytes, _Current_buffer, _Space_available)) {
_Increase_capacity(_Bytes, _Align);
}
void* const _Result = _Current_buffer;
_Current_buffer = static_cast<char*>(_Current_buffer) + _Bytes;
_Space_available -= _Bytes;
return _Result;
}
void do_deallocate(void*, size_t, size_t) override {} // nothing to do
private:
struct _Header : _Single_link<> { // track the size and alignment of an allocation from upstream
size_t _Size;
size_t _Align;
_Header(const size_t _Size_, const size_t _Align_) : _Size{_Size_}, _Align{_Align_} {}
void* _Base_address() const { // header is stored at the end of the allocated memory block
return const_cast<char*>(reinterpret_cast<const char*>(this + 1) - _Size);
}
};
static constexpr size_t _Min_allocation = 2 * sizeof(_Header);
static constexpr size_t _Max_allocation = 0 - alignof(_Header);
static constexpr size_t _Round(const size_t _Size) noexcept {
// return the smallest multiple of alignof(_Header) greater than _Size,
// clamped to the range [_Min_allocation, _Max_allocation]
if (_Size < _Min_allocation) {
return _Min_allocation;
}
if (_Size >= _Max_allocation) {
return _Max_allocation;
}
// Since _Max_allocation == -alignof(_Header), _Size < _Max_allocation implies that
// (_Size + alignof(_Header) - 1) does not overflow.
return (_Size + alignof(_Header) - 1) & _Max_allocation;
}
static constexpr size_t _Scale(const size_t _Size) noexcept {
// scale _Size by 1.5, rounding up to a multiple of alignof(_Header), saturating to _Max_allocation
// (keep synchronized with monotonic_buffer_resource::release)
#pragma warning(push)
#pragma warning(disable : 26450) // TRANSITION, VSO-1828677
constexpr auto _Max_size = (_Max_allocation - alignof(_Header) + 1) / 3 * 2;
#pragma warning(pop)
if (_Size >= _Max_size) {
return _Max_allocation;
}
return (_Size + (_Size + 1) / 2 + alignof(_Header) - 1) & _Max_allocation;
}
void _Increase_capacity(const size_t _Bytes, const size_t _Align) { // obtain a new buffer from upstream
if (_Bytes > _Max_allocation - sizeof(_Header)) {
_Xbad_alloc();
}
size_t _New_size = _Next_buffer_size;
if (_New_size < _Bytes + sizeof(_Header)) {
_New_size = (_Bytes + sizeof(_Header) + alignof(_Header) - 1) & _Max_allocation;
}
const size_t _New_align = (_STD max)(alignof(_Header), _Align);
void* _New_buffer = _Resource->allocate(_New_size, _New_align);
_Check_alignment(_New_buffer, _New_align);
_Current_buffer = _New_buffer;
_Space_available = _New_size - sizeof(_Header);
_New_buffer = static_cast<char*>(_New_buffer) + _Space_available;
_Chunks._Push(::new (_New_buffer) _Header{_New_size, _New_align});
_Next_buffer_size = _Scale(_New_size);
}
void* _Current_buffer = nullptr; // current memory block to parcel out to callers
size_t _Space_available = 0; // space remaining in current block
size_t _Next_buffer_size = _Min_allocation; // size of next block to allocate from upstream
_Intrusive_stack<_Header> _Chunks{}; // list of memory blocks allocated from upstream
memory_resource* _Resource = _STD pmr::get_default_resource(); // upstream resource from which to allocate
};
} // namespace pmr
_STD_END
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)
#endif // ^^^ _HAS_CXX17 ^^^
#endif // _STL_COMPILER_PREPROCESSOR
#endif // _MEMORY_RESOURCE_