Louis Dionne 208a6d97f5
[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
2023-10-18 19:58:24 -07:00
..