From 58b1adfc1c55e7f8606b4de2e44974a7b0f14da9 Mon Sep 17 00:00:00 2001 From: Nikolas Klauser 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 -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 [[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(__alloc.allocate(__n), __n); } +// Provide an efficient __allocate_at_least for std::allocator in all standard modes + +template +[[__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 #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 +_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(__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)); +# if __has_builtin(__builtin_assume) + __builtin_assume(__ptr); + __builtin_assume((reinterpret_cast(__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(__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 +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 __new_at_least(size_t); +# if _LIBCPP_HAS_LIBRARY_ALIGNED_ALLOCATION +_LIBCPP_MALLOC_SPAN _LIBCPP_EXPORTED_FROM_ABI __allocation_result __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 #include #include @@ -23,6 +25,12 @@ void __throw_bad_alloc_shim(); # define _LIBCPP_ASSERT_SHIM #endif +enum class on_failure { + return_null, + throw_bad_alloc, +}; + +template 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(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(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(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 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(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(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(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 +#elifdef __FreeBSD__ +# include +#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(std::size_t); +using new_at_least_aligned_t = __allocation_result(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 { return {::operator new(size), size}; }; + } else { + return [](std::size_t size) -> __allocation_result { +#ifdef __APPLE__ + auto good_size = ::malloc_good_size(size); + return {operator_new_impl(good_size), good_size}; +#elifdef __FreeBSD__ + auto good_size = ::nallocx(size, 0); + return {operator_new_impl(good_size), good_size}; +#else + // Other platforms should specialize this for their system allocator + return {operator_new_impl(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 { + return {::operator new(size, align), size}; + }; + } else { + return [](std::size_t size, std::align_val_t align) -> __allocation_result { +#ifdef __APPLE__ + auto good_size = ::malloc_good_size(size); + return {operator_new_aligned_impl(good_size, align), good_size}; +#elifdef __FreeBSD__ + auto good_size = ::nallocx(size, MALLOCX_ALIGN(static_cast(align))); + return {operator_new_aligned_impl(good_size), good_size}; +#else + return {operator_new_aligned_impl(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 __new_at_least(std::size_t size) { return {::operator new(size), size}; } + +# if _LIBCPP_HAS_LIBRARY_ALIGNED_ALLOCATION +std::__allocation_result __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 +#include +#include + +#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(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(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 +#include +#include + +#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(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(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(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(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(std::__element_count(std::numeric_limits::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( + std::__element_count(std::numeric_limits::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 +#include +#include + +#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(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(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