Skip to content

Conversation

philnik777
Copy link
Contributor

@philnik777 philnik777 commented Aug 21, 2025

LLVM 20 contained an ABI break that can result in the size of std::unordered_{map,set,multimap,multiset} and std::deque changing when used with an allocator type that is empty and contains a base class that is the same across rebound allocator instantiations (e.g. Allocator<int> and Allocator<char> are both empty and contain the same base class).

In addition, the layout of a user-defined type that:
- contains one of the following containers: std::unordered_{map,set,multimap,multiset}, std::deque, std::map, std::set, std::multimap, std::multiset, std::list or std::vector, and
- passes an empty allocator, comparator or hasher type to that container, and
- has a member of that same empty allocator, comparator or hasher type inside the enclosing struct, and
- that member is either marked with [[no_unique_address]] or optimized out via the EBO (empty base optimization) technique
saw its size increase from LLVM 19 to LLVM 20. This was caused by the usage of [[no_unique_address]] within some of libc++'s containers in a way that allowed subtle interactions with enclosing objects. This is fixed in LLVM 21 on Clang (returning to the LLVM 19 ABI), however that implies an ABI break from LLVM 20 to LLVM 21.
Furthermore, fixing this causes a slight regression to constant evaluation support in std::unique_ptr. Specifically, constant evaluation will now fail when the deleter relies on being value-initialized for constant-evaluation admissibility. If a default-initialized deleter can be used during constant evaluation, or if the default constructor is non-trivial, the unique_ptr is not affected by this regression. In particular, this regression does not impact any unique_ptr using the default deleter.

Note that there is currently no way to realistically fix this ABI break on GCC, therefore GCC will remain on the ABI introduced in LLVM 19. That also means that Clang and GCC will have a slightly different ABI for the small subset of types listed above until we are able to apply the same fix we did with Clang on GCC.

We fix this regression by surrounding the members of the _LIBCPP_COMPRESSED_PAIR with an anonymous struct. This restricts the shifting of empty types to the front of the _LIBCPP_COMPRESSED_PAIR instead of throughout the surrounding object. This "frees up" the zero offset to contain another object of the same type, restoring the ability to perform EBO or to elide the storage for a type with [[no_unique_address]] in the enclosing (user-defined) struct.

Fixes #154146

@philnik777 philnik777 changed the title fix abi break [libc++] Fix ABI break introduced by switching to _LIBCPP_COMPRESSED_PAIR Aug 21, 2025
@github-actions
Copy link

github-actions bot commented Aug 21, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@philnik777 philnik777 force-pushed the fix_abi_break branch 2 times, most recently from 6f3c374 to 5d9b6ae Compare August 21, 2025 11:54
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the attribute is needed anymore now that we have a struct enclosing it. However, I wouldn't make that change now, I would do it in a followup patch since that wouldn't be an ABI change, just a simplification.

@philnik777 philnik777 force-pushed the fix_abi_break branch 3 times, most recently from 92355d0 to b07285a Compare August 22, 2025 13:27
@ldionne ldionne marked this pull request as ready for review August 22, 2025 13:27
@ldionne ldionne requested a review from a team as a code owner August 22, 2025 13:27
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Aug 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 22, 2025

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes

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

14 Files Affected:

  • (modified) libcxx/docs/ReleaseNotes/21.rst (+14)
  • (modified) libcxx/include/__memory/compressed_pair.h (+38-15)
  • (modified) libcxx/include/string (+12-2)
  • (modified) libcxx/test/libcxx/containers/associative/map/abi.compile.pass.cpp (+1-3)
  • (modified) libcxx/test/libcxx/containers/associative/set/abi.compile.pass.cpp (+1-3)
  • (modified) libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp (+2-4)
  • (modified) libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp (+2-4)
  • (modified) libcxx/test/libcxx/containers/sequences/deque/abi.compile.pass.cpp (+2-4)
  • (modified) libcxx/test/libcxx/containers/sequences/list/abi.compile.pass.cpp (+1-3)
  • (modified) libcxx/test/libcxx/containers/sequences/vector.bool/abi.compile.pass.cpp (+1-3)
  • (modified) libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp (+1-3)
  • (modified) libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/default.pass.cpp (+4)
  • (modified) libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/nullptr.pass.cpp (+4)
  • (modified) libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/pointer.pass.cpp (+4)
diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst
index 91123ffa3e34b..1eaba67b18465 100644
--- a/libcxx/docs/ReleaseNotes/21.rst
+++ b/libcxx/docs/ReleaseNotes/21.rst
@@ -148,6 +148,20 @@ ABI Affecting Changes
   comparison between shared libraries, since all RTTI has the correct visibility now. There is no behaviour change on
   Clang.
 
+- LLVM 20 contains an ABI break when
+  (1) using empty allocators which derive from the same base class, or
+  (2) having an object of the same type as an empty member in one of the following classes that could be layed out at
+      the beginnig of the object
+
+  (1) applies to ``unordered_map``, ``unordered_set``, ``unordered_multimap``, ``unordered_multiset``  and ``deque``.
+  (2) applies to the containers listed in (1) as well as ``map``, ``set``, ``multimap``, ``multiset``, ``list`` and
+      ``vector``.
+
+  Fixing this causes a regression in ``unique_ptr``. Specifically, constant evaluation fails when the deleter relies on
+  being value-initialized. If the deleter works if it is default initialized or is not trivially default constructible
+  it is not affected.
+
+  For more details see https://llvm.org/PR154146.
 
 Build System Changes
 --------------------
diff --git a/libcxx/include/__memory/compressed_pair.h b/libcxx/include/__memory/compressed_pair.h
index fb7b7b7afcc8c..220dafe856716 100644
--- a/libcxx/include/__memory/compressed_pair.h
+++ b/libcxx/include/__memory/compressed_pair.h
@@ -80,21 +80,44 @@ class __compressed_pair_padding {
 template <class _ToPad>
 class __compressed_pair_padding<_ToPad, true> {};
 
-#  define _LIBCPP_COMPRESSED_PAIR(T1, Initializer1, T2, Initializer2)                                                  \
-    _LIBCPP_NO_UNIQUE_ADDRESS __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>))) T1 Initializer1;    \
-    _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);          \
-    _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                         \
-    _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _)
-
-#  define _LIBCPP_COMPRESSED_TRIPLE(T1, Initializer1, T2, Initializer2, T3, Initializer3)                              \
-    _LIBCPP_NO_UNIQUE_ADDRESS                                                                                          \
-    __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>),                                                \
-                   __aligned__(::std::__compressed_pair_alignment<T3>))) T1 Initializer1;                              \
-    _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);          \
-    _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                         \
-    _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _);          \
-    _LIBCPP_NO_UNIQUE_ADDRESS T3 Initializer3;                                                                         \
-    _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T3> _LIBCPP_CONCAT3(__padding3_, __LINE__, _)
+#  ifdef _LIBCPP_COMPILER_GCC
+#    define _LIBCPP_COMPRESSED_PAIR(T1, Initializer1, T2, Initializer2)                                                \
+      _LIBCPP_NO_UNIQUE_ADDRESS __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>))) T1 Initializer1;  \
+      _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);        \
+      _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                       \
+      _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _)
+
+#    define _LIBCPP_COMPRESSED_TRIPLE(T1, Initializer1, T2, Initializer2, T3, Initializer3)                            \
+      _LIBCPP_NO_UNIQUE_ADDRESS                                                                                        \
+      __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>),                                              \
+                     __aligned__(::std::__compressed_pair_alignment<T3>))) T1 Initializer1;                            \
+      _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);        \
+      _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                       \
+      _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _);        \
+      _LIBCPP_NO_UNIQUE_ADDRESS T3 Initializer3;                                                                       \
+      _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T3> _LIBCPP_CONCAT3(__padding3_, __LINE__, _)
+#  else
+#    define _LIBCPP_COMPRESSED_PAIR(T1, Initializer1, T2, Initializer2)                                                \
+      struct {                                                                                                         \
+        _LIBCPP_NO_UNIQUE_ADDRESS                                                                                      \
+        __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>))) T1 Initializer1;                          \
+        _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);      \
+        _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                     \
+        _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _);      \
+      }
+
+#    define _LIBCPP_COMPRESSED_TRIPLE(T1, Initializer1, T2, Initializer2, T3, Initializer3)                            \
+      struct {                                                                                                         \
+        _LIBCPP_NO_UNIQUE_ADDRESS                                                                                      \
+        __attribute__((__aligned__(::std::__compressed_pair_alignment<T2>),                                            \
+                       __aligned__(::std::__compressed_pair_alignment<T3>))) T1 Initializer1;                          \
+        _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T1> _LIBCPP_CONCAT3(__padding1_, __LINE__, _);      \
+        _LIBCPP_NO_UNIQUE_ADDRESS T2 Initializer2;                                                                     \
+        _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T2> _LIBCPP_CONCAT3(__padding2_, __LINE__, _);      \
+        _LIBCPP_NO_UNIQUE_ADDRESS T3 Initializer3;                                                                     \
+        _LIBCPP_NO_UNIQUE_ADDRESS ::std::__compressed_pair_padding<T3> _LIBCPP_CONCAT3(__padding3_, __LINE__, _);      \
+      }
+#  endif
 
 #else
 #  define _LIBCPP_COMPRESSED_PAIR(T1, Name1, T2, Name2)                                                                \
diff --git a/libcxx/include/string b/libcxx/include/string
index 3c1523eb5de33..1d197654b9fee 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -974,7 +974,12 @@ public:
 
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string()
       _NOEXCEPT_(is_nothrow_default_constructible<allocator_type>::value)
-      : __rep_() {
+#  if _LIBCPP_STD_VER >= 20 // TODO(LLVM 23): Remove this condition; this is a workaround for https://llvm.org/PR154567
+      : __rep_(__short())
+#  else
+      : __rep_()
+#  endif
+  {
     __annotate_new(0);
   }
 
@@ -984,7 +989,12 @@ public:
 #  else
       _NOEXCEPT
 #  endif
-      : __rep_(), __alloc_(__a) {
+#  if _LIBCPP_STD_VER >= 20 // TODO(LLVM 23): Remove this condition; this is a workaround for https://llvm.org/PR154567
+      : __rep_(__short()),
+#  else
+      : __rep_(),
+#  endif
+        __alloc_(__a) {
     __annotate_new(0);
   }
 
diff --git a/libcxx/test/libcxx/containers/associative/map/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/map/abi.compile.pass.cpp
index 04c1802bc84f6..908338631bb51 100644
--- a/libcxx/test/libcxx/containers/associative/map/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/associative/map/abi.compile.pass.cpp
@@ -8,8 +8,6 @@
 
 // UNSUPPORTED: libcpp-abi-no-compressed-pair-padding
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <map>
 
@@ -90,7 +88,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 32, "");
+static_assert(sizeof(user_struct) == 24, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(map_alloc<int, std::allocator<std::pair<const int, int> > >) == 24, "");
diff --git a/libcxx/test/libcxx/containers/associative/set/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/set/abi.compile.pass.cpp
index 1b85bb7c93773..626f573967efb 100644
--- a/libcxx/test/libcxx/containers/associative/set/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/associative/set/abi.compile.pass.cpp
@@ -8,8 +8,6 @@
 
 // UNSUPPORTED: libcpp-abi-no-compressed-pair-padding
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <set>
 
@@ -90,7 +88,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 32, "");
+static_assert(sizeof(user_struct) == 24, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(set_alloc<int, std::allocator<int> >) == 24, "");
diff --git a/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp
index 0476872487036..e6d27d245c472 100644
--- a/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/associative/unord.map/abi.compile.pass.cpp
@@ -12,8 +12,6 @@
 // unordered containers changes when bounded unique_ptr is enabled.
 // UNSUPPORTED: libcpp-has-abi-bounded-unique_ptr
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <unordered_map>
 
@@ -94,7 +92,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 48, "");
+static_assert(sizeof(user_struct) == 40, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(unordered_map_alloc<int, std::allocator<std::pair<const int, int> > >) == 40, "");
@@ -126,7 +124,7 @@ struct TEST_ALIGNAS(32) AlignedHash {};
 struct UnalignedEqualTo {};
 
 // This part of the ABI has been broken between LLVM 19 and LLVM 20.
-static_assert(sizeof(std::unordered_map<int, int, AlignedHash, UnalignedEqualTo>) == 64, "");
+static_assert(sizeof(std::unordered_map<int, int, AlignedHash, UnalignedEqualTo>) == 96, "");
 static_assert(TEST_ALIGNOF(std::unordered_map<int, int, AlignedHash, UnalignedEqualTo>) == 32, "");
 
 #elif __SIZE_WIDTH__ == 32
diff --git a/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp
index e9d88a126de6c..1658e7e33f850 100644
--- a/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/associative/unord.set/abi.compile.pass.cpp
@@ -12,8 +12,6 @@
 // unordered containers changes when bounded unique_ptr is enabled.
 // UNSUPPORTED: libcpp-has-abi-bounded-unique_ptr
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <unordered_set>
 
@@ -94,7 +92,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 48, "");
+static_assert(sizeof(user_struct) == 40, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(unordered_set_alloc<int, std::allocator<int> >) == 40, "");
@@ -125,7 +123,7 @@ struct TEST_ALIGNAS(32) AlignedHash {};
 struct UnalignedEqualTo {};
 
 // This part of the ABI has been broken between LLVM 19 and LLVM 20.
-static_assert(sizeof(std::unordered_set<int, AlignedHash, UnalignedEqualTo>) == 64, "");
+static_assert(sizeof(std::unordered_set<int, AlignedHash, UnalignedEqualTo>) == 96, "");
 static_assert(TEST_ALIGNOF(std::unordered_set<int, AlignedHash, UnalignedEqualTo>) == 32, "");
 
 #elif __SIZE_WIDTH__ == 32
diff --git a/libcxx/test/libcxx/containers/sequences/deque/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/abi.compile.pass.cpp
index 38f957f416c87..a19897cbb1588 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/abi.compile.pass.cpp
@@ -8,8 +8,6 @@
 
 // UNSUPPORTED: libcpp-abi-no-compressed-pair-padding
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <deque>
 
@@ -88,14 +86,14 @@ static_assert(sizeof(std::deque<int, min_allocator<int> >) == 48, "");
 static_assert(sizeof(std::deque<int, test_allocator<int> >) == 80, "");
 static_assert(sizeof(std::deque<int, small_iter_allocator<int> >) == 12, "");
 static_assert(sizeof(std::deque<int, final_small_iter_allocator<int> >) == 16, "");
-static_assert(sizeof(std::deque<int, common_base_allocator<int> >) == 56, "");
+static_assert(sizeof(std::deque<int, common_base_allocator<int> >) == 48, "");
 
 static_assert(sizeof(std::deque<char>) == 48, "");
 static_assert(sizeof(std::deque<char, min_allocator<char> >) == 48, "");
 static_assert(sizeof(std::deque<char, test_allocator<char> >) == 80, "");
 static_assert(sizeof(std::deque<char, small_iter_allocator<char> >) == 12, "");
 static_assert(sizeof(std::deque<char, final_small_iter_allocator<char> >) == 16, "");
-static_assert(sizeof(std::deque<char, common_base_allocator<char> >) == 56, "");
+static_assert(sizeof(std::deque<char, common_base_allocator<char> >) == 48, "");
 
 static_assert(TEST_ALIGNOF(std::deque<int>) == 8, "");
 static_assert(TEST_ALIGNOF(std::deque<int, min_allocator<int> >) == 8, "");
diff --git a/libcxx/test/libcxx/containers/sequences/list/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/list/abi.compile.pass.cpp
index 1737497ab159e..ad10051e514fe 100644
--- a/libcxx/test/libcxx/containers/sequences/list/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/list/abi.compile.pass.cpp
@@ -6,8 +6,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <list>
 
@@ -65,7 +63,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 32, "");
+static_assert(sizeof(user_struct) == 24, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(std::list<int>) == 24, "");
diff --git a/libcxx/test/libcxx/containers/sequences/vector.bool/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector.bool/abi.compile.pass.cpp
index 97e09ab4ca868..109e97a3fe8be 100644
--- a/libcxx/test/libcxx/containers/sequences/vector.bool/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector.bool/abi.compile.pass.cpp
@@ -8,8 +8,6 @@
 
 // UNSUPPORTED: libcpp-abi-no-compressed-pair-padding
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <vector>
 
@@ -67,7 +65,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 32, "");
+static_assert(sizeof(user_struct) == 24, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(std::vector<bool>) == 24, "");
diff --git a/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp
index 879e143510752..1a55d1359eeb0 100644
--- a/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector/abi.compile.pass.cpp
@@ -6,8 +6,6 @@
 //
 //===----------------------------------------------------------------------===//
 
-// XFAIL: FROZEN-CXX03-HEADERS-FIXME
-
 #include <cstdint>
 #include <vector>
 
@@ -73,7 +71,7 @@ struct user_struct {
 };
 
 #if __SIZE_WIDTH__ == 64
-static_assert(sizeof(user_struct) == 32, "");
+static_assert(sizeof(user_struct) == 24, "");
 static_assert(TEST_ALIGNOF(user_struct) == 8, "");
 
 static_assert(sizeof(std::vector<int>) == 24, "");
diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/default.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/default.pass.cpp
index 7f4c90922d6c3..e2abfd17d27c3 100644
--- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/default.pass.cpp
+++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/default.pass.cpp
@@ -82,6 +82,10 @@ TEST_CONSTEXPR_CXX23 bool test_basic() {
     p.get_deleter().set_state(5);
     assert(p.get_deleter().state() == 5);
   }
+// TODO: Remove this check once https://llvm.org/PR154567 is fixed
+#if TEST_STD_VER >= 23 && defined(TEST_COMPILER_CLANG)
+  if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
   {
     std::unique_ptr<ElemType, DefaultCtorDeleter<ElemType> > p;
     assert(p.get() == 0);
diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/nullptr.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/nullptr.pass.cpp
index 45017a03b95dd..f5e0541684d13 100644
--- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/nullptr.pass.cpp
+++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/nullptr.pass.cpp
@@ -47,6 +47,10 @@ TEST_CONSTEXPR_CXX23 void test_basic() {
     assert(p.get() == 0);
     assert(p.get_deleter().state() == 0);
   }
+// TODO: Remove this check once https://llvm.org/PR154567 is fixed
+#if TEST_STD_VER >= 23 && defined(TEST_COMPILER_CLANG)
+  if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
   {
     std::unique_ptr<VT, DefaultCtorDeleter<VT> > p(nullptr);
     assert(p.get() == 0);
diff --git a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/pointer.pass.cpp b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/pointer.pass.cpp
index cbce5c9c74c5a..e9912d4574af6 100644
--- a/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/pointer.pass.cpp
+++ b/libcxx/test/std/utilities/smartptr/unique.ptr/unique.ptr.class/unique.ptr.ctor/pointer.pass.cpp
@@ -75,6 +75,10 @@ TEST_CONSTEXPR_CXX23 void test_pointer() {
   }
   if (!TEST_IS_CONSTANT_EVALUATED)
     assert(A::count == 0);
+// TODO: Remove this check once https://llvm.org/PR154567 is fixed
+#if TEST_STD_VER >= 23 && defined(TEST_COMPILER_CLANG)
+  if (!TEST_IS_CONSTANT_EVALUATED)
+#endif
   {
     A* p = newValue<ValueT>(expect_alive);
     if (!TEST_IS_CONSTANT_EVALUATED)

Copy link
Member

Choose a reason for hiding this comment

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

Is that still the case? I thought we were fixing this (accepted) ABI break at the same time?

@philnik777 philnik777 force-pushed the fix_abi_break branch 2 times, most recently from 275999a to 67fc781 Compare August 22, 2025 15:55
Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

Thanks a lot for fixing this. LGTM once CI is green. Once this is landed, we'll also cherry-pick it to the LLVM 21 release.

@philnik777
Copy link
Contributor Author

@Michael137 Are the lldb failures related? (Even if they are I'll land this, since it's very time sensitive, just want to know whether there is follow up work to do)

…PAIR

Update libcxx/docs/ReleaseNotes/21.rst

Co-authored-by: Louis Dionne <[email protected]>
@philnik777 philnik777 merged commit f5e687d into llvm:main Aug 23, 2025
72 of 79 checks passed
@github-project-automation github-project-automation bot moved this from Needs Merge to Done in LLVM Release Status Aug 23, 2025
Comment on lines +50 to +53
// TODO: Remove this check once https://llvm.org/PR154567 is fixed
#if TEST_STD_VER >= 23 && defined(TEST_COMPILER_CLANG)
if (!TEST_IS_CONSTANT_EVALUATED)
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

#154567 has been fixed already. Is this still needed/desired?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we support older compilers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant that this deviates from the // TODO(LLVM 23): format elsewhere in this PR, and wasn't clear to me why, hence the question

@boomanaiden154
Copy link
Contributor

boomanaiden154 commented Aug 23, 2025

The LLDB formatter failures are showing up in premerge. It would be nice if they could be fixed soonish.

@Michael137
Copy link
Member

The LLDB formatter failures are showing up in premerge. It would be nice if they could be fixed soonish.

I'll only be able to take a look evening UK time today at the earliest

@slydiman
Copy link
Contributor

@Michael137
Copy link
Member

Note the following buildbots are broken

https://lab.llvm.org/buildbot/#/builders/195/builds/13641 https://lab.llvm.org/buildbot/#/builders/197/builds/8319

Will put a fix up later today

@Michael137
Copy link
Member

Will need to go through a proper round of reviews but here's the preliminary patch: #155153

Michael137 added a commit to Michael137/llvm-project that referenced this pull request Aug 24, 2025
Starting with llvm#154686 the compressed_pair
children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.
@philnik777
Copy link
Contributor Author

/cherry-pick f5e687d

@llvmbot
Copy link
Member

llvmbot commented Aug 25, 2025

Failed to cherry-pick: f5e687d

https://github.com/llvm/llvm-project/actions/runs/17202429330

Please manually backport the fix and push it to your github fork. Once this is done, please create a pull request

@philnik777
Copy link
Contributor Author

@tru This should cherry-pick cleanly once #154945 is merged. Do you have any preference on how exactly this should be done? Just wait for the other PR to be merged and then create a new PR, or cherry-pick both onto a branch and let them be merged together? Something different?

philnik777 added a commit to philnik777/llvm-project that referenced this pull request Aug 25, 2025
…PAIR (llvm#154686)

LLVM 20 contained an ABI break that can result in the size of
`std::unordered_{map,set,multimap,multiset}` and `std::deque` changing
when used with an allocator type that is empty and contains a base class
that is the same across rebound allocator instantiations (e.g.
``Allocator<int>`` and ``Allocator<char>`` are both empty and contain
the same base class).

In addition, the layout of a user-defined type that:
- contains one of the following containers:
`std::unordered_{map,set,multimap,multiset}`, `std::deque`, `std::map`,
`std::set`, `std::multimap`, `std::multiset`, `std::list` or
`std::vector`, and
- passes an empty allocator, comparator or hasher type to that
container, and
- has a member of that same empty allocator, comparator or hasher type
inside the enclosing struct, and
- that member is either marked with `[[no_unique_address]]` or optimized
out via the EBO (empty base optimization) technique
saw its size increase from LLVM 19 to LLVM 20. This was caused by the
usage of `[[no_unique_address]]` within some of libc++'s containers in a
way that allowed subtle interactions with enclosing objects. This is
fixed in LLVM 21 on Clang (returning to the LLVM 19 ABI), however that
implies an ABI break from LLVM 20 to LLVM 21.
Furthermore, fixing this causes a slight regression to constant
evaluation support in `std::unique_ptr`. Specifically, constant
evaluation will now fail when the deleter relies on being
value-initialized for constant-evaluation admissibility. If a
default-initialized deleter can be used during constant evaluation, or
if the default constructor is non-trivial, the `unique_ptr` is not
affected by this regression. In particular, this regression does not
impact any `unique_ptr` using the default deleter.

Note that there is currently no way to realistically fix this ABI break
on GCC, therefore GCC will remain on the ABI introduced in LLVM 19. That
also means that Clang and GCC will have a slightly different ABI for the
small subset of types listed above until we are able to apply the same
fix we did with Clang on GCC.

We fix this regression by surrounding the members of the
`_LIBCPP_COMPRESSED_PAIR` with an anonymous struct. This restricts the
shifting of empty types to the front of the `_LIBCPP_COMPRESSED_PAIR`
instead of throughout the surrounding object. This "frees up" the zero
offset to contain another object of the same type, restoring the ability
to perform EBO or to elide the storage for a type with
`[[no_unique_address]]` in the enclosing (user-defined) struct.

Fixes llvm#154146

Co-authored-by: Louis Dionne <[email protected]>
adrian-prantl pushed a commit that referenced this pull request Aug 25, 2025
…155153)

Starting with #154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Aug 25, 2025
…IR layout (#155153)

Starting with llvm/llvm-project#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.
@h-vetinari
Copy link
Contributor

/cherry-pick f5e687d

@llvmbot
Copy link
Member

llvmbot commented Aug 25, 2025

/pull-request #155324

tru pushed a commit to llvmbot/llvm-project that referenced this pull request Aug 26, 2025
…lvm#155153)

Starting with llvm#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.

(cherry picked from commit 20dd053)
Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Aug 26, 2025
…lvm#155153)

Starting with llvm#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.

(cherry picked from commit 20dd053)
Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Aug 26, 2025
…lvm#155153)

Starting with llvm#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.

(cherry picked from commit 20dd053)
(cherry picked from commit f3ec20b)
Michael137 added a commit to swiftlang/llvm-project that referenced this pull request Aug 26, 2025
…lvm#155153)

Starting with llvm#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.

(cherry picked from commit 20dd053)
(cherry picked from commit f3ec20b)
MarkMurrayARM pushed a commit to arm/arm-toolchain that referenced this pull request Aug 28, 2025
…IR layout (#155153)

Starting with llvm/llvm-project#154686 the
compressed_pair children are now wrapped in an anonymous structure.

This patch adjusts the LLDB data-formatters to support that.

Outstanding questions:
1. Should GetChildMemberWithName look through anonymous structures? That
will break users most likely. But maybe introducing a new API is worth
it? Then we wouldnt have to do this awkward passing around of
`anon_struct_index`
2. Do we support the layout without the anonymous structure? It's not
too much added complexity. And we did release that version of libc++, so
there is code out there compiled against it. But there is no great way
of testing it (some of our macOS matrix bots do test it i suppose, but
not in a targeted way). We have the layout "simulator" tests for some of
the STL types which I will adjust.

(cherry picked from commit 20dd053)
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.

Projects

Development

Successfully merging this pull request may close these issues.

[libc++] ABI break: std::deque changes its size in LLVM 21

7 participants