[libc++] Fix native wait alignment (#180928)

This PR fixes two issues regarding the alignment of native wait:
- In the internal platform call, the local variable is copied from a
potentially non-aligned buffer
- Under the unstable ABI, the predicate to test eligibility of a type
being able to do native wait is purely on size. We should test also the
alignment of such type is qualified for platform call

---------

Co-authored-by: Louis Dionne <ldionne.2@gmail.com>
This commit is contained in:
Hui 2026-02-12 19:14:18 +00:00 committed by GitHub
parent a1c4c1de05
commit 155beb9749
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 9 deletions

View File

@ -67,8 +67,11 @@ concept __atomic_waitable = requires(const _Tp __t, memory_order __order) {
# if defined(_LIBCPP_ABI_ATOMIC_WAIT_NATIVE_BY_SIZE)
_LIBCPP_HIDE_FROM_ABI constexpr bool __has_native_atomic_wait_impl(size_t __size) {
switch (__size) {
template <class _Tp>
_LIBCPP_HIDE_FROM_ABI constexpr bool __has_native_atomic_wait_impl() {
if (alignof(_Tp) % sizeof(_Tp) != 0)
return false;
switch (sizeof(_Tp)) {
# define _LIBCPP_MAKE_CASE(n) \
case n: \
return true;
@ -82,7 +85,7 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool __has_native_atomic_wait_impl(size_t __size
template <class _Tp>
concept __has_native_atomic_wait =
has_unique_object_representations_v<_Tp> && is_trivially_copyable_v<_Tp> &&
__has_native_atomic_wait_impl(sizeof(_Tp));
std::__has_native_atomic_wait_impl<_Tp>();
# else // _LIBCPP_ABI_ATOMIC_WAIT_NATIVE_BY_SIZE

View File

@ -66,7 +66,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
template <std::size_t _Size>
static void __platform_wait_on_address(void const* __ptr, void const* __val, uint64_t __timeout_ns) {
static_assert(_Size == 4, "Can only wait on 4 bytes value");
char buffer[_Size];
alignas(__cxx_contention_t) char buffer[_Size];
std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
static constexpr timespec __default_timeout = {2, 0};
timespec __timeout;
@ -99,15 +99,18 @@ extern "C" int __ulock_wake(uint32_t operation, void* addr, uint64_t wake_value)
template <std::size_t _Size>
static void __platform_wait_on_address(void const* __ptr, void const* __val, uint64_t __timeout_ns) {
static_assert(_Size == 8 || _Size == 4, "Can only wait on 8 bytes or 4 bytes value");
char buffer[_Size];
std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
auto __timeout_us = __timeout_ns == 0 ? 0 : static_cast<uint32_t>(__timeout_ns / 1000);
if constexpr (_Size == 4)
if constexpr (_Size == 4) {
alignas(uint32_t) char buffer[_Size];
std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
__ulock_wait(
UL_COMPARE_AND_WAIT, const_cast<void*>(__ptr), *reinterpret_cast<uint32_t const*>(&buffer), __timeout_us);
else
} else {
alignas(uint64_t) char buffer[_Size];
std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
__ulock_wait(
UL_COMPARE_AND_WAIT64, const_cast<void*>(__ptr), *reinterpret_cast<uint64_t const*>(&buffer), __timeout_us);
}
}
template <std::size_t _Size>
@ -130,7 +133,7 @@ static void __platform_wake_by_address(void const* __ptr, bool __notify_one) {
template <std::size_t _Size>
static void __platform_wait_on_address(void const* __ptr, void const* __val, uint64_t __timeout_ns) {
static_assert(_Size == 8, "Can only wait on 8 bytes value");
char buffer[_Size];
alignas(__cxx_contention_t) char buffer[_Size];
std::memcpy(&buffer, const_cast<const void*>(__val), _Size);
if (__timeout_ns == 0) {
_umtx_op(const_cast<void*>(__ptr), UMTX_OP_WAIT, *reinterpret_cast<__cxx_contention_t*>(&buffer), nullptr, nullptr);

View File

@ -0,0 +1,45 @@
//===----------------------------------------------------------------------===//
//
// 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, c++14, c++17
// UNSUPPORTED: no-threads
// When __has_native_atomic_wait<T> is true, the atomic object's address will be directly passed to the platform's wait.
// This test ensures that types that do not satisfy the platform's wait requirement should have __has_native_atomic_wait<T> be false.
#include <atomic>
#include <cstddef>
#include <type_traits>
template <std::size_t Size, std::size_t Align>
struct alignas(Align) Data {
char buffer[Size];
};
static_assert(std::__has_native_atomic_wait<std::__cxx_contention_t>);
#if defined(_LIBCPP_ABI_ATOMIC_WAIT_NATIVE_BY_SIZE) && defined(__APPLE__)
static_assert(std::__has_native_atomic_wait<Data<4, 4>>);
static_assert(std::__has_native_atomic_wait<Data<8, 8>>);
static_assert(!std::has_unique_object_representations_v<Data<4, 8>>);
static_assert(!std::__has_native_atomic_wait<Data<4, 8>>,
"Object with !has_unique_object_representations_v should not have native wait");
static_assert(!std::__has_native_atomic_wait<Data<1, 1>>, "Should only support native wait for 4 and 8 byte types");
// `__ulock_wait` requires the address is aligned to the requested size (4 or 8)
static_assert(!std::__has_native_atomic_wait<Data<4, 1>>,
"Should only support native wait for types with properly aligned types");
static_assert(!std::__has_native_atomic_wait<Data<8, 1>>,
"Should only support native wait for types with properly aligned types");
#endif