[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