diff --git a/libcxx/include/__config b/libcxx/include/__config --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -720,6 +720,15 @@ # define _LIBCPP_HAS_NO_ALIGNED_ALLOCATION # endif +// It is not yet possible to use aligned_alloc() on all Apple platforms since +// 10.15 was the first version to ship an implementation of aligned_alloc(). +# if defined(__APPLE__) +# if (defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101500) +# define _LIBCPP_HAS_NO_C11_ALIGNED_ALLOC +# endif +# endif + # if defined(__APPLE__) || defined(__FreeBSD__) # define _LIBCPP_HAS_DEFAULTRUNELOCALE # endif diff --git a/libcxx/include/new b/libcxx/include/new --- a/libcxx/include/new +++ b/libcxx/include/new @@ -337,16 +337,26 @@ // chances are that you want to use `__libcpp_allocate` instead. // // Returns the allocated memory, or `nullptr` on failure. -inline _LIBCPP_INLINE_VISIBILITY -void* __libcpp_aligned_alloc(std::size_t __alignment, std::size_t __size) { -#if defined(_LIBCPP_MSVCRT_LIKE) - return ::_aligned_malloc(__size, __alignment); -#else - void* __result = nullptr; - (void)::posix_memalign(&__result, __alignment, __size); - // If posix_memalign fails, __result is unmodified so we still return `nullptr`. - return __result; -#endif +inline _LIBCPP_INLINE_VISIBILITY void* __libcpp_aligned_alloc(std::size_t __alignment, std::size_t __size) { +# if defined(_LIBCPP_MSVCRT_LIKE) + return ::_aligned_malloc(__size, __alignment); +# elif _LIBCPP_STD_VER > 14 && !defined(_LIBCPP_HAS_NO_C11_ALIGNED_ALLOC) + // aligned_alloc() requires that __size is a multiple of __alignment, + // but for C++ [new.delete.general], only states "if the value of an + // alignment argument passed to any of these functions is not a valid + // alignment value, the behavior is undefined". + // To handle calls such as ::operator new(1, std::align_val_t(128)), we + // round __size up to the next multiple of __alignment. + size_t __rounded_size = (__size + __alignment - 1) & ~(__alignment - 1); + // Rounding up could have wrapped around to zero, so we have to add another + // max() ternary to the actual call site to avoid succeeded in that case. + return ::aligned_alloc(__alignment, __size > __rounded_size ? __size : __rounded_size); +# else + void* __result = nullptr; + (void)::posix_memalign(&__result, __alignment, __size); + // If posix_memalign fails, __result is unmodified so we still return `nullptr`. + return __result; +# endif } inline _LIBCPP_INLINE_VISIBILITY diff --git a/libcxx/test/libcxx/language.support/support.dynamic/new_faligned_allocation.pass.cpp b/libcxx/test/libcxx/language.support/support.dynamic/new_faligned_allocation.pass.cpp --- a/libcxx/test/libcxx/language.support/support.dynamic/new_faligned_allocation.pass.cpp +++ b/libcxx/test/libcxx/language.support/support.dynamic/new_faligned_allocation.pass.cpp @@ -28,6 +28,33 @@ #include "test_macros.h" +static void test_allocations(size_t size, size_t alignment) { + { + void* ptr = ::operator new(size, std::align_val_t(alignment)); + assert(ptr); + assert(reinterpret_cast(ptr) % alignment == 0); + ::operator delete(ptr, std::align_val_t(alignment)); + } + { + void* ptr = ::operator new(size, std::align_val_t(alignment), std::nothrow); + assert(ptr); + assert(reinterpret_cast(ptr) % alignment == 0); + ::operator delete(ptr, std::align_val_t(alignment), std::nothrow); + } + { + void* ptr = ::operator new[](size, std::align_val_t(alignment)); + assert(ptr); + assert(reinterpret_cast(ptr) % alignment == 0); + ::operator delete[](ptr, std::align_val_t(alignment)); + } + { + void* ptr = ::operator new[](size, std::align_val_t(alignment), std::nothrow); + assert(ptr); + assert(reinterpret_cast(ptr) % alignment == 0); + ::operator delete[](ptr, std::align_val_t(alignment), std::nothrow); + } +} + int main(int, char**) { { static_assert(std::is_enum::value, ""); @@ -49,30 +76,24 @@ assert(a == std::align_val_t(0)); assert(b == std::align_val_t(32)); } - { - void *ptr = ::operator new(1, std::align_val_t(128)); - assert(ptr); - assert(reinterpret_cast(ptr) % 128 == 0); - ::operator delete(ptr, std::align_val_t(128)); - } - { - void *ptr = ::operator new(1, std::align_val_t(128), std::nothrow); - assert(ptr); - assert(reinterpret_cast(ptr) % 128 == 0); - ::operator delete(ptr, std::align_val_t(128), std::nothrow); - } - { - void *ptr = ::operator new[](1, std::align_val_t(128)); - assert(ptr); - assert(reinterpret_cast(ptr) % 128 == 0); - ::operator delete[](ptr, std::align_val_t(128)); - } - { - void *ptr = ::operator new[](1, std::align_val_t(128), std::nothrow); - assert(ptr); - assert(reinterpret_cast(ptr) % 128 == 0); - ::operator delete[](ptr, std::align_val_t(128), std::nothrow); - } + // First, check the basic case, a large allocation with alignment==size. + test_allocations(64, 64); + // Size being a multiple of alignment also needs to be supported. + test_allocations(64, 32); + // When aligned allocation is implemented using posix_memalign, + // that function requires a minimum alignment of sizeof(void*). + // Check that we can also create overaligned allocations with + // an alignment argument less than sizeof(void*). + test_allocations(2, 2); + // When implemented using the C11 aligned_alloc() function, + // that requires that size be a multiple of alignment. + // However, the C++ operator new has no such requirements. + // Check that we can create an overaligned allocation that does + // adhere to not have this constraint. + test_allocations(1, 128); + // Finally, test size > alignment, but with size not being + // a multiple of alignment. + test_allocations(65, 32); #ifndef TEST_HAS_NO_RTTI { // Check that libc++ doesn't define align_val_t in a versioning namespace.