[libcxx-commits] [libcxx] [libc++] Add __new_at_least (PR #215813)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Aug 26 05:11:11 PDT 2026
https://github.com/philnik777 updated https://github.com/llvm/llvm-project/pull/215813
>From 58b1adfc1c55e7f8606b4de2e44974a7b0f14da9 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 | 12 ++
libcxx/include/__memory/allocate_at_least.h | 27 ++--
libcxx/include/__memory/allocator.h | 7 +-
libcxx/include/__new/allocate.h | 31 +++++
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 | 118 ++++++++++++++++--
...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 +
22 files changed, 486 insertions(+), 23 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 d717a7354c62b..4afbc1ca7be4b 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -650,11 +650,13 @@ set(files
__mutex/unique_lock.h
__new/align_val_t.h
__new/allocate.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 2f8841fce0a07..2ae819213534c 100644
--- a/libcxx/include/__configuration/attributes.h
+++ b/libcxx/include/__configuration/attributes.h
@@ -473,6 +473,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 0709deff47ea6..b01ec3fa53239 100644
--- a/libcxx/include/__configuration/availability.h
+++ b/libcxx/include/__configuration/availability.h
@@ -39,6 +39,9 @@
// in all versions of the library are available.
#if !_LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS
+# 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 */
@@ -73,6 +76,11 @@
// clang-format off
+// LLVM 24
+// TODO: Fill this in
+# define _LIBCPP_INTRODUCED_IN_LLVM_24 0
+# define _LIBCPP_INTRODUCED_IN_LLVM_24_ATTRIBUTE __attribute__((unavailable))
+
// LLVM 23
// TODO: Fill this in
# define _LIBCPP_INTRODUCED_IN_LLVM_23 0
@@ -239,6 +247,10 @@
#endif
+// 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 72140d0de27af..c2a39ceaf292d 100644
--- a/libcxx/include/__memory/allocate_at_least.h
+++ b/libcxx/include/__memory/allocate_at_least.h
@@ -11,7 +11,10 @@
#include <__config>
#include <__cstddef/size_t.h>
+#include <__fwd/memory.h>
#include <__memory/allocator_traits.h>
+#include <__new/allocate.h>
+#include <__type_traits/is_constant_evaluated.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -19,18 +22,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 +42,17 @@ __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);
+ } else {
+ return std::__libcpp_allocate_at_least<_Tp>(__element_count(__n));
+ }
+}
+
#endif // _LIBCPP_STD_VER >= 23
_LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/include/__memory/allocator.h b/libcxx/include/__memory/allocator.h
index dd8c8647c178a..cffc65d19e913 100644
--- a/libcxx/include/__memory/allocator.h
+++ b/libcxx/include/__memory/allocator.h
@@ -96,7 +96,12 @@ 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 {
+ auto [__ptr, __count] = std::__libcpp_allocate_at_least<_Tp>(__element_count(__n));
+ return {__ptr, __count};
+ }
}
#endif
diff --git a/libcxx/include/__new/allocate.h b/libcxx/include/__new/allocate.h
index b9bc2e1a50710..5a5a421639215 100644
--- a/libcxx/include/__new/allocate.h
+++ b/libcxx/include/__new/allocate.h
@@ -13,8 +13,11 @@
#include <__cstddef/max_align_t.h>
#include <__cstddef/size_t.h>
#include <__new/align_val_t.h>
+#include <__new/allocation_result.h>
+#include <__new/new_at_least.h>
#include <__type_traits/type_identity.h>
#include <__utility/element_count.h>
+#include <cstdint>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -42,6 +45,34 @@ __libcpp_allocate(__element_count __n, [[__maybe_unused__]] size_t __align = _LI
return static_cast<_Tp*>(__builtin_operator_new(__size));
}
+template <class _Tp>
+_LIBCPP_NO_CFI __allocation_result<_Tp*>
+__libcpp_allocate_at_least(__element_count __n, [[__maybe_unused__]] size_t __align = _LIBCPP_ALIGNOF(_Tp)) {
+#if _LIBCPP_AVAILABILITY_HAS_NEW_AT_LEAST
+ size_t __size = static_cast<size_t>(__n) * sizeof(_Tp);
+# if _LIBCPP_HAS_ALIGNED_ALLOCATION
+ if (__is_overaligned_for_new(__align)) {
+ auto [__ptr, __count] = std::__new_at_least(__size, static_cast<align_val_t>(__align));
+# if __has_builtin(__builtin_assume)
+ __builtin_assume(__ptr);
+ __builtin_assume((reinterpret_cast<uintptr_t>(__ptr) & (__align - 1)) == 0);
+ __builtin_assume(__count >= __size);
+# endif
+ return {static_cast<_Tp*>(__ptr), __count / sizeof(_Tp)};
+ }
+# endif
+ auto [__ptr, __count] = std::__new_at_least(__size);
+# if __has_builtin(__builtin_assume)
+ __builtin_assume(__ptr);
+ __builtin_assume((reinterpret_cast<uintptr_t>(__ptr) & (__align - 1)) == 0);
+ __builtin_assume(__count >= __size);
+# endif
+ return __allocation_result<_Tp*>(static_cast<_Tp*>(__ptr), __count / sizeof(_Tp));
+#else
+ return __allocation_result<_Tp*>(std::__libcpp_allocate<_Tp>(__n, __align), __n);
+#endif
+}
+
#if defined(__cpp_sized_deallocation) && __cpp_sized_deallocation >= 201309L
# define _LIBCPP_ONLY_IF_SIZED_DEALLOCATION(...) __VA_ARGS__
#else
diff --git a/libcxx/include/__new/allocation_result.h b/libcxx/include/__new/allocation_result.h
new file mode 100644
index 0000000000000..5c56abf0c3fff
--- /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 0000000000000..3b370c8371b12
--- /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_LIBRARY_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 78f4e08d9c282..2443091b8912c 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1785,6 +1785,7 @@ module std {
export std.utility.element_count // used as part of the API
export * // TODO: Workaround for https://llvm.org/PR120108
}
+ 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 {
@@ -1794,6 +1795,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 30bb3d6254965..61b6e8869c8c3 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++] Remove locale::id::__next_id from the ABI
`__next_id` is only ever used inside the dylib, so we can avoid making it public.
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 c727c028d21e6..25a006ec26b59 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 80be2d904af4c..89689fcaaeaba 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>
@@ -23,6 +25,12 @@ void __throw_bad_alloc_shim();
# define _LIBCPP_ASSERT_SHIM
#endif
+enum class on_failure {
+ return_null,
+ throw_bad_alloc,
+};
+
+template <on_failure failure_mode>
static void* operator_new_impl(std::size_t size) {
if (size == 0)
size = 1;
@@ -36,14 +44,13 @@ static void* operator_new_impl(std::size_t size) {
else
break;
}
+ if (failure_mode == on_failure::throw_bad_alloc && !p)
+ __throw_bad_alloc_shim();
return p;
}
OVERRIDABLE_FUNCTION void* operator new(std::size_t size) _THROW_BAD_ALLOC {
- void* p = operator_new_impl(size);
- if (p == nullptr)
- __throw_bad_alloc_shim();
- return p;
+ return operator_new_impl<on_failure::throw_bad_alloc>(size);
}
[[gnu::weak]] void* operator new(size_t size, const std::nothrow_t&) noexcept {
@@ -59,7 +66,7 @@ OVERRIDABLE_FUNCTION void* operator new(std::size_t size) _THROW_BAD_ALLOC {
"`operator new(size_t, nothrow_t)` as well.");
# endif
- return operator_new_impl(size);
+ return operator_new_impl<on_failure::return_null>(size);
#else
void* p = nullptr;
try {
@@ -85,7 +92,7 @@ OVERRIDABLE_FUNCTION void* operator new[](size_t size) _THROW_BAD_ALLOC { return
"`operator new[](size_t, nothrow_t)` as well.");
# endif
- return operator_new_impl(size);
+ return operator_new_impl<on_failure::return_null>(size);
#else
void* p = nullptr;
try {
@@ -110,6 +117,7 @@ OVERRIDABLE_FUNCTION void* operator new[](size_t size) _THROW_BAD_ALLOC { return
#if _LIBCPP_HAS_LIBRARY_ALIGNED_ALLOCATION
+template <on_failure failure_mode>
static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignment) {
if (size == 0)
size = 1;
@@ -127,14 +135,13 @@ static void* operator_new_aligned_impl(std::size_t size, std::align_val_t alignm
else
break;
}
+ if (failure_mode == on_failure::throw_bad_alloc && !p)
+ __throw_bad_alloc_shim();
return p;
}
OVERRIDABLE_FUNCTION void* operator new(std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC {
- void* p = operator_new_aligned_impl(size, alignment);
- if (p == nullptr)
- __throw_bad_alloc_shim();
- return p;
+ return operator_new_aligned_impl<on_failure::throw_bad_alloc>(size, alignment);
}
[[gnu::weak]] void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept {
@@ -150,7 +157,7 @@ OVERRIDABLE_FUNCTION void* operator new(std::size_t size, std::align_val_t align
"`operator new(size_t, align_val_t, nothrow_t)` as well.");
# endif
- return operator_new_aligned_impl(size, alignment);
+ return operator_new_aligned_impl<on_failure::return_null>(size, alignment);
# else
void* p = nullptr;
try {
@@ -178,7 +185,7 @@ OVERRIDABLE_FUNCTION void* operator new[](size_t size, std::align_val_t alignmen
"override `operator new[](size_t, align_val_t, nothrow_t)` as well.");
# endif
- return operator_new_aligned_impl(size, alignment);
+ return operator_new_aligned_impl<on_failure::return_null>(size, alignment);
# else
void* p = nullptr;
try {
@@ -211,3 +218,90 @@ OVERRIDABLE_FUNCTION void* operator new[](size_t size, std::align_val_t alignmen
::operator delete[](ptr, alignment);
}
#endif // _LIBCPP_HAS_LIBRARY_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_LIBRARY_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 0000000000000..c7a6d1d97d146
--- /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 0000000000000..75b20973a21c7
--- /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 0000000000000..d0c44aa8f878d
--- /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 ad2f90062c078..123e605e6a907 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -499,6 +499,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 672df0a488c9e..75106f556cc71 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -501,6 +501,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 f496a00270386..9da731c59c6ca 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.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/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index e0a92259202c4..cf86625cd9aa2 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -561,6 +561,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 44b5fe1f1577f..712be5181542f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -626,6 +626,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 a199c610e9f57..f28f67aa66f71 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -660,6 +660,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 bd9227ca685d0..7d832f1f477ce 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -653,6 +653,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