Skip to content

[libc++] Don't implement <stdatomic.h> before C++23 #123130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 17, 2025

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Jan 15, 2025

#95498 implemented a libc++ extension where <stdatomic.h> would forward to even before C++23. Unfortunately, this was found to be a breaking change (with fairly widespread impact) since that changes whether _Atomic(T) is a C style atomic or std::atomic. In principle, this can even be an ABI break.

We generally don't implement extensions in libc++ because they cause so many problems, and that extension had been accepted because it was deemed pretty small and only a quality of life improvement. Since it has widespread impact on valid C++20 (and before) code, this patch removes the extension before we ship it in any public release.

llvm#95498 implemented a libc++
extension where <stdatomic.h> would forward to <atomic> even before
C++23. Unfortunately, this was found to be a breaking change (with
fairly widespread impact) since that changes whether _Atomic(T) is
a C style atomic or std::atomic<T>. In principle, this can even be
an ABI break.

We generally don't implement extensions in libc++ because they cause
so many problems, and that extension had been accepted because it was
deemed pretty small and only a quality of life improvement. Since it
has widespread impact on valid C++20 (and before) code, this patch
removes the extension before we ship it in any public release.
@ldionne ldionne requested a review from rprichard January 15, 2025 22:27
@ldionne ldionne requested a review from a team as a code owner January 15, 2025 22:27
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jan 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 15, 2025

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

#95498 implemented a libc++ extension where <stdatomic.h> would forward to <atomic> even before C++23. Unfortunately, this was found to be a breaking change (with fairly widespread impact) since that changes whether _Atomic(T) is a C style atomic or std::atomic<T>. In principle, this can even be an ABI break.

We generally don't implement extensions in libc++ because they cause so many problems, and that extension had been accepted because it was deemed pretty small and only a quality of life improvement. Since it has widespread impact on valid C++20 (and before) code, this patch removes the extension before we ship it in any public release.


Full diff: https://github.com/llvm/llvm-project/pull/123130.diff

6 Files Affected:

  • (modified) libcxx/include/atomic (+4)
  • (modified) libcxx/include/stdatomic.h (+5-2)
  • (modified) libcxx/test/libcxx/atomics/atomics.syn/compatible_with_stdatomic.compile.pass.cpp (+6-5)
  • (added) libcxx/test/libcxx/atomics/atomics.syn/incompatible_with_stdatomic.verify.cpp (+22)
  • (added) libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.compile.pass.cpp (+24)
  • (added) libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.cxx23.compile.pass.cpp (+25)
diff --git a/libcxx/include/atomic b/libcxx/include/atomic
index 80f9e437bfaab0..0c641c7a68b58c 100644
--- a/libcxx/include/atomic
+++ b/libcxx/include/atomic
@@ -592,6 +592,10 @@ template <class T>
 #else
 #  include <__config>
 
+#  if _LIBCPP_STD_VER < 23 && defined(_LIBCPP_STDATOMIC_H)
+#    error <atomic> is incompatible with <stdatomic.h> before C++23. Please compile with -std=c++23.
+#  endif
+
 #  include <__atomic/aliases.h>
 #  include <__atomic/atomic.h>
 #  include <__atomic/atomic_flag.h>
diff --git a/libcxx/include/stdatomic.h b/libcxx/include/stdatomic.h
index a0b46e3b7bc173..ea57a1fb067020 100644
--- a/libcxx/include/stdatomic.h
+++ b/libcxx/include/stdatomic.h
@@ -126,7 +126,7 @@ using std::atomic_signal_fence                         // see below
 #    pragma GCC system_header
 #  endif
 
-#  if defined(__cplusplus)
+#  if defined(__cplusplus) && _LIBCPP_STD_VER >= 23
 
 #    include <atomic>
 #    include <version>
@@ -233,11 +233,14 @@ using std::atomic_thread_fence _LIBCPP_USING_IF_EXISTS;
 
 #  else
 
+// Before C++23, we include the next <stdatomic.h> on the path to avoid hijacking
+// the header, since Clang and some platforms have been providing this header for
+// a long time and users rely on it.
 #    if __has_include_next(<stdatomic.h>)
 #      include_next <stdatomic.h>
 #    endif
 
-#  endif // defined(__cplusplus)
+#  endif // defined(__cplusplus) && _LIBCPP_STD_VER >= 23
 #endif   // defined(__cplusplus) && __cplusplus < 201103L && defined(_LIBCPP_USE_FROZEN_CXX03_HEADERS)
 
 #endif // _LIBCPP_STDATOMIC_H
diff --git a/libcxx/test/libcxx/atomics/atomics.syn/compatible_with_stdatomic.compile.pass.cpp b/libcxx/test/libcxx/atomics/atomics.syn/compatible_with_stdatomic.compile.pass.cpp
index 323072da14463b..30e9672a256830 100644
--- a/libcxx/test/libcxx/atomics/atomics.syn/compatible_with_stdatomic.compile.pass.cpp
+++ b/libcxx/test/libcxx/atomics/atomics.syn/compatible_with_stdatomic.compile.pass.cpp
@@ -7,15 +7,16 @@
 //===----------------------------------------------------------------------===//
 
 // UNSUPPORTED: no-threads
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
 
 // XFAIL: FROZEN-CXX03-HEADERS-FIXME
 
-// This test verifies that <stdatomic.h> redirects to <atomic>. As an extension,
-// libc++ enables this redirection even before C++23.
+// This test verifies that <stdatomic.h> redirects to <atomic>.
 
-// Ordinarily, <stdatomic.h> can be included after <atomic>, but including it
-// first doesn't work because its macros break <atomic>. Verify that
-// <stdatomic.h> can be included first.
+// Before C++23, <stdatomic.h> can be included after <atomic>, but including it
+// first doesn't work because its macros break <atomic>. Fixing that is the point
+// of the C++23 change that added <stdatomic.h> to C++. Thus, this test verifies
+// that <stdatomic.h> can be included first.
 #include <stdatomic.h>
 #include <atomic>
 
diff --git a/libcxx/test/libcxx/atomics/atomics.syn/incompatible_with_stdatomic.verify.cpp b/libcxx/test/libcxx/atomics/atomics.syn/incompatible_with_stdatomic.verify.cpp
new file mode 100644
index 00000000000000..ca092d9c602753
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/atomics.syn/incompatible_with_stdatomic.verify.cpp
@@ -0,0 +1,22 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-threads
+// REQUIRES: c++03 || c++11 || c++14 || c++17 || c++20
+
+// This test ensures that we issue a reasonable diagnostic when including <atomic> after
+// <stdatomic.h> has been included. Before C++23, this otherwise leads to obscure errors
+// because <atomic> may try to redefine things defined by <stdatomic.h>.
+
+// Ignore additional weird errors that happen when the two headers are mixed.
+// ADDITIONAL_COMPILE_FLAGS: -Xclang -verify-ignore-unexpected=error -Xclang -verify-ignore-unexpected=warning
+
+#include <stdatomic.h>
+#include <atomic>
+
+// expected-error@*:* {{<atomic> is incompatible with <stdatomic.h> before C++23.}}
diff --git a/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.compile.pass.cpp b/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.compile.pass.cpp
new file mode 100644
index 00000000000000..6df80daf9414e5
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.compile.pass.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-threads
+
+// This test ensures that we don't hijack the <stdatomic.h> header (e.g. by providing
+// an empty header) even when compiling before C++23, since some users were using the
+// Clang or platform provided header before libc++ added its own.
+
+// On GCC, the compiler-provided <stdatomic.h> is not C++ friendly, so including <stdatomic.h>
+// doesn't work at all if we don't use the <stdatomic.h> provided by libc++ in C++23 and above.
+// XFAIL: (c++11 || c++14 || c++17 || c++20) && gcc
+
+#include <stdatomic.h>
+
+void f() {
+  atomic_int i; // just make sure the header isn't empty
+  (void)i;
+}
diff --git a/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.cxx23.compile.pass.cpp b/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.cxx23.compile.pass.cpp
new file mode 100644
index 00000000000000..9a30b439c96c9b
--- /dev/null
+++ b/libcxx/test/libcxx/atomics/stdatomic.h.syn/dont_hijack_header.cxx23.compile.pass.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: no-threads
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
+// This test verifies that <stdatomic.h> DOES NOT redirect to <atomic> before C++23,
+// since doing so is a breaking change. Several things can break when that happens,
+// because the type of _Atomic(T) changes from _Atomic(T) to std::atomic<T>.
+//
+// For example, redeclarations can become invalid depending on whether they
+// have been declared with <stdatomic.h> in scope or not.
+
+// REQUIRES: c++03 || c++11 || c++14 || c++17 || c++20
+
+#include <atomic>
+#include <stdatomic.h>
+#include <type_traits>
+
+static_assert(!std::is_same<_Atomic(int), std::atomic<int> >::value, "");

@ldionne
Copy link
Member Author

ldionne commented Jan 15, 2025

Another example of valid code that works without the extension and breaks with the extension:

// REQUIRES: c++03 || c++11 || c++14 || c++17 || c++20

struct Foo {
  _Atomic(int) f() const;
};

#include <stdatomic.h>

_Atomic(int) Foo::f() const; // This declaration would differ from the previous one in C++23

@rprichard
Copy link
Contributor

Yeah, removing the extension from upstream seems OK to me. I suspect the Android toolchain situation around stdatomic.h will continue to be complicated, but maybe we can upgrade enough stuff to C++23 and drop Android's historic extension?

Another example of valid code that works without the extension and breaks with the extension:

// REQUIRES: c++03 || c++11 || c++14 || c++17 || c++20

struct Foo {
  _Atomic(int) f() const;
};

#include <stdatomic.h>

_Atomic(int) Foo::f() const; // This declaration would differ from the previous one in C++23

FWIW, I think this code is accepted by Clang prior to the stdatomic.h extension PR, but not by other compilers. i.e. It's a Clang extension to allow C11-style atomics in a C++ language dialect (as long as <atomic> isn't included!).

@@ -592,6 +592,10 @@ template <class T>
#else
# include <__config>

# if _LIBCPP_STD_VER < 23 && defined(_LIBCPP_STDATOMIC_H)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW: Android might be better served by checking for a macro like kill_dependency or atomic_load instead of _LIBCPP_STDATOMIC_H. (The previous version of this compatibility check looked for kill_dependency. #83351) Using kill_dependency or atomic_load, the Bionic stdatomic.h and the libc++ atomic headers could be included simultaneously.

The Android/Bionic stdatomic.h never defines kill_dependency. It does define atomic_load for the C dialect, but for C++, it does not define atomic_load, because it delegates to atomic instead and has a using std::atomic_load; declaration. (The Bionic stdatomic.h has delegated to atomic since 2014 and was the inspiration for the current C++23 behavior.)

Maybe it's possible for Bionic to drop its C++ support from its stdatomic.h, by switching users to C++23 instead. That seems like a good goal in the long-run. I think Android can carry a local LLVM patch, though, too, if we need to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know what you think about the latest update.

@ldionne ldionne added the pending-ci Merging the PR is only pending completion of CI label Jan 17, 2025
@ldionne ldionne merged commit bbd871e into llvm:main Jan 17, 2025
77 checks passed
@ldionne ldionne deleted the review/stdatomic-no-cxx20 branch January 17, 2025 19:22
@zmodem
Copy link
Collaborator

zmodem commented Jan 23, 2025

Still debugging, but I wanted to give a heads up that we're hitting build issues after this, e.g.

../../third_party/libc++/src/include/__memory/shared_ptr.h:1496:35: error: expected expression
 1496 | inline _LIBCPP_HIDE_FROM_ABI bool atomic_is_lock_free(const shared_ptr<_Tp>*) {
      |                                   ^
../../third_party/llvm-build/Release+Asserts/lib/clang/20/include/stdatomic.h:90:73: note: expanded from macro 'atomic_is_lock_free'
   90 | #define atomic_is_lock_free(obj) __c11_atomic_is_lock_free(sizeof(*(obj)))

I think that before this change, we got the atomic_is_lock_free definition from libcxx/include/stdatomic.h, which is a template, but now the #include_next is giving us clang's clang/lib/Headers/stdatomic.h which defines the macro instead.

@ldionne
Copy link
Member Author

ldionne commented Jan 27, 2025

That would make sense: this change goes back to the status quo that C's <stdatomic.h> is incompatible with <atomic> before C++23, and you must have been relying on that problem being fixed. This patch explains why we can't do that before C++23, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. pending-ci Merging the PR is only pending completion of CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants