diff --git a/stl/inc/deque b/stl/inc/deque index 80b4571d1a2..9eb2cfd6f44 100644 --- a/stl/inc/deque +++ b/stl/inc/deque @@ -1557,12 +1557,13 @@ private: _Newsize *= 2; } - _Count = _Newsize - _Mapsize(); size_type _Myboff = _Myoff() / _Block_size; - _Mapptr _Newmap = _Almap.allocate(_Mapsize() + _Count); + _Mapptr _Newmap = _Allocate_at_least_helper(_Almap, _Newsize); _Mapptr _Myptr = _Newmap + _Myboff; + _Count = _Newsize - _Mapsize(); + _Myptr = _STD uninitialized_copy(_Map() + _Myboff, _Map() + _Mapsize(), _Myptr); // copy initial to end if (_Myboff <= _Count) { // increment greater than offset of initial block _Myptr = _STD uninitialized_copy(_Map(), _Map() + _Myboff, _Myptr); // copy rest of old diff --git a/stl/inc/sstream b/stl/inc/sstream index 15584a80853..222dd025793 100644 --- a/stl/inc/sstream +++ b/stl/inc/sstream @@ -261,7 +261,7 @@ protected: return _Traits::eof(); } - const auto _Newptr = _Unfancy(_Al.allocate(_Newsize)); + const auto _Newptr = _Unfancy(_Allocate_at_least_helper(_Al, _Newsize)); _Traits::copy(_Newptr, _Oldptr, _Oldsize); const auto _New_pnext = _Newptr + _Oldsize; @@ -430,7 +430,7 @@ protected: return pos_type{_Off}; } - void _Init(const _Elem* _Ptr, _Mysize_type _Count, int _State) { + void _Init(const _Elem* _Ptr, const _Mysize_type _Count, int _State) { // initialize buffer to [_Ptr, _Ptr + _Count), set state _State &= ~_From_rvalue; @@ -440,9 +440,10 @@ protected: if (_Count != 0 && (_State & (_Noread | _Constant)) != (_Noread | _Constant)) { // finite buffer that can be read or written, set it up - const auto _Pnew = _Unfancy(_Al.allocate(_Count)); + _Mysize_type _Newsize = _Count; + const auto _Pnew = _Unfancy(_Allocate_at_least_helper(_Al, _Newsize)); _Traits::copy(_Pnew, _Ptr, _Count); - _Seekhigh = _Pnew + _Count; + _Seekhigh = _Pnew + _Newsize; if (!(_State & _Noread)) { _Mysb::setg(_Pnew, _Pnew, _Seekhigh); // setup read buffer diff --git a/stl/inc/syncstream b/stl/inc/syncstream index ec106eeff8a..969d756a051 100644 --- a/stl/inc/syncstream +++ b/stl/inc/syncstream @@ -120,10 +120,10 @@ public: if (_Al != _Right_al) { _Tidy(); - const _Size_type _Right_buf_size = _Right._Get_buffer_size(); + _Size_type _Right_buf_size = _Right._Get_buffer_size(); const _Size_type _Right_data_size = _Right._Get_data_size(); - _Elem* const _New_ptr = _Unfancy(_Al.allocate(_Right_buf_size)); + _Elem* const _New_ptr = _Unfancy(_Allocate_at_least_helper(_Al, _Right_buf_size)); _Traits::copy(_New_ptr, _Right.pbase(), _Right_data_size); streambuf_type::setp(_New_ptr, _New_ptr + _Right_data_size, _New_ptr + _Right_buf_size); @@ -217,11 +217,11 @@ protected: return _Traits::eof(); } - const _Size_type _New_capacity = _Calculate_growth(_Buf_size, _Buf_size + 1, _Max_allocation); + _Size_type _New_capacity = _Calculate_growth(_Buf_size, _Buf_size + 1, _Max_allocation); _Elem* const _Old_ptr = streambuf_type::pbase(); const _Size_type _Old_data_size = _Get_data_size(); - _Elem* const _New_ptr = _Unfancy(_Al.allocate(_New_capacity)); + _Elem* const _New_ptr = _Unfancy(_Allocate_at_least_helper(_Al, _New_capacity)); _Traits::copy(_New_ptr, _Old_ptr, _Old_data_size); if (0 < _Buf_size) { _Al.deallocate(_Refancy<_Pointer>(_Old_ptr), _Buf_size); @@ -237,8 +237,9 @@ private: static constexpr _Size_type _Min_size = 32; // constant for minimum buffer size void _Init() { - _Elem* const _New_ptr = _Unfancy(_Getal().allocate(_Min_size)); - streambuf_type::setp(_New_ptr, _New_ptr + _Min_size); + _Size_type _New_capacity = _Min_size; + _Elem* const _New_ptr = _Unfancy(_Allocate_at_least_helper(_Getal(), _New_capacity)); + streambuf_type::setp(_New_ptr, _New_ptr + _New_capacity); } void _Tidy() noexcept { diff --git a/stl/inc/vector b/stl/inc/vector index 5b05f4afa40..c6552845a31 100644 --- a/stl/inc/vector +++ b/stl/inc/vector @@ -825,10 +825,10 @@ private: _Xlength(); } - const size_type _Newsize = _Oldsize + 1; - const size_type _Newcapacity = _Calculate_growth(_Newsize); + const size_type _Newsize = _Oldsize + 1; + size_type _Newcapacity = _Calculate_growth(_Newsize); - const pointer _Newvec = _Al.allocate(_Newcapacity); + const pointer _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); const pointer _Constructed_last = _Newvec + _Whereoff + 1; pointer _Constructed_first = _Constructed_last; @@ -912,10 +912,10 @@ private: _Xlength(); } - const size_type _Newsize = _Oldsize + _Count; - const size_type _Newcapacity = _Calculate_growth(_Newsize); + const size_type _Newsize = _Oldsize + _Count; + size_type _Newcapacity = _Calculate_growth(_Newsize); - const pointer _Newvec = _Al.allocate(_Newcapacity); + const pointer _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); const pointer _Constructed_last = _Newvec + _Oldsize + _Count; pointer _Constructed_first = _Constructed_last; @@ -1033,10 +1033,10 @@ public: _Xlength(); } - const size_type _Newsize = _Oldsize + _Count; - const size_type _Newcapacity = _Calculate_growth(_Newsize); + const size_type _Newsize = _Oldsize + _Count; + size_type _Newcapacity = _Calculate_growth(_Newsize); - const pointer _Newvec = _Al.allocate(_Newcapacity); + const pointer _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); const pointer _Constructed_last = _Newvec + _Whereoff + _Count; pointer _Constructed_first = _Constructed_last; @@ -1128,10 +1128,10 @@ private: _Xlength(); } - const size_type _Newsize = _Oldsize + _Count; - const size_type _Newcapacity = _Calculate_growth(_Newsize); + const size_type _Newsize = _Oldsize + _Count; + size_type _Newcapacity = _Calculate_growth(_Newsize); - const pointer _Newvec = _Al.allocate(_Newcapacity); + const pointer _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); const auto _Whereoff = static_cast(_Whereptr - _Oldfirst); const pointer _Constructed_last = _Newvec + _Whereoff + _Count; pointer _Constructed_first = _Constructed_last; @@ -1518,10 +1518,10 @@ private: pointer& _Myfirst = _My_data._Myfirst; pointer& _Mylast = _My_data._Mylast; - const auto _Oldsize = static_cast(_Mylast - _Myfirst); - const size_type _Newcapacity = _Calculate_growth(_Newsize); + const auto _Oldsize = static_cast(_Mylast - _Myfirst); + size_type _Newcapacity = _Calculate_growth(_Newsize); - const pointer _Newvec = _Al.allocate(_Newcapacity); + const pointer _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); const pointer _Appended_first = _Newvec + _Oldsize; pointer _Appended_last = _Appended_first; @@ -1598,7 +1598,10 @@ public: } private: - _CONSTEXPR20 void _Reallocate_exactly(const size_type _Newcapacity) { + enum class _Reallocation_policy { _At_least, _Exactly }; + + template <_Reallocation_policy _Policy> + _CONSTEXPR20 void _Reallocate(size_type& _Newcapacity) { // set capacity to _Newcapacity (without geometric growth), provide strong guarantee auto& _Al = _Getal(); auto& _My_data = _Mypair._Myval2; @@ -1607,7 +1610,13 @@ private: const auto _Size = static_cast(_Mylast - _Myfirst); - const pointer _Newvec = _Al.allocate(_Newcapacity); + pointer _Newvec; + if constexpr (_Policy == _Reallocation_policy::_At_least) { + _Newvec = _Allocate_at_least_helper(_Al, _Newcapacity); + } else { + _STL_INTERNAL_STATIC_ASSERT(_Policy == _Reallocation_policy::_Exactly); + _Newvec = _Al.allocate(_Newcapacity); + } _TRY_BEGIN if constexpr (is_nothrow_move_constructible_v<_Ty> || !is_copy_constructible_v<_Ty>) { @@ -1675,14 +1684,14 @@ private: } public: - _CONSTEXPR20 void reserve(_CRT_GUARDOVERFLOW const size_type _Newcapacity) { + _CONSTEXPR20 void reserve(_CRT_GUARDOVERFLOW size_type _Newcapacity) { // increase capacity to _Newcapacity (without geometric growth), provide strong guarantee if (_Newcapacity > capacity()) { // something to do (reserve() never shrinks) if (_Newcapacity > max_size()) { _Xlength(); } - _Reallocate_exactly(_Newcapacity); + _Reallocate<_Reallocation_policy::_At_least>(_Newcapacity); } } @@ -1694,7 +1703,8 @@ public: if (_Oldfirst == _Oldlast) { _Tidy(); } else { - _Reallocate_exactly(static_cast(_Oldlast - _Oldfirst)); + size_type _Newcapacity = static_cast(_Oldlast - _Oldfirst); + _Reallocate<_Reallocation_policy::_Exactly>(_Newcapacity); } } } @@ -1976,7 +1986,7 @@ private: return _Geometric; // geometric growth is sufficient } - _CONSTEXPR20 void _Buy_raw(const size_type _Newcapacity) { + _CONSTEXPR20 void _Buy_raw(size_type _Newcapacity) { // allocate array with _Newcapacity elements auto& _My_data = _Mypair._Myval2; pointer& _Myfirst = _My_data._Myfirst; @@ -1986,10 +1996,10 @@ private: _STL_INTERNAL_CHECK(!_Myfirst && !_Mylast && !_Myend); // check that *this is tidy _STL_INTERNAL_CHECK(0 < _Newcapacity && _Newcapacity <= max_size()); - const auto _Newvec = _Getal().allocate(_Newcapacity); - _Myfirst = _Newvec; - _Mylast = _Newvec; - _Myend = _Newvec + _Newcapacity; + const pointer _Newvec = _Allocate_at_least_helper(_Getal(), _Newcapacity); + _Myfirst = _Newvec; + _Mylast = _Newvec; + _Myend = _Newvec + _Newcapacity; } _CONSTEXPR20 void _Buy_nonzero(const size_type _Newcapacity) { diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 6c8992f47a2..0260da13c88 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2174,6 +2174,36 @@ _NODISCARD constexpr bool _Allocators_equal(const _Alloc& _Lhs, const _Alloc& _R } } +#if _HAS_CXX23 +template +inline constexpr bool _Has_member_from_primary = false; +template +inline constexpr bool _Has_member_from_primary<_Ty, void_t> = true; + +// Avoid using allocate_at_least when the allocator publicly derives from std::allocator: +// "old" allocators might hide allocate and deallocate but fail to hide allocate_at_least. +// Also avoid using allocate_at_least from std::allocator itself because it currently doesn't do anything useful. +template +inline constexpr bool _Should_allocate_at_least = + !_Has_member_from_primary<_Alloc> + && _Has_member_allocate_at_least<_Alloc, typename allocator_traits<_Alloc>::size_type>; +#endif // _HAS_CXX23 + +template +_NODISCARD_RAW_PTR_ALLOC _CONSTEXPR20 typename allocator_traits<_Alloc>::pointer _Allocate_at_least_helper( + _Alloc& _Al, _CRT_GUARDOVERFLOW typename allocator_traits<_Alloc>::size_type& _Count) { +#if _HAS_CXX23 + if constexpr (_Should_allocate_at_least<_Alloc>) { + auto [_Ptr, _Allocated] = _Al.allocate_at_least(_Count); + _Count = _Allocated; + return _Ptr; + } else +#endif // _HAS_CXX23 + { + return _Al.allocate(_Count); + } +} + _EXPORT_STD template _NODISCARD_REMOVE_ALG _CONSTEXPR20 _FwdIt remove(_FwdIt _First, const _FwdIt _Last, const _Ty& _Val) { // remove each matching _Val diff --git a/stl/inc/xstring b/stl/inc/xstring index b5ab9511a73..b034af94254 100644 --- a/stl/inc/xstring +++ b/stl/inc/xstring @@ -2647,9 +2647,11 @@ private: return; } - _My_data._Myres = _BUF_SIZE - 1; - const size_type _New_capacity = _Calculate_growth(_Count); - const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + _My_data._Myres = _BUF_SIZE - 1; + size_type _New_capacity = _Calculate_growth(_Count); + ++_New_capacity; + const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws + --_New_capacity; _Construct_in_place(_My_data._Bx._Ptr, _New_ptr); _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); @@ -2691,8 +2693,10 @@ private: } if (_Count >= _BUF_SIZE) { - const size_type _New_capacity = _Calculate_growth(_Count); - const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + size_type _New_capacity = _Calculate_growth(_Count); + ++_New_capacity; + const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws + --_New_capacity; _Construct_in_place(_My_data._Bx._Ptr, _New_ptr); _My_data._Myres = _New_capacity; @@ -2708,9 +2712,11 @@ private: _Xlen_string(); // result too long } - const auto _Old_ptr = _My_data._Myptr(); - const size_type _New_capacity = _Calculate_growth(_My_data._Mysize); - const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + const auto _Old_ptr = _My_data._Myptr(); + size_type _New_capacity = _Calculate_growth(_My_data._Mysize); + ++_New_capacity; + const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws + --_New_capacity; _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _Traits::copy(_Unfancy(_New_ptr), _Old_ptr, _My_data._Mysize); @@ -2792,9 +2798,11 @@ public: _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); // throws if (_New_capacity < _New_size) { - _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, max_size()); - const pointer _Fancyptr = _Getal().allocate(_New_capacity + 1); // throws - _Ptr = _Unfancy(_Fancyptr); + _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, max_size()); + ++_New_capacity; + const pointer _Fancyptr = _Allocate_at_least_helper(_Getal(), _New_capacity); // throws + --_New_capacity; + _Ptr = _Unfancy(_Fancyptr); _Construct_in_place(_My_data._Bx._Ptr, _Fancyptr); _Start_element_lifetimes(_Ptr, _New_capacity + 1); @@ -2863,10 +2871,12 @@ public: _Xlen_string(); } - const auto _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, _Max); - auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal()); + auto _New_capacity = _Calculate_growth(_New_size, _BUF_SIZE - 1, _Max); + auto&& _Alproxy = _GET_PROXY_ALLOCATOR(_Alty, _Getal()); _Container_proxy_ptr<_Alty> _Proxy(_Alproxy, _My_data); // throws - const pointer _Fancyptr = _Getal().allocate(_New_capacity + 1); // throws + ++_New_capacity; + const pointer _Fancyptr = _Allocate_at_least_helper(_Getal(), _New_capacity); // throws + --_New_capacity; // nothrow hereafter _Start_element_lifetimes(_Unfancy(_Fancyptr), _New_capacity + 1); _Construct_in_place(_My_data._Bx._Ptr, _Fancyptr); @@ -2946,9 +2956,10 @@ public: _Result._Res = _My_data._Myres + 1; } else { // use _BUF_SIZE + 1 to avoid SSO, if the buffer is assigned back - _Result._Ptr = _Al.allocate(_BUF_SIZE + 1); + size_type _Allocated = _BUF_SIZE + 1; + _Result._Ptr = _Allocate_at_least_helper(_Al, _Allocated); _Traits::copy(_Unfancy(_Result._Ptr), _My_data._Bx._Buf, _BUF_SIZE); - _Result._Res = _BUF_SIZE + 1; + _Result._Res = _Allocated; } _My_data._Orphan_all(); _Tidy_init(); @@ -3167,9 +3178,11 @@ public: if (_Right._Mypair._Myval2._Large_string_engaged()) { const auto _New_size = _Right._Mypair._Myval2._Mysize; - const auto _New_capacity = _Calculate_growth(_New_size, 0, _Right.max_size()); + auto _New_capacity = _Calculate_growth(_New_size, 0, _Right.max_size()); auto _Right_al_non_const = _Right_al; - const auto _New_ptr = _Right_al_non_const.allocate(_New_capacity + 1); // throws + ++_New_capacity; + const auto _New_ptr = _Allocate_at_least_helper(_Right_al_non_const, _New_capacity); // throws + --_New_capacity; _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); @@ -4736,9 +4749,11 @@ private: } const size_type _Old_capacity = _Mypair._Myval2._Myres; - const size_type _New_capacity = _Calculate_growth(_New_size); + size_type _New_capacity = _Calculate_growth(_New_size); auto& _Al = _Getal(); - const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + ++_New_capacity; + const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws + --_New_capacity; _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _Mypair._Myval2._Orphan_all(); @@ -4769,9 +4784,11 @@ private: const size_type _New_size = _Old_size + _Size_increase; const size_type _Old_capacity = _My_data._Myres; - const size_type _New_capacity = _Calculate_growth(_New_size); + size_type _New_capacity = _Calculate_growth(_New_size); auto& _Al = _Getal(); - const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws + ++_New_capacity; + const pointer _New_ptr = _Allocate_at_least_helper(_Al, _New_capacity); // throws + --_New_capacity; _Start_element_lifetimes(_Unfancy(_New_ptr), _New_capacity + 1); _My_data._Orphan_all(); diff --git a/tests/std/test.lst b/tests/std/test.lst index 5c0b3ca9533..417cd8a6fa1 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -223,6 +223,7 @@ tests\GH_003022_substr_allocator tests\GH_003105_piecewise_densities tests\GH_003119_error_category_ctor tests\GH_003246_cmath_narrowing +tests\GH_003570_allocate_at_least tests\GH_003617_vectorized_meow_element tests\GH_003676_format_large_hh_mm_ss_values tests\GH_003735_char_traits_signatures diff --git a/tests/std/tests/GH_003570_allocate_at_least/env.lst b/tests/std/tests/GH_003570_allocate_at_least/env.lst new file mode 100644 index 00000000000..642f530ffad --- /dev/null +++ b/tests/std/tests/GH_003570_allocate_at_least/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_latest_matrix.lst diff --git a/tests/std/tests/GH_003570_allocate_at_least/test.cpp b/tests/std/tests/GH_003570_allocate_at_least/test.cpp new file mode 100644 index 00000000000..05a5f45cf52 --- /dev/null +++ b/tests/std/tests/GH_003570_allocate_at_least/test.cpp @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +struct signaller { + [[nodiscard]] bool consume() { + return exchange(is_set, false); + } + + void set() { + is_set = true; + } + +private: + bool is_set = false; +}; + +signaller allocate_at_least_signal; + +template +struct signalling_allocator { + using value_type = T; + + signalling_allocator() = default; + + template + signalling_allocator(const signalling_allocator&) {} + + T* allocate(size_t count) { + T* const ptr = static_cast(malloc(count * sizeof(T))); + if (ptr) { + return ptr; + } + + throw bad_alloc(); + } + + allocation_result allocate_at_least(size_t count) { + allocate_at_least_signal.set(); + return {allocate(count * 2), count * 2}; + } + + void deallocate(T* ptr, size_t) noexcept { + free(ptr); + } + + friend bool operator==(const signalling_allocator&, const signalling_allocator&) = default; +}; + +template +void test_container() { + T container; + const size_t reserve_count = container.capacity() + 100; + container.reserve(reserve_count); + assert(allocate_at_least_signal.consume()); + assert(container.capacity() >= reserve_count * 2); + assert(container.size() == 0); +} + +void test_deque() { + deque> d; + d.resize(100); + assert(allocate_at_least_signal.consume()); + assert(d.size() == 100); +} + +void test_stream_overflow(auto& stream) { + stream << "my very long string that is indeed very long in order to make sure " + << "that overflow is called, hopefully calling allocate_at_least in return"; + assert(allocate_at_least_signal.consume()); +} + +void test_sstream() { + basic_stringstream, signalling_allocator> ss; + ss.str("my_cool_string"); + assert(allocate_at_least_signal.consume()); + test_stream_overflow(ss); +} + +void test_syncstream() { + basic_syncbuf, signalling_allocator> buf; + basic_osyncstream, signalling_allocator> ss(&buf); + test_stream_overflow(ss); +} + +template +constexpr bool always_false = false; + +template +struct icky_allocator : allocator { + template + struct rebind { + using other = icky_allocator; + }; + + allocation_result allocate_at_least(size_t) { + // The initial implementation of this feature had a problem with (icky) allocators that + // publicly derive from std::allocator and implement allocate/deallocate. The STL would + // call allocate_at_least in the std::allocator base (which would then call std::allocator::allocate), + // and would then call deallocate in the derived class (hiding the base implementation), a terrible mismatch. + // We now detect public derivation from std::allocator and avoid using allocate_at_least in that case. + static_assert(always_false); + } +}; + +void test_inheriting_allocator() { + vector> vec{2, 1, 4, 7, 5, 6, 3, 8}; + assert(accumulate(vec.begin(), vec.end(), 0, plus<>{}) == 36); +} + +int main() { + test_deque(); + test_container, signalling_allocator>>(); + test_container>>(); + test_sstream(); + test_syncstream(); + test_inheriting_allocator(); +}