[PATCH v5] libstdc++: allocate_at_least ask only what it reports (P0401)

Torbjorn SVENSSON torbjorn.svensson@foss.st.com
Wed Jun 10 13:03:03 GMT 2026


Hello,

It looks like this change is introducing a regression for arm-none-eabi:

Testing tree-ssa/pr109442.C,  -std=gnu++11
doing compile
Executing on host: /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++  /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C  -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto   -fdiagnostics-plain-output   -fmessage-length=0  -std=gnu++11 -O2 -fdump-tree-optimized  -S     -o pr109442.s    (timeout = 800)
spawn -ignore SIGHUP /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++ /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto -fdiagnostics-plain-output -fmessage-length=0 -std=gnu++11 -O2 -fdump-tree-optimized -S -o pr109442.s
pid is 430895 -430895
pid is -1
output is  status 0
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++11 (test for excess errors)
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++11  scan-tree-dump-not optimized "_M_start"
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++11  scan-tree-dump-not optimized "delete"
Testing tree-ssa/pr109442.C,  -std=gnu++23
doing compile
Executing on host: /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++  /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C  -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto   -fdiagnostics-plain-output   -fmessage-length=0  -std=gnu++23 -O2 -fdump-tree-optimized  -S     -o pr109442.s    (timeout = 800)
spawn -ignore SIGHUP /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++ /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto -fdiagnostics-plain-output -fmessage-length=0 -std=gnu++23 -O2 -fdump-tree-optimized -S -o pr109442.s
pid is 431059 -431059
pid is -1
output is  status 0
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++23 (test for excess errors)
FAIL: g++.dg/tree-ssa/pr109442.C  -std=gnu++23  scan-tree-dump-not optimized "_M_start"
FAIL: g++.dg/tree-ssa/pr109442.C  -std=gnu++23  scan-tree-dump-not optimized "delete"
Testing tree-ssa/pr109442.C,  -std=gnu++20
doing compile
Executing on host: /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++  /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C  -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto   -fdiagnostics-plain-output   -fmessage-length=0  -std=gnu++20 -O2 -fdump-tree-optimized  -S     -o pr109442.s    (timeout = 800)
spawn -ignore SIGHUP /build/r17-1372-g80b78b2504fba0/bin/arm-none-eabi-g++ /build/gcc_src/gcc/testsuite/g++.dg/tree-ssa/pr109442.C -mthumb -march=armv6s-m -mtune=cortex-m0 -mfloat-abi=soft -mfpu=auto -fdiagnostics-plain-output -fmessage-length=0 -std=gnu++20 -O2 -fdump-tree-optimized -S -o pr109442.s
pid is 431306 -431306
pid is -1
output is  status 0
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++20 (test for excess errors)
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++20  scan-tree-dump-not optimized "_M_start"
PASS: g++.dg/tree-ssa/pr109442.C  -std=gnu++20  scan-tree-dump-not optimized "delete"
std_list: 20 98 26




With -std=gnu+20, it produces code like:

         .type   _Z4vat1St6vectorIiSaIiEE, %function
_Z4vat1St6vectorIiSaIiEE:
         .fnstart
.LFB1718:
         @ args = 0, pretend = 0, frame = 0
         @ frame_needed = 0, uses_anonymous_args = 0
         @ link register save eliminated.
         movs    r0, #10
         @ sp needed
         bx      lr
         .cantunwind
         .fnend
         .size   _Z4vat1St6vectorIiSaIiEE, .-_Z4vat1St6vectorIiSaIiEE


With -std=gnu+23, it instead produces code like:

         .type   _Z4vat1St6vectorIiSaIiEE, %function
_Z4vat1St6vectorIiSaIiEE:
         .fnstart
.LFB1898:
         @ args = 0, pretend = 0, frame = 0
         @ frame_needed = 0, uses_anonymous_args = 0
         ldmia   r0, {r0, r3}
         subs    r0, r3, r0
         push    {r4, r5, r6, lr}
         .save {r4, r5, r6, lr}
         asrs    r5, r0, #2
         beq     .L2
         movs    r2, #7
         adds    r3, r0, #7
         bics    r3, r2
         asrs    r2, r3, #31
         subs    r3, r3, r0
         adds    r3, r2, r3
         movs    r4, r0
         cmp     r3, #3
         bls     .L10
         adds    r0, r0, #4
         bl      _Znwj
         ldr     r3, .L11
         adds    r5, r5, #1
         cmp     r5, r3
         bls     .L5
         movs    r5, r3
.L5:
         lsls    r4, r5, #2
         movs    r1, r4
         bl      _ZdlPvj
.L2:
         @ sp needed
         movs    r0, #10
         pop     {r4, r5, r6, pc}
.L10:
         bl      _Znwj
         movs    r1, r4
         bl      _ZdlPvj
         b       .L2
.L12:
         .align  2
.L11:
         .word   536870911
         .fnend
         .size   _Z4vat1St6vectorIiSaIiEE, .-_Z4vat1St6vectorIiSaIiEE



Kind regards,
Torbjörn


On 2026-05-29 09:04, Jonathan Wakely wrote:
> On Thu, 28 May 2026 at 20:53, Nathan Myers <ncm@cantrip.org> wrote:
>>
>> Changes in v5:
>>   - Rename __ask -> __bytes in __new_allocator<>::allocate_at_least.
>>   - Guard __new_allocator<>::allocate_at_least body vs. __cpp_aligned_new.
>>   - Make new test pass when __cpp_aligned_new is not defined.
>>   - Make new test pass when __cpp_sized_deallocation is not defined
>>   - Include stdlib.h in test, to get malloc and free.
>>   - Omit unused parameter name in defn of op delete(p, n).
>>
>> Changes in v4:
>>   - Simplify calculation.
>>   - Drop __builtin_unreachable(), as compiler seems unable to use it.
>>   - Distinguish objects of size 8 or more, where __bonus can be only
>>    0 or 1, as compilers do not notice.
>>   - Test more thoroughly.
>>
>> Changes in v3:
>>   - Calculate extra allocations with no attempt to force 8-bit
>>    division, which pessimized everything except x86.
>>   - Add test to verify allocate_at_least change.
>>   - Also fix vector insert_vs_emplace.cc as run built with -m32.
>>
>> (Please ignore v2.)
>>
>> allocate_at_least is rounding up the allocation request size to
>> its default alignment, which may be more than an integral
>> multiple of the object size requested. When the memory is freed,
>> what the container reports it is freeing differs from the amount
>> that was allocated. This patch rounds the request size back down
>> to what will be reported to the caller.
>>
>> The algorithm to compute the allocation is altered in response
>> to findings on godbolt.org, which indicate dropping to uint8 to
>> perform the division is a pessimization everywhere other than
>> x86. The new version is implemented with multiplication,
>> instead.
>>
>> In addition, the remaining -m32 test that failed under the new
>> allocation method is fixed.
>>
>> Tested on x86 -m64/-m32.
>>
>> libstdc++-v3/ChangeLog:
>>          * include/bits/new_allocator.h (allocate_at_least): Reduce
>>          allocation to match what is reported.
>>          * testsuite/20_util/allocator/allocate_at_least2.cc: Add tests.
>>          * testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc:
>>          Fix for -m32 and new allocation results.
> 
> OK, thanks
> 
>> ---
>>   libstdc++-v3/include/bits/new_allocator.h     | 30 ++++++---
>>   .../20_util/allocator/allocate_at_least2.cc   | 66 +++++++++++++++++++
>>   .../vector/modifiers/insert_vs_emplace.cc     | 48 +++++++++-----
>>   3 files changed, 116 insertions(+), 28 deletions(-)
>>   create mode 100644 libstdc++-v3/testsuite/20_util/allocator/allocate_at_least2.cc
>>
>> diff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h
>> index 4524355a4a0..e7666b5831c 100644
>> --- a/libstdc++-v3/include/bits/new_allocator.h
>> +++ b/libstdc++-v3/include/bits/new_allocator.h
>> @@ -177,6 +177,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>         [[nodiscard]] constexpr std::allocation_result<_Tp*, size_t>
>>         allocate_at_least(size_t __n)
>>         {
>> +#if __cpp_aligned_new
>>          if ! consteval
>>            {
>>              if constexpr (requires { sizeof(_Tp); })
>> @@ -184,19 +185,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>              if constexpr ( sizeof(_Tp) <  __STDCPP_DEFAULT_NEW_ALIGNMENT__)
>>                {
>>                  _S_check_allocation_limit(__n);
>> -               const size_t __need = __n * sizeof(_Tp);
>> +               size_t __bytes = __n * sizeof(_Tp);
>>                  const size_t __mask = __STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1;
>> -               size_t __ask = (__need + __mask) & ~__mask;
>> -               // Avoid rounding up to and asking for 2^63 bytes (PR108377):
>> -               __ask -= __ask >> (__SIZE_WIDTH__ - 1);
>> -               auto* __p = static_cast<_Tp*>(_GLIBCXX_OPERATOR_NEW(__ask));
>> -               using _U8 = const unsigned char;
>> -               static_assert(sizeof(_Tp) <= ~_U8());
>> -               // Use 8-bit division for minimal latency:
>> -               _U8 __spare = __ask - __need, __size = sizeof(_Tp);
>> -               return { __p , __n + __spare / __size };
>> +               size_t __max = (__bytes + __mask) & ~__mask;
>> +               // Avoid seeming to ask for 2^63 bytes (PR108377):
>> +               __max -= __max >> (__SIZE_WIDTH__ - 1);
>> +               auto __spare = static_cast<unsigned>(__max - __bytes);
>> +               if constexpr (sizeof(_Tp) < (__mask + 1) / 2)
>> +                 {
>> +                   auto __bonus = __spare / sizeof(_Tp);
>> +                   __n += __bonus;
>> +                   __bytes += __bonus * sizeof(_Tp);
>> +                 }
>> +               else if (sizeof(_Tp) <= __spare)
>> +                 {
>> +                   __n += 1;
>> +                   __bytes += sizeof(_Tp);
>> +                 }
>> +               void* __p = _GLIBCXX_OPERATOR_NEW(__bytes);
>> +               return { static_cast<_Tp*>(__p), __n };
>>                }
>>            }
>> +#endif
>>          return { allocate(__n), __n };
>>         }
>>   #endif
>> diff --git a/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least2.cc b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least2.cc
>> new file mode 100644
>> index 00000000000..4f8645ed50d
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least2.cc
>> @@ -0,0 +1,66 @@
>> +// { dg-do run { target c++23 } }
>> +
>> +#include <memory>
>> +#include <stdlib.h>
>> +#include <testsuite_hooks.h>
>> +
>> +std::size_t gn = 0;
>> +void* operator new(std::size_t n)
>> +{
>> +  gn = n;
>> +  return ::malloc(n);
>> +}
>> +
>> +// Failing to define ops delete, too, would generate warnings.
>> +
>> +void operator delete(void* p) noexcept
>> +{ ::free(p); }
>> +
>> +#if __cpp_sized_deallocation
>> +void operator delete(void* p, std::size_t) noexcept
>> +{ ::free(p); }
>> +#endif
>> +
>> +template <unsigned size, unsigned n>
>> +void test_nm()
>> +{
>> +  struct A { char a[size]; };
>> +  std::allocator<A> alloc;
>> +  using alloc_traits = std::allocator_traits<std::allocator<A>>;
>> +  auto [p, m] = alloc_traits::allocate_at_least(alloc, n);
>> +
>> +#if __cpp_aligned_new
>> +  unsigned mod = __STDCPP_DEFAULT_NEW_ALIGNMENT__;
>> +  unsigned max = ((n * size) + mod - 1) & ~(mod - 1);
>> +  unsigned count = max / sizeof(A);
>> +#else
>> +  unsigned count = n;
>> +#endif
>> +  VERIFY(m == count);
>> +  VERIFY(gn == count * sizeof(A));
>> +  VERIFY(p != nullptr);  // named it, use it.
>> +}
>> +
>> +void test()
>> +{                  //  m  gn
>> +  test_nm<1,3>();  // 16  16
>> +  test_nm<2,3>();  //  8  16
>> +  test_nm<3,3>();  //  5  15
>> +  test_nm<4,3>();  //  4  16
>> +  test_nm<5,3>();  //  3  15
>> +  test_nm<6,3>();  //  5  30
>> +  test_nm<7,3>();  //  4  28
>> +  test_nm<8,3>();  //  4  32
>> +  test_nm<9,3>();  //  3  27
>> +  test_nm<10,3>(); //  3  30
>> +  test_nm<11,3>(); //  4  44
>> +  test_nm<12,3>(); //  4  48
>> +  test_nm<13,3>(); //  3  39
>> +  test_nm<14,3>(); //  3  42
>> +  test_nm<15,3>(); //  3  45
>> +}
>> +
>> +int main()
>> +{
>> +  test();
>> +}
>> diff --git a/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc
>> index 187e433d9d3..d226490d6f3 100644
>> --- a/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc
>> +++ b/libstdc++-v3/testsuite/23_containers/vector/modifiers/insert_vs_emplace.cc
>> @@ -223,11 +223,12 @@ test03()
>>   void
>>   test04()
>>   {
>> -  const X::special expected{ 0, 3, 1, 0, 3, 0 };
>> +  const X::special expected{ 0, 4, 1, 0, 4, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -241,7 +242,8 @@ test04()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -261,11 +263,12 @@ test04()
>>   void
>>   test05()
>>   {
>> -  const X::special expected{ 0, 3, 0, 0, 4, 0 };
>> +  const X::special expected{ 0, 4, 0, 0, 5, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -279,7 +282,8 @@ test05()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -299,11 +303,12 @@ test05()
>>   void
>>   test06()
>>   {
>> -  const X::special expected{ 1, 4, 0, 0, 4, 0 };
>> +  const X::special expected{ 1, 5, 0, 0, 5, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -316,7 +321,8 @@ test06()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -447,11 +453,12 @@ test09()
>>   void
>>   test10()
>>   {
>> -  const X::special expected{ 0, 3, 1, 0, 3, 0 };
>> +  const X::special expected{ 0, 4, 1, 0, 4, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -465,7 +472,8 @@ test10()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -485,11 +493,12 @@ test10()
>>   void
>>   test11()
>>   {
>> -  const X::special expected{ 0, 3, 0, 0, 4, 0 };
>> +  const X::special expected{ 0, 4, 0, 0, 5, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -503,7 +512,8 @@ test11()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -523,11 +533,12 @@ test11()
>>   void
>>   test12()
>>   {
>> -  const X::special expected{ 1, 4, 0, 0, 4, 0 };
>> +  const X::special expected{ 1, 5, 0, 0, 5, 0 };
>>     X::special ins, emp;
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> @@ -540,7 +551,8 @@ test12()
>>     }
>>     {
>>       std::vector<X> v;
>> -    v.reserve(3);
>> +    v.reserve(4);
>> +    v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>>       v.push_back(X(0,0));
>> --
>> 2.54.0
>>
> 



More information about the Libstdc++ mailing list