[libcxx-commits] [libcxx] [libc++] Add __new_at_least (PR #215813)

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Wed Oct 21 06:47:55 PDT 2026


https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/215813

>From 0857105abc0dc1fd7d0dbe58ac7f2a1b4c70b347 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Sat, 8 Aug 2026 08:13:25 +0200
Subject: [PATCH] [libc++] Add __new_at_least

---
 libcxx/include/CMakeLists.txt                 |   2 +
 libcxx/include/__configuration/attributes.h   |   6 +
 libcxx/include/__configuration/availability.h |   7 ++
 libcxx/include/__memory/allocate_at_least.h   |  34 ++++--
 libcxx/include/__memory/allocator.h           |  14 ++-
 libcxx/include/__new/allocation_result.h      |  33 ++++++
 libcxx/include/__new/new_at_least.h           |  41 +++++++
 libcxx/include/module.modulemap.in            |   2 +
 libcxx/lib/abi/CHANGELOG.TXT                  |   6 +
 ...bcxxabi.v1.stable.exceptions.nonew.abilist |   1 +
 libcxx/src/support/new.ipp                    |  89 +++++++++++++++
 ...cate_at_least.align_val_t.replace.pass.cpp |  55 +++++++++
 .../libcpp_allocate_at_least.pass.cpp         | 105 ++++++++++++++++++
 .../libcpp_allocate_at_least.replace.pass.cpp |  56 ++++++++++
 .../test/libcxx/transitive_includes/cxx03.csv |   1 +
 .../test/libcxx/transitive_includes/cxx11.csv |   1 +
 .../test/libcxx/transitive_includes/cxx14.csv |   1 +
 .../test/libcxx/transitive_includes/cxx17.csv |   1 +
 .../test/libcxx/transitive_includes/cxx20.csv |   1 +
 .../test/libcxx/transitive_includes/cxx23.csv |   1 +
 .../test/libcxx/transitive_includes/cxx26.csv |   1 +
 21 files changed, 447 insertions(+), 11 deletions(-)
 create mode 100644 libcxx/include/__new/allocation_result.h
 create mode 100644 libcxx/include/__new/new_at_least.h
 create mode 100644 libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.align_val_t.replace.pass.cpp
 create mode 100644 libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.pass.cpp
 create mode 100644 libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.replace.pass.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index fcb66d5fe2f48a..8b4b89fb102916 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -655,11 +655,13 @@ set(files
   __mutex/tag_types.h
   __mutex/unique_lock.h
   __new/align_val_t.h
+  __new/allocation_result.h
   __new/destroying_delete_t.h
   __new/exceptions.h
   __new/global_new_delete.h
   __new/interference_size.h
   __new/launder.h
+  __new/new_at_least.h
   __new/new_handler.h
   __new/nothrow_t.h
   __new/placement_new_delete.h
diff --git a/libcxx/include/__configuration/attributes.h b/libcxx/include/__configuration/attributes.h
index 8fc0c6320af621..d48c07e516e577 100644
--- a/libcxx/include/__configuration/attributes.h
+++ b/libcxx/include/__configuration/attributes.h
@@ -477,6 +477,12 @@
 #  define _LIBCPP_DISABLE_POINTER_FIELD_PROTECTION
 #endif
 
+#if __has_cpp_attribute(_Clang::__malloc_span__)
+#  define _LIBCPP_MALLOC_SPAN [[_Clang::__malloc_span__]]
+#else
+#  define _LIBCPP_MALLOC_SPAN
+#endif
+
 // TODO(LLVM 25): Remove this escape hatch
 #ifndef _LIBCPP_DISABLE_UNUSED_STRUCT_WARNINGS
 #  define _LIBCPP_WARN_UNUSED [[__gnu__::__warn_unused__]]
diff --git a/libcxx/include/__configuration/availability.h b/libcxx/include/__configuration/availability.h
index 805a9edab14bbf..a1bce3e48221b0 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -41,6 +41,9 @@
 #  define _LIBCPP_INTRODUCED_IN_LLVM_24 1
 #  define _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE /* nothing */
 
+#  define _LIBCPP_INTRODUCED_IN_LLVM_24 1
+#  define _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE /* nothing */
+
 #  define _LIBCPP_INTRODUCED_IN_LLVM_23 1
 #  define _LIBCPP_INTRODUCED_IN_LLVM_23_ATTRIBUTE /* nothing */
 
@@ -271,6 +274,10 @@
 #define _LIBCPP_AVAILABILITY_HAS_DEBUGGING _LIBCPP_INTRODUCED_IN_LLVM_24
 #define _LIBCPP_AVAILABILITY_DEBUGGING _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE
 
+// This determines whether `std::__new_at_least` is available in the dylib, which
+// is used for std::allocator::allocate_at_least.
+#define _LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST _LIBCPP_INTRODUCED_IN_LLVM_24
+
 // This determines whether we assume that the internal std::__bad_variant_access_with_msg class
 // (which carries a message describing the cause of the failure in bad_variant_access::what())
 // provides a key function in the dylib. This allows centralizing its vtable and typeinfo instead
diff --git a/libcxx/include/__memory/allocate_at_least.h b/libcxx/include/__memory/allocate_at_least.h
index 72140d0de27afe..c1081d785436a3 100644
--- a/libcxx/include/__memory/allocate_at_least.h
+++ b/libcxx/include/__memory/allocate_at_least.h
@@ -11,7 +11,11 @@
 
 #include <__config>
 #include <__cstddef/size_t.h>
+#include <__fwd/memory.h>
 #include <__memory/allocator_traits.h>
+#include <__new/allocation_result.h>
+#include <__new/new_at_least.h>
+#include <__type_traits/is_constant_evaluated.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -19,18 +23,11 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Pointer, class _SizeT = size_t>
-struct __allocation_result {
-  _Pointer ptr;
-  _SizeT count;
-
-  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __allocation_result(_Pointer __ptr, _SizeT __count)
-      : ptr(__ptr), count(__count) {}
-};
-_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(__allocation_result);
-
 #if _LIBCPP_STD_VER >= 23
 
+// This function allocates memory using the allocator's allocate_at_least member if possible, and falls back the normal
+// allocate in older modes.
+
 template <class _Alloc>
 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto __allocate_at_least(_Alloc& __alloc, size_t __n) {
   auto __res = std::allocator_traits<_Alloc>::allocate_at_least(__alloc, __n);
@@ -46,6 +43,23 @@ __allocate_at_least(_Alloc& __alloc, size_t __n) {
   return __allocation_result<typename _Traits::pointer, typename _Traits::size_type>(__alloc.allocate(__n), __n);
 }
 
+// Provide an efficient __allocate_at_least for std::allocator in all standard modes
+
+template <class _Tp>
+[[__nodiscard__]] _LIBCPP_CONSTEXPR __allocation_result<_Tp*> __allocate_at_least(allocator<_Tp>& __alloc, size_t __n) {
+  if (__libcpp_is_constant_evaluated()) {
+    return __allocation_result<_Tp*>(__alloc.allocate(__n), __n);
+  }
+#  if _LIBCPP_HAS_ALIGNED_ALLOCATION
+  if _LIBCPP_CONSTEXPR (_LIBCPP_ALIGNOF(value_type) > _LIBCPP_DEFAULT_NEW_ALIGNMENT) {
+    auto [__ptr, __count] = std::__new_at_least(__n * sizeof(value_type), align_val_t(_LIBCPP_ALIGNOF(value_type)));
+    return {static_cast<_Tp*>(__ptr), __count / sizeof(_Tp)};
+  }
+#  endif
+  auto [__ptr, __count] = std::__new_at_least(__n * sizeof(value_type));
+  return {static_cast<_Tp*>(__ptr), __count / sizeof(_Tp)};
+}
+
 #endif // _LIBCPP_STD_VER >= 23
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__memory/allocator.h b/libcxx/include/__memory/allocator.h
index aaa9da5a87ce12..a840376bc66b1c 100644
--- a/libcxx/include/__memory/allocator.h
+++ b/libcxx/include/__memory/allocator.h
@@ -17,6 +17,7 @@
 #include <__memory/allocator_traits.h>
 #include <__new/align_val_t.h>
 #include <__new/exceptions.h>
+#include <__new/new_at_least.h>
 #include <__type_traits/is_const.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_same.h>
@@ -111,7 +112,18 @@ class allocator
 #if _LIBCPP_STD_VER >= 23
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr allocation_result<_Tp*> allocate_at_least(size_t __n) {
     static_assert(sizeof(_Tp) >= 0, "cannot allocate memory for an incomplete type");
-    return {allocate(__n), __n};
+    if consteval {
+      return {allocate(__n), __n};
+    } else {
+#  if _LIBCPP_HAS_ALIGNED_ALLOCATION
+      if _LIBCPP_CONSTEXPR (_LIBCPP_ALIGNOF(value_type) > _LIBCPP_DEFAULT_NEW_ALIGNMENT) {
+        auto [__ptr, __count] = std::__new_at_least(__n * sizeof(value_type), align_val_t(_LIBCPP_ALIGNOF(value_type)));
+        return {static_cast<value_type*>(__ptr), __count / sizeof(value_type)};
+      }
+#  endif
+      auto [__ptr, __count] = std::__new_at_least(__n * sizeof(value_type));
+      return {static_cast<value_type*>(__ptr), __count / sizeof(value_type)};
+    }
   }
 #endif
 
diff --git a/libcxx/include/__new/allocation_result.h b/libcxx/include/__new/allocation_result.h
new file mode 100644
index 00000000000000..5c56abf0c3fffd
--- /dev/null
+++ b/libcxx/include/__new/allocation_result.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___NEW_ALLOCATION_RESULT_H
+#define _LIBCPP___NEW_ALLOCATION_RESULT_H
+
+#include <__config>
+#include <__cstddef/size_t.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Pointer, class _SizeT = size_t>
+struct __allocation_result {
+  _Pointer ptr;
+  _SizeT count;
+
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __allocation_result(_Pointer __ptr, _SizeT __count)
+      : ptr(__ptr), count(__count) {}
+};
+_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(__allocation_result);
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___NEW_ALLOCATION_RESULT_H
diff --git a/libcxx/include/__new/new_at_least.h b/libcxx/include/__new/new_at_least.h
new file mode 100644
index 00000000000000..29304550eeb6cc
--- /dev/null
+++ b/libcxx/include/__new/new_at_least.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___NEW_NEW_AT_LEAST_H
+#define _LIBCPP___NEW_NEW_AT_LEAST_H
+
+#include <__config>
+#include <__cstddef/size_t.h>
+#include <__new/align_val_t.h>
+#include <__new/allocation_result.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+
+// `__new_at_least` acts like an overload of `operator new` which takes a size (and possibly alignment) and returns
+// a pointer as well as the actually allocated amount of memory. If the user replaces the relevant `operator new`
+// overload this will fall back to calling that. Otherwise it tries to allocate in a way to get the actually allocated
+// size, depending on what the platform provides.
+
+_LIBCPP_MALLOC_SPAN _LIBCPP_EXPORTED_FROM_ABI __allocation_result<void*> __new_at_least(size_t);
+#  if _LIBCPP_HAS_ALIGNED_ALLOCATION
+_LIBCPP_MALLOC_SPAN _LIBCPP_EXPORTED_FROM_ABI __allocation_result<void*> __new_at_least(size_t, align_val_t);
+#  endif
+
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST
+
+#endif // _LIBCPP___NEW_NEW_AT_LEAST_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 6ca65ba556fa1c..64b94831a40419 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1787,6 +1787,7 @@ module std {
   module new {
     header "new"
     module align_val_t          { header "__new/align_val_t.h" }
+    module allocation_result    { header "__new/allocation_result.h" }
     module destroying_delete_t  { header "__new/destroying_delete_t.h" }
     module exceptions           { header "__new/exceptions.h" }
     module global_new_delete    {
@@ -1796,6 +1797,7 @@ module std {
     }
     module interference_size    { header "__new/interference_size.h" }
     module launder              { header "__new/launder.h" }
+    module new_at_least         { header "__new/new_at_least.h" }
     module new_handler          { header "__new/new_handler.h" }
     module nothrow_t            { header "__new/nothrow_t.h" }
     module placement_new_delete { header "__new/placement_new_delete.h" }
diff --git a/libcxx/lib/abi/CHANGELOG.TXT b/libcxx/lib/abi/CHANGELOG.TXT
index 05f4a14540270e..a6dbf46dceea34 100644
--- a/libcxx/lib/abi/CHANGELOG.TXT
+++ b/libcxx/lib/abi/CHANGELOG.TXT
@@ -16,6 +16,12 @@ New entries should be added directly below the "Version" header.
 Version 24.0
 ------------
 
+* [libc++] Add __new_at_least
+
+  All platforms
+  -------------
+  Symbol added: _ZNSt3__114__new_at_leastEm
+
 * [libc++] Add <__thread/hardware_concurrency.h> __thread_hardware_concurrency()
 
   All platforms
diff --git a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
index 880cb2504c0843..c02753f16c3da3 100644
--- a/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
+++ b/libcxx/lib/abi/x86_64-unknown-linux-gnu.libcxxabi.v1.stable.exceptions.nonew.abilist
@@ -9,6 +9,7 @@
 {'is_defined': False, 'name': '_ZNSt16invalid_argumentD1Ev', 'type': 'FUNC'}
 {'is_defined': False, 'name': '_ZNSt20bad_array_new_lengthC1Ev', 'type': 'FUNC'}
 {'is_defined': False, 'name': '_ZNSt20bad_array_new_lengthD1Ev', 'type': 'FUNC'}
+{'is_defined': False, 'name': '_ZNSt3__114__new_at_leastEm', 'type': 'FUNC'}
 {'is_defined': False, 'name': '_ZNSt8bad_castC1Ev', 'type': 'FUNC'}
 {'is_defined': False, 'name': '_ZNSt8bad_castD1Ev', 'type': 'FUNC'}
 {'is_defined': False, 'name': '_ZNSt8bad_castD2Ev', 'type': 'FUNC'}
diff --git a/libcxx/src/support/new.ipp b/libcxx/src/support/new.ipp
index 286dc8e1b2564a..26ecfd5822c3b5 100644
--- a/libcxx/src/support/new.ipp
+++ b/libcxx/src/support/new.ipp
@@ -6,6 +6,8 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <__memory/allocate_at_least.h>
+#include <__new/new_at_least.h>
 #include <cstddef>
 #include <cstdlib>
 #include <new>
@@ -216,3 +218,90 @@ OVERRIDABLE_FUNCTION void* operator new[](size_t size, std::align_val_t alignmen
   ::operator delete[](ptr, alignment);
 }
 #endif // _LIBCPP_HAS_ALIGNED_ALLOCATION
+
+// This part implements __new_at_least, a version of operator new that returns the actually allocated amount of memory
+// in addition to the pointer. Since users are allowed to replace operator new, we have to check whether it is replaced
+// and fall back to that. Otherwise we can use platform-specific APIs for an improved implementation.
+//
+// We do that check via `gnu::ifunc` if it's available. Otherwise we don't do anything and just unconditionally forward
+// to `operator new(size_t{, align_val_t})`. `gnu::ifunc` takes the mangled name of a resolver function. That resolver
+// function returns a pointer to the function that should be linked. We make use of `__is_function_overridden` to detect
+// whether we can use our own special implementation or have to fall back to a user-provided operator new. This approach
+// avoids any repeated checks in a very hot path.
+
+#ifdef __APPLE__
+#  include <malloc/malloc.h>
+#elifdef __FreeBSD__
+#  include <malloc_np.h>
+#endif
+
+// FIXME: Clang should really accept functions in [[gnu::ifunc]] (or possibly [[clang::ifunc]])
+using std::__allocation_result;
+
+using new_t         = void*(std::size_t);
+using new_aligned_t = void*(std::size_t, std::align_val_t);
+
+using new_at_least_t         = __allocation_result<void*>(std::size_t);
+using new_at_least_aligned_t = __allocation_result<void*>(std::size_t, std::align_val_t);
+
+[[maybe_unused]] static new_at_least_t* new_at_least_resolver() {
+  if (std::__is_function_overridden < new_t, operator new>()) {
+    return [](std::size_t size) -> __allocation_result<void*> { return {::operator new(size), size}; };
+  } else {
+    return [](std::size_t size) -> __allocation_result<void*> {
+#ifdef __APPLE__
+      auto good_size = ::malloc_good_size(size);
+      return {operator_new_impl<on_failure::throw_bad_alloc>(good_size), good_size};
+#elifdef __FreeBSD__
+      auto good_size = ::nallocx(size, 0);
+      return {operator_new_impl<on_failure::throw_bad_alloc>(good_size), good_size};
+#else
+      // Other platforms should specialize this for their system allocator
+      return {operator_new_impl<on_failure::throw_bad_alloc>(size), size};
+#endif
+    };
+  }
+}
+
+[[maybe_unused]] static new_at_least_aligned_t* new_at_least_aligned_resolver() {
+  if (std::__is_function_overridden < new_aligned_t, operator new>()) {
+    return [](std::size_t size, std::align_val_t align) -> __allocation_result<void*> {
+      return {::operator new(size, align), size};
+    };
+  } else {
+    return [](std::size_t size, std::align_val_t align) -> __allocation_result<void*> {
+#ifdef __APPLE__
+      auto good_size = ::malloc_good_size(size);
+      return {operator_new_aligned_impl<on_failure::throw_bad_alloc>(good_size, align), good_size};
+#elifdef __FreeBSD__
+      auto good_size = ::nallocx(size, MALLOCX_ALIGN(static_cast<size_t>(align)));
+      return {operator_new_aligned_impl<on_failure::throw_bad_alloc>(good_size), good_size};
+#else
+      return {operator_new_aligned_impl<on_failure::throw_bad_alloc>(size, align), size};
+#endif
+    };
+  }
+}
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+_LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
+
+#if __has_cpp_attribute(gnu::ifunc)
+
+[[gnu::ifunc("_ZL21new_at_least_resolverv")]] new_at_least_t __new_at_least;
+[[gnu::ifunc("_ZL29new_at_least_aligned_resolverv")]] new_at_least_aligned_t __new_at_least;
+
+#else
+
+std::__allocation_result<void*> __new_at_least(std::size_t size) { return {::operator new(size), size}; }
+
+#  if _LIBCPP_HAS_ALIGNED_ALLOCATION
+std::__allocation_result<void*> __new_at_least(std::size_t size, std::align_val_t align) {
+  return {::operator new(size, align), size};
+}
+#  endif
+
+#endif
+
+_LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS
+_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.align_val_t.replace.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.align_val_t.replace.pass.cpp
new file mode 100644
index 00000000000000..c7a6d1d97d146c
--- /dev/null
+++ b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.align_val_t.replace.pass.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11
+
+// Check that __libcpp_allocate_at_least falls back to a user-provided operator new if provided.
+
+#include <__memory/is_sufficiently_aligned.h>
+#include <__new/allocate.h>
+#include <cassert>
+#include <cstdlib>
+#include <new>
+
+#include "test_macros.h"
+
+int new_called    = 0;
+int delete_called = 0;
+
+alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) char data[32];
+
+void* operator new(std::size_t, std::align_val_t) {
+  ++new_called;
+  return data;
+}
+
+void operator delete(void*, std::align_val_t) noexcept { ++delete_called; }
+
+int main(int, char**) {
+  { // Check that a simple call works as expected
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1));
+    assert(new_called == 0);
+    operator delete(result.ptr, result.count);
+    assert(delete_called == 0);
+  }
+
+  // operator new(size_t, align_val_t) isn't overridden, so we still use the special implementation.
+#ifndef TEST_HAS_NO_ALIGNED_ALLOCATION
+  { // Check that the aligned version is called with when using an alignment
+    // that's larger than the default new alignment
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1), __STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2);
+    assert(new_called == 1);
+    new_called = 0;
+    operator delete(result.ptr, result.count, std::align_val_t(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2));
+    assert(delete_called == 1);
+    delete_called = 0;
+  }
+#endif // TEST_HAS_NO_ALIGNED_ALLOCATION
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.pass.cpp
new file mode 100644
index 00000000000000..75b20973a21c74
--- /dev/null
+++ b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.pass.cpp
@@ -0,0 +1,105 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11
+
+// Check that __libcpp_allocate_at_least works as expected
+
+#include <__memory/is_sufficiently_aligned.h>
+#include <__new/allocate.h>
+#include <cassert>
+#include <limits>
+#include <new>
+
+#include "test_macros.h"
+
+int new_handler_called = 0;
+
+void my_new_handler() {
+  ++new_handler_called;
+  std::set_new_handler(nullptr);
+}
+
+int main(int, char**) {
+  { // Check that a simple call works as expected
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1));
+    assert(result.ptr);
+    assert(result.count >= 1);
+#if defined(_LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST) && (defined(__APPLE__) || defined(__FreeBSD__))
+    // Check that on platforms where we have a better implementation than the generic one we will always get more than
+    // one byte. This is technically not guaranteed, but allocators are in general not capable of allocating individual
+    // bytes efficiently.
+    assert(result.count > 1);
+#endif
+    operator delete(result.ptr, result.count);
+  }
+
+  { // Check that any size calculations are done correctly
+    auto result = std::__libcpp_allocate_at_least<int>(std::__element_count(3));
+    assert(result.ptr);
+    assert(result.count >= 3);
+    operator delete(result.ptr, result.count);
+  }
+
+#ifndef TEST_HAS_NO_ALIGNED_ALLOCATION
+  { // Check that the aligned version is called with when using an alignment
+    // that's larger than the default new alignment
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1), __STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2);
+    assert(result.ptr);
+    assert(std::__is_sufficiently_aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2>(result.ptr));
+    assert(result.count >= 1);
+#  if defined(_LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST) && (defined(__APPLE__) || defined(__FreeBSD__))
+    // Check that on platforms where we have a better implementation than the generic one we will always get more than
+    // one byte. This is technically not guaranteed, but allocators are in general not capable of allocating individual
+    // bytes efficiently.
+    assert(result.count > 1);
+#  endif
+    operator delete(result.ptr, result.count, std::align_val_t(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2));
+  }
+
+  { // Check that any size calculations are done correctly
+    auto result = std::__libcpp_allocate_at_least<int>(std::__element_count(3), __STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2);
+    assert(result.ptr);
+    assert(result.count >= 3);
+    operator delete(result.ptr, result.count);
+  }
+#endif // TEST_HAS_NO_ALIGNED_ALLOCATION
+
+  { // Test that the new handler is called if allocation fails
+#ifndef TEST_HAS_NO_EXCEPTIONS
+    new_handler_called = 0;
+    std::set_new_handler(my_new_handler);
+    try {
+      (void)std::__libcpp_allocate_at_least<char>(std::__element_count(std::numeric_limits<std::size_t>::max()));
+      assert(false);
+    } catch (std::bad_alloc const&) {
+      assert(new_handler_called == 1);
+    } catch (...) {
+      assert(false);
+    }
+#endif
+  }
+
+  { // Test that the new handler is called if allocation fails (aligned overload)
+#if !defined(TEST_HAS_NO_EXCEPTIONS) && !defined(TEST_HAS_NO_ALIGNED_ALLOCATION)
+    new_handler_called = 0;
+    std::set_new_handler(my_new_handler);
+    try {
+      (void)std::__libcpp_allocate_at_least<char>(
+          std::__element_count(std::numeric_limits<std::size_t>::max()), __STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2);
+      assert(false);
+    } catch (std::bad_alloc const&) {
+      assert(new_handler_called == 1);
+    } catch (...) {
+      assert(false);
+    }
+#endif
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.replace.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.replace.pass.cpp
new file mode 100644
index 00000000000000..d0c44aa8f878dd
--- /dev/null
+++ b/libcxx/test/libcxx/language.support/support.dynamic/libcpp_allocate_at_least.replace.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11
+
+// Check that __libcpp_allocate_at_least falls back to a user-provided operator new if provided.
+
+#include <__memory/is_sufficiently_aligned.h>
+#include <__new/allocate.h>
+#include <cassert>
+#include <cstdlib>
+#include <new>
+
+#include "test_macros.h"
+
+int new_called    = 0;
+int delete_called = 0;
+
+void* operator new(std::size_t size) {
+  ++new_called;
+  return malloc(size);
+}
+
+void operator delete(void* ptr) noexcept {
+  ++delete_called;
+  free(ptr);
+}
+
+int main(int, char**) {
+  { // Check that a simple call works as expected
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1));
+    assert(new_called == 1);
+    new_called = 0;
+    operator delete(result.ptr, result.count);
+    assert(delete_called == 1);
+    delete_called = 0;
+  }
+
+  // operator new(size_t, align_val_t) isn't overridden, so we still use the special implementation.
+#ifndef TEST_HAS_NO_ALIGNED_ALLOCATION
+  { // Check that the aligned version is called with when using an alignment
+    // that's larger than the default new alignment
+    auto result = std::__libcpp_allocate_at_least<char>(std::__element_count(1), __STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2);
+    assert(new_called == 0);
+    operator delete(result.ptr, result.count, std::align_val_t(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2));
+    assert(delete_called == 0);
+  }
+#endif // TEST_HAS_NO_ALIGNED_ALLOCATION
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 201fc2f36bf482..e09ac2cfd4bf82 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -503,6 +503,7 @@ mutex ctime
 mutex limits
 mutex ratio
 mutex version
+new cstdint
 new version
 numbers version
 numeric version
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index ab8791f9aa0747..f917f1c4aa3781 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -505,6 +505,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric version
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 3d5030b5644e2b..de63a8b6ef532d 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -509,6 +509,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric version
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 8e3b1de5efd7ee..796064b7fe623c 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -565,6 +565,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric cctype
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index b9d1dd17aa016f..567200f4147cc7 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -631,6 +631,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric cctype
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index 83125257f35f54..31291406ab42ae 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -665,6 +665,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric cctype
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index 229e85afd106b7..adeb1aae37749d 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -657,6 +657,7 @@ mutex limits
 mutex ratio
 mutex tuple
 mutex version
+new cstdint
 new version
 numbers version
 numeric cctype



More information about the libcxx-commits mailing list