[libcxx-commits] [libcxx] eb6fbad - [libc++] Use aligned_alloc instead of posix_memalign for C++17
Alex Richardson via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Dec 22 07:02:02 PST 2022
Author: Alex Richardson
Date: 2022-12-22T15:01:22Z
New Revision: eb6fbad711a2cd39ccfb4b111db0a77933e06573
URL: https://github.com/llvm/llvm-project/commit/eb6fbad711a2cd39ccfb4b111db0a77933e06573
DIFF: https://github.com/llvm/llvm-project/commit/eb6fbad711a2cd39ccfb4b111db0a77933e06573.diff
LOG: [libc++] Use aligned_alloc instead of posix_memalign for C++17
C++17 defines the C11 `aligned_alloc`, so we can use that instead of
posix_memalign. This change allows building against picolibc without
defining _DEFAULT_SOURCE/_GNU_SOURCE.
The C11 `aligned_alloc` function should be available on all supported
non-Windows platforms except for macOS where we need version 10.15.
There is one caveat: aligned_alloc() requires that __size is a multiple of
__alignment, but [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 up __size to __alignment (and check for wrap-around).
This is required at least for macOS where aligned_alloc(128, 1) returns
an error instead of allocating memory (glibc ignores the specification).
Differential Revision: https://reviews.llvm.org/D138196
Added:
Modified:
libcxx/include/__config
libcxx/include/new
libcxx/test/libcxx/language.support/support.dynamic/new_faligned_allocation.pass.cpp
Removed:
################################################################################
diff --git a/libcxx/include/__config b/libcxx/include/__config
index b80e4cb4ee5ed..8f69db2270ba0 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -720,6 +720,15 @@ _LIBCPP_BEGIN_NAMESPACE_STD _LIBCPP_END_NAMESPACE_STD
# 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
index 825034c44ce1c..892ab7a8c2a40 100644
--- a/libcxx/include/new
+++ b/libcxx/include/new
@@ -337,16 +337,26 @@ inline _LIBCPP_INLINE_VISIBILITY void __libcpp_deallocate_unsized(void* __ptr, s
// 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
index 0e4441e013c67..8820e12b55e95 100644
--- 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<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(ptr) % alignment == 0);
+ ::operator delete[](ptr, std::align_val_t(alignment), std::nothrow);
+ }
+}
+
int main(int, char**) {
{
static_assert(std::is_enum<std::align_val_t>::value, "");
@@ -49,30 +76,24 @@ int main(int, char**) {
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<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(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.
More information about the libcxx-commits
mailing list