Skip to content

Commit

Permalink
[libc++] Use aligned_alloc instead of posix_memalign for C++17
Browse files Browse the repository at this point in the history
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
  • Loading branch information
arichardson committed Dec 22, 2022
1 parent 659c512 commit eb6fbad
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 34 deletions.
9 changes: 9 additions & 0 deletions libcxx/include/__config
Expand Up @@ -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
Expand Down
30 changes: 20 additions & 10 deletions libcxx/include/new
Expand Up @@ -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
Expand Down
Expand Up @@ -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, "");
Expand All @@ -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.
Expand Down

0 comments on commit eb6fbad

Please sign in to comment.