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 added test checks
the value actually passed to ::operator new.

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. Most targets implement the altered code with multiplication,
instead.

In addition, the dance to avoid provoking the PR108377 spurious
warning is changed so as not to generate redundant code.

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: New tests.
---
 libstdc++-v3/include/bits/new_allocator.h     | 20 +++----
 .../20_util/allocator/allocate_at_least2.cc   | 52 +++++++++++++++++++
 2 files changed, 63 insertions(+), 9 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..2890f9af8d5 100644
--- a/libstdc++-v3/include/bits/new_allocator.h
+++ b/libstdc++-v3/include/bits/new_allocator.h
@@ -31,6 +31,7 @@
 #define _STD_NEW_ALLOCATOR_H 1
 
 #include <bits/c++config.h>
+#include <utility>
 #include <new>
 #include <bits/new_throw.h>
 #include <bits/move.h>
@@ -186,15 +187,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                _S_check_allocation_limit(__n);
                const size_t __need = __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 };
+               const size_t __max = (__need + __mask) & ~__mask;
+               const size_t __spare = __max - __need;
+               if (__spare > __mask) std::unreachable();
+               const size_t __bonus = __spare / sizeof(_Tp);
+               const size_t __left =  __spare % sizeof(_Tp);
+               const size_t __ask = __max - __left;
+               // Avoid appearing to ask for 2^63 bytes (PR108377):
+               if (__ask >> (__SIZE_WIDTH__ - 1)) std::unreachable();
+               void* __p = _GLIBCXX_OPERATOR_NEW(__ask);
+               return { static_cast<_Tp*>(__p), __n + __bonus };
              }
          }
        return { allocate(__n), __n };
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..809e61ff8f9
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/allocator/allocate_at_least2.cc
@@ -0,0 +1,52 @@
+// { dg-do run { target c++23 } }
+
+#include <memory>
+#include <testsuite_hooks.h>
+
+std::size_t gn = 0;
+void* operator new(std::size_t n)
+{
+  gn = n;
+  return ::malloc(n);
+}
+
+void operator delete(void* p) noexcept
+{ ::free(p); }
+
+void operator delete(void* p, std::size_t n) noexcept
+{ (void)n; ::free(p); }
+
+void a11()
+{
+  struct A { char a[11]; };
+  std::allocator<A> alloc;
+  using alloc_traits = std::allocator_traits<std::allocator<A>>;
+  auto [p, m] = alloc_traits::allocate_at_least(alloc, 3);
+  VERIFY(m == 4); 
+  VERIFY(gn == 44); 
+}
+void a12()
+{
+  struct A { char a[12]; };
+  std::allocator<A> alloc;
+  using alloc_traits = std::allocator_traits<std::allocator<A>>;
+  auto [p, m] = alloc_traits::allocate_at_least(alloc, 3);
+  VERIFY(m == 4); 
+  VERIFY(gn == 48); 
+}
+void a13()
+{
+  struct A { char a[13]; };
+  std::allocator<A> alloc;
+  using alloc_traits = std::allocator_traits<std::allocator<A>>;
+  auto [p, m] = alloc_traits::allocate_at_least(alloc, 3);
+  VERIFY(m == 3); 
+  VERIFY(gn == 39); 
+}
+
+int main()
+{
+  a11();
+  a12();
+  a13();
+}
-- 
2.54.0

Reply via email to