Skip to content

Commit d64394b

Browse files
ldionnetru
authored andcommitted
[libc++] Always query the compiler to find whether a type is always lockfree
In https://llvm.org/D56913, we added an emulation for the __atomic_always_lock_free compiler builtin when compiling in Freestanding mode. However, the emulation did (and could not) give exactly the same answer as the compiler builtin, which led to a potential ABI break for e.g. enum classes. After speaking to the original author of D56913, we agree that the correct behavior is to instead always use the compiler builtin, since that provides a more accurate answer, and __atomic_always_lock_free is a purely front-end builtin which doesn't require any runtime support. Furthermore, it is available regardless of the Standard mode (see https://godbolt.org/z/cazf3ssYY). However, this patch does constitute an ABI break. As shown by https://godbolt.org/z/1eoex6zdK: - In LLVM <= 11.0.1, an atomic<enum class with 1 byte> would not contain a lock byte. - In LLVM >= 12.0.0, an atomic<enum class with 1 byte> would contain a lock byte. This patch breaks the ABI again to bring it back to 1 byte, which seems like the correct thing to do. Fixes #57440 Differential Revision: https://reviews.llvm.org/D133377 (cherry picked from commit f1a601f)
1 parent c6d2e8b commit d64394b

File tree

4 files changed

+26
-43
lines changed

4 files changed

+26
-43
lines changed

libcxx/docs/ReleaseNotes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ Upcoming Deprecations and Removals
194194

195195
ABI Affecting Changes
196196
---------------------
197+
- In freestanding mode, ``atomic<small enum class>`` does not contain a lock byte anymore if the platform
198+
can implement lockfree atomics for that size. More specifically, in LLVM <= 11.0.1, an ``atomic<small enum class>``
199+
would not contain a lock byte. This was broken in LLVM >= 12.0.0, where it started including a lock byte despite
200+
the platform supporting lockfree atomics for that size. Starting in LLVM 15.0.1, the ABI for these types has been
201+
restored to what it used to be (no lock byte), which is the most efficient implementation.
202+
203+
This ABI break only affects users that compile with ``-ffreestanding``, and only for ``atomic<T>`` where ``T``
204+
is a non-builtin type that could be lockfree on the platform. See https://llvm.org/D133377 for more details.
197205

198206
- The ``_LIBCPP_ABI_USE_CXX03_NULLPTR_EMULATION`` macro controlling whether we use an
199207
emulation for ``std::nullptr_t`` in C++03 mode has been removed. After this change,

libcxx/include/atomic

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,12 @@ _Tp kill_dependency(_Tp __y) _NOEXCEPT
11131113
# define ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE
11141114
#endif
11151115

1116+
template <class _Tp>
1117+
struct __libcpp_is_always_lock_free {
1118+
// __atomic_always_lock_free is available in all Standard modes
1119+
static const bool __value = __atomic_always_lock_free(sizeof(_Tp), 0);
1120+
};
1121+
11161122
#ifdef _LIBCPP_ATOMIC_ONLY_USE_BUILTINS
11171123

11181124
template<typename _Tp>
@@ -1404,42 +1410,8 @@ _Tp __cxx_atomic_fetch_xor(__cxx_atomic_lock_impl<_Tp>* __a,
14041410
return __old;
14051411
}
14061412

1407-
#ifdef __cpp_lib_atomic_is_always_lock_free
1408-
1409-
template<typename _Tp> struct __cxx_is_always_lock_free {
1410-
enum { __value = __atomic_always_lock_free(sizeof(_Tp), 0) }; };
1411-
1412-
#else
1413-
1414-
template<typename _Tp> struct __cxx_is_always_lock_free { enum { __value = false }; };
1415-
// Implementations must match the C ATOMIC_*_LOCK_FREE macro values.
1416-
template<> struct __cxx_is_always_lock_free<bool> { enum { __value = 2 == ATOMIC_BOOL_LOCK_FREE }; };
1417-
template<> struct __cxx_is_always_lock_free<char> { enum { __value = 2 == ATOMIC_CHAR_LOCK_FREE }; };
1418-
template<> struct __cxx_is_always_lock_free<signed char> { enum { __value = 2 == ATOMIC_CHAR_LOCK_FREE }; };
1419-
template<> struct __cxx_is_always_lock_free<unsigned char> { enum { __value = 2 == ATOMIC_CHAR_LOCK_FREE }; };
1420-
#ifndef _LIBCPP_HAS_NO_CHAR8_T
1421-
template<> struct __cxx_is_always_lock_free<char8_t> { enum { __value = 2 == ATOMIC_CHAR8_T_LOCK_FREE }; };
1422-
#endif
1423-
template<> struct __cxx_is_always_lock_free<char16_t> { enum { __value = 2 == ATOMIC_CHAR16_T_LOCK_FREE }; };
1424-
template<> struct __cxx_is_always_lock_free<char32_t> { enum { __value = 2 == ATOMIC_CHAR32_T_LOCK_FREE }; };
1425-
#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
1426-
template<> struct __cxx_is_always_lock_free<wchar_t> { enum { __value = 2 == ATOMIC_WCHAR_T_LOCK_FREE }; };
1427-
#endif
1428-
template<> struct __cxx_is_always_lock_free<short> { enum { __value = 2 == ATOMIC_SHORT_LOCK_FREE }; };
1429-
template<> struct __cxx_is_always_lock_free<unsigned short> { enum { __value = 2 == ATOMIC_SHORT_LOCK_FREE }; };
1430-
template<> struct __cxx_is_always_lock_free<int> { enum { __value = 2 == ATOMIC_INT_LOCK_FREE }; };
1431-
template<> struct __cxx_is_always_lock_free<unsigned int> { enum { __value = 2 == ATOMIC_INT_LOCK_FREE }; };
1432-
template<> struct __cxx_is_always_lock_free<long> { enum { __value = 2 == ATOMIC_LONG_LOCK_FREE }; };
1433-
template<> struct __cxx_is_always_lock_free<unsigned long> { enum { __value = 2 == ATOMIC_LONG_LOCK_FREE }; };
1434-
template<> struct __cxx_is_always_lock_free<long long> { enum { __value = 2 == ATOMIC_LLONG_LOCK_FREE }; };
1435-
template<> struct __cxx_is_always_lock_free<unsigned long long> { enum { __value = 2 == ATOMIC_LLONG_LOCK_FREE }; };
1436-
template<typename _Tp> struct __cxx_is_always_lock_free<_Tp*> { enum { __value = 2 == ATOMIC_POINTER_LOCK_FREE }; };
1437-
template<> struct __cxx_is_always_lock_free<std::nullptr_t> { enum { __value = 2 == ATOMIC_POINTER_LOCK_FREE }; };
1438-
1439-
#endif //__cpp_lib_atomic_is_always_lock_free
1440-
14411413
template <typename _Tp,
1442-
typename _Base = typename conditional<__cxx_is_always_lock_free<_Tp>::__value,
1414+
typename _Base = typename conditional<__libcpp_is_always_lock_free<_Tp>::__value,
14431415
__cxx_atomic_base_impl<_Tp>,
14441416
__cxx_atomic_lock_impl<_Tp> >::type>
14451417
#else
@@ -1561,7 +1533,7 @@ struct __atomic_base // false
15611533
mutable __cxx_atomic_impl<_Tp> __a_;
15621534

15631535
#if defined(__cpp_lib_atomic_is_always_lock_free)
1564-
static _LIBCPP_CONSTEXPR bool is_always_lock_free = __atomic_always_lock_free(sizeof(__a_), 0);
1536+
static _LIBCPP_CONSTEXPR bool is_always_lock_free = __libcpp_is_always_lock_free<__cxx_atomic_impl<_Tp> >::__value;
15651537
#endif
15661538

15671539
_LIBCPP_INLINE_VISIBILITY
@@ -2664,7 +2636,7 @@ typedef atomic<uintmax_t> atomic_uintmax_t;
26642636
// atomic_*_lock_free : prefer the contention type most highly, then the largest lock-free type
26652637

26662638
#ifdef __cpp_lib_atomic_is_always_lock_free
2667-
# define _LIBCPP_CONTENTION_LOCK_FREE __atomic_always_lock_free(sizeof(__cxx_contention_t), 0)
2639+
# define _LIBCPP_CONTENTION_LOCK_FREE ::std::__libcpp_is_always_lock_free<__cxx_contention_t>::__value
26682640
#else
26692641
# define _LIBCPP_CONTENTION_LOCK_FREE false
26702642
#endif

libcxx/test/libcxx/atomics/atomics.align/align.pass.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ int main(int, char**) {
105105
CHECK_ALIGNMENT(struct LLIArr16 { long long int i[16]; });
106106
CHECK_ALIGNMENT(struct Padding { char c; /* padding */ long long int i; });
107107
CHECK_ALIGNMENT(union IntFloat { int i; float f; });
108+
CHECK_ALIGNMENT(enum class StrongEnum { foo });
108109

109110
return 0;
110111
}

libcxx/test/std/atomics/atomics.lockfree/isalwayslockfree.pass.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717

1818
#include "test_macros.h"
1919

20-
#if !defined(__cpp_lib_atomic_is_always_lock_free)
21-
# error Feature test macro missing.
22-
#endif
23-
24-
template <typename T> void checkAlwaysLockFree() {
25-
if (std::atomic<T>::is_always_lock_free)
20+
template <typename T>
21+
void checkAlwaysLockFree() {
22+
if (std::atomic<T>::is_always_lock_free) {
23+
LIBCPP_ASSERT(sizeof(std::atomic<T>) == sizeof(T)); // technically not required, but libc++ does it that way
2624
assert(std::atomic<T>().is_lock_free());
25+
}
2726
}
2827

2928
void run()
@@ -85,10 +84,13 @@ void run()
8584
CHECK_ALWAYS_LOCK_FREE(struct LLIArr16 { long long int i[16]; });
8685
CHECK_ALWAYS_LOCK_FREE(struct Padding { char c; /* padding */ long long int i; });
8786
CHECK_ALWAYS_LOCK_FREE(union IntFloat { int i; float f; });
87+
CHECK_ALWAYS_LOCK_FREE(enum class CharEnumClass : char { foo });
8888

8989
// C macro and static constexpr must be consistent.
90+
enum class CharEnumClass : char { foo };
9091
static_assert(std::atomic<bool>::is_always_lock_free == (2 == ATOMIC_BOOL_LOCK_FREE), "");
9192
static_assert(std::atomic<char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
93+
static_assert(std::atomic<CharEnumClass>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
9294
static_assert(std::atomic<signed char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
9395
static_assert(std::atomic<unsigned char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
9496
#if TEST_STD_VER > 17 && defined(__cpp_char8_t)

0 commit comments

Comments
 (0)