Skip to content

Commit 208a6d9

Browse files
authored
[libc++] Fix inconsistency between is_lock_free and is_always_lock_free (#68109)
std::atomic is implemented with the following (confusing!) hierarchy of types: std::atomic<T> : std::__atomic_base<T> { ... }; std::__atomic_base<T> { std::__cxx_atomic_impl<T> __impl; }; std::__cxx_atomic_impl<T> { _Atomic(T) __val; }; Inside std::__atomic_base, we implement the is_lock_free() and is_always_lock_free() functions. However, we used to implement them inconsistently: - is_always_lock_free() is based on whether __cxx_atomic_impl<T> is always lock free (using the builtin), which means that we include any potential padding added by _Atomic(T) into the determination. - is_lock_free() was based on whether T is lock free (using the builtin), which meant that we did not take into account any potential padding added by _Atomic(T). It is important to note that the padding added by _Atomic(T) can turn a type that wouldn't be lock free into a lock free type, for example by making its size become a power of two. The inconsistency of how the two functions were implemented could lead to cases where is_always_lock_free() would return true, but is_lock_free() would then return false. This is the case for example of the following type, which is always lock free on arm64 but was incorrectly reported as !is_lock_free() before this patch: struct Foo { float x[3]; }; This patch switches the determination of is_lock_free() to be based on __cxx_atomic_impl<T> instead to match how we determine is_always_lock_free(). rdar://115324353
1 parent 1196e6d commit 208a6d9

File tree

7 files changed

+47
-12
lines changed

7 files changed

+47
-12
lines changed

libcxx/include/__atomic/atomic_base.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ struct __atomic_base // false
3939

4040
_LIBCPP_HIDE_FROM_ABI
4141
bool is_lock_free() const volatile _NOEXCEPT
42-
{return __cxx_atomic_is_lock_free(sizeof(_Tp));}
42+
{return __cxx_atomic_is_lock_free(sizeof(__cxx_atomic_impl<_Tp>));}
4343
_LIBCPP_HIDE_FROM_ABI
4444
bool is_lock_free() const _NOEXCEPT
4545
{return static_cast<__atomic_base const volatile*>(this)->is_lock_free();}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ int main(int, char**) {
100100
CHECK_ALIGNMENT(struct Empty {});
101101
CHECK_ALIGNMENT(struct OneInt { int i; });
102102
CHECK_ALIGNMENT(struct IntArr2 { int i[2]; });
103+
CHECK_ALIGNMENT(struct FloatArr3 { float i[3]; });
103104
CHECK_ALIGNMENT(struct LLIArr2 { long long int i[2]; });
104105
CHECK_ALIGNMENT(struct LLIArr4 { long long int i[4]; });
105106
CHECK_ALIGNMENT(struct LLIArr8 { long long int i[8]; });

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
template <typename T>
2222
void checkAlwaysLockFree() {
2323
if (std::atomic<T>::is_always_lock_free) {
24-
LIBCPP_ASSERT(sizeof(std::atomic<T>) == sizeof(T)); // technically not required, but libc++ does it that way
2524
assert(std::atomic<T>().is_lock_free());
2625
}
2726
}
@@ -79,6 +78,7 @@ void run()
7978
CHECK_ALWAYS_LOCK_FREE(struct Empty {});
8079
CHECK_ALWAYS_LOCK_FREE(struct OneInt { int i; });
8180
CHECK_ALWAYS_LOCK_FREE(struct IntArr2 { int i[2]; });
81+
CHECK_ALWAYS_LOCK_FREE(struct FloatArr3 { float i[3]; });
8282
CHECK_ALWAYS_LOCK_FREE(struct LLIArr2 { long long int i[2]; });
8383
CHECK_ALWAYS_LOCK_FREE(struct LLIArr4 { long long int i[4]; });
8484
CHECK_ALWAYS_LOCK_FREE(struct LLIArr8 { long long int i[8]; });

libcxx/test/std/atomics/atomics.types.generic/address.pass.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,14 @@ do_test()
8080
typedef typename std::remove_pointer<T>::type X;
8181
A obj(T(0));
8282
assert(obj == T(0));
83-
bool b0 = obj.is_lock_free();
84-
((void)b0); // mark as unused
83+
{
84+
bool lockfree = obj.is_lock_free();
85+
(void)lockfree;
86+
#if TEST_STD_VER >= 17
87+
if (A::is_always_lock_free)
88+
assert(lockfree);
89+
#endif
90+
}
8591
obj.store(T(0));
8692
assert(obj == T(0));
8793
obj.store(T(1), std::memory_order_release);

libcxx/test/std/atomics/atomics.types.generic/bool.pass.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,14 @@ int main(int, char**)
6161
{
6262
volatile std::atomic<bool> obj(true);
6363
assert(obj == true);
64-
bool b0 = obj.is_lock_free();
65-
(void)b0; // to placate scan-build
64+
{
65+
bool lockfree = obj.is_lock_free();
66+
(void)lockfree;
67+
#if TEST_STD_VER >= 17
68+
if (std::atomic<bool>::is_always_lock_free)
69+
assert(lockfree);
70+
#endif
71+
}
6672
obj.store(false);
6773
assert(obj == false);
6874
obj.store(true, std::memory_order_release);
@@ -112,8 +118,14 @@ int main(int, char**)
112118
{
113119
std::atomic<bool> obj(true);
114120
assert(obj == true);
115-
bool b0 = obj.is_lock_free();
116-
(void)b0; // to placate scan-build
121+
{
122+
bool lockfree = obj.is_lock_free();
123+
(void)lockfree;
124+
#if TEST_STD_VER >= 17
125+
if (std::atomic<bool>::is_always_lock_free)
126+
assert(lockfree);
127+
#endif
128+
}
117129
obj.store(false);
118130
assert(obj == false);
119131
obj.store(true, std::memory_order_release);
@@ -163,8 +175,14 @@ int main(int, char**)
163175
{
164176
std::atomic_bool obj(true);
165177
assert(obj == true);
166-
bool b0 = obj.is_lock_free();
167-
(void)b0; // to placate scan-build
178+
{
179+
bool lockfree = obj.is_lock_free();
180+
(void)lockfree;
181+
#if TEST_STD_VER >= 17
182+
if (std::atomic_bool::is_always_lock_free)
183+
assert(lockfree);
184+
#endif
185+
}
168186
obj.store(false);
169187
assert(obj == false);
170188
obj.store(true, std::memory_order_release);

libcxx/test/std/atomics/atomics.types.generic/integral.pass.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,14 @@ do_test()
9898
{
9999
A obj(T(0));
100100
assert(obj == T(0));
101-
bool b0 = obj.is_lock_free();
102-
((void)b0); // mark as unused
101+
{
102+
bool lockfree = obj.is_lock_free();
103+
(void)lockfree;
104+
#if TEST_STD_VER >= 17
105+
if (A::is_always_lock_free)
106+
assert(lockfree);
107+
#endif
108+
}
103109
obj.store(T(0));
104110
assert(obj == T(0));
105111
obj.store(T(1), std::memory_order_release);

libcxx/test/std/atomics/atomics.types.operations/atomics.types.operations.req/atomic_is_lock_free.pass.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ struct TestFn {
2727
void operator()() const {
2828
typedef std::atomic<T> A;
2929
T t = T();
30+
3031
A a(t);
3132
bool b1 = std::atomic_is_lock_free(static_cast<const A*>(&a));
33+
if (A::is_always_lock_free)
34+
assert(b1);
35+
3236
volatile A va(t);
3337
bool b2 = std::atomic_is_lock_free(static_cast<const volatile A*>(&va));
3438
assert(b1 == b2);

0 commit comments

Comments
 (0)