Skip to content

[libc++] Fix triviality of std::pair for trivially copyable types without an assignment operator #95444

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 5 commits into from
Jun 19, 2024

Conversation

ldionne
Copy link
Member

@ldionne ldionne commented Jun 13, 2024

Since 83ead2b, std::pair would not be trivially copyable when it holds a trivially copyable type without an assignment operator. That is because pair gained an elligible copy-assignment-operator (the const version) in 83ead2b in C++ >= 23.

This means that the trivially copyable property of std::pair for such types would be inconsistent between C++11/14/17/20 (trivially copyable) and C++23/26 (not trivially copyable). This patch makes std::pair's behavior consistent in all Standard modes EXCEPT C++03, which is a pre-existing condition and we have no way of changing (also, it shouldn't matter because the std::is_trivially_copyable trait was introduced in C++11).

While this is not technically an ABI break, in practice we do know that folks sometimes use a different representation based on whether a type is trivially copyable. So we're treating 83ead2b as an ABI break and this patch is fixing said breakage.

This patch also adds tests stolen from #89652 that pin down the ABI of std::pair with respect to being trivially copyable.

Fixes #95428

@ldionne ldionne requested a review from a team as a code owner June 13, 2024 17:50
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jun 13, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 13, 2024

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

Changes

Since 83ead2b, std::pair would not be trivially copyable when it holds a trivially copyable type without an assignment operator. That is because pair gained an elligible copy-assignment-operator (the const version) in 83ead2b in C++ >= 23.

This means that the trivially copyable property of std::pair for such types would be inconsistent between C++11/14/17/20 (trivially copyable) and C++23/26 (not trivially copyable). This patch makes std::pair's behavior consistent in all Standard modes EXCEPT C++03, which is a pre-existing condition and we have no way of changing (also, it shouldn't matter because the std::is_trivially_copyable trait was introduced in C++11).

While this is not technically an ABI break, in practice we do know that folks sometimes use a different representation based on whether a type is trivially copyable. So we're treating 83ead2b as an ABI break and this patch is fixing said breakage.

This patch also adds tests stolen from #89652 that pin down the ABI of std::pair with respect to being trivially copyable.

Fixes #95428


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

4 Files Affected:

  • (modified) libcxx/include/__utility/pair.h (+2)
  • (renamed) libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.non_trivial_copy_move.pass.cpp ()
  • (renamed) libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivial_copy_move.pass.cpp (+31)
  • (added) libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp (+56)
diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 3ffab2f7fe4f6..380a2a21b9d4b 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -261,6 +261,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
   }
 
 #  if _LIBCPP_STD_VER >= 23
+  template <class = void>
   _LIBCPP_HIDE_FROM_ABI constexpr const pair& operator=(pair const& __p) const
       noexcept(is_nothrow_copy_assignable_v<const first_type> && is_nothrow_copy_assignable_v<const second_type>)
     requires(is_copy_assignable_v<const first_type> && is_copy_assignable_v<const second_type>)
@@ -270,6 +271,7 @@ struct _LIBCPP_TEMPLATE_VIS pair
     return *this;
   }
 
+  template <class = void>
   _LIBCPP_HIDE_FROM_ABI constexpr const pair& operator=(pair&& __p) const
       noexcept(is_nothrow_assignable_v<const first_type&, first_type> &&
                is_nothrow_assignable_v<const second_type&, second_type>)
diff --git a/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/non_trivial_copy_move_ABI.pass.cpp b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.non_trivial_copy_move.pass.cpp
similarity index 100%
rename from libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/non_trivial_copy_move_ABI.pass.cpp
rename to libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.non_trivial_copy_move.pass.cpp
diff --git a/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivial_copy_move_ABI.pass.cpp b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivial_copy_move.pass.cpp
similarity index 79%
rename from libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivial_copy_move_ABI.pass.cpp
rename to libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivial_copy_move.pass.cpp
index fe098a7af7e8f..3ec60c08b8eab 100644
--- a/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/trivial_copy_move_ABI.pass.cpp
+++ b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivial_copy_move.pass.cpp
@@ -71,6 +71,17 @@ struct Trivial {
 static_assert(HasTrivialABI<Trivial>::value, "");
 #endif
 
+struct TrivialNoAssignment {
+  int arr[4];
+  TrivialNoAssignment& operator=(const TrivialNoAssignment&) = delete;
+};
+
+struct TrivialNoConstruction {
+  int arr[4];
+  TrivialNoConstruction()                                        = default;
+  TrivialNoConstruction(const TrivialNoConstruction&)            = delete;
+  TrivialNoConstruction& operator=(const TrivialNoConstruction&) = default;
+};
 
 void test_trivial()
 {
@@ -135,6 +146,26 @@ void test_trivial()
         static_assert(HasTrivialABI<P>::value, "");
     }
 #endif
+    {
+        using P = std::pair<TrivialNoAssignment, int>;
+        static_assert(std::is_trivially_copy_constructible<P>::value, "");
+        static_assert(std::is_trivially_move_constructible<P>::value, "");
+#if TEST_STD_VER >= 11 // This is https://llvm.org/PR90605
+        static_assert(!std::is_trivially_copy_assignable<P>::value, "");
+        static_assert(!std::is_trivially_move_assignable<P>::value, "");
+#endif // TEST_STD_VER >= 11
+        static_assert(std::is_trivially_destructible<P>::value, "");
+    }
+    {
+        using P = std::pair<TrivialNoConstruction, int>;
+#if TEST_STD_VER >= 11
+        static_assert(!std::is_trivially_copy_constructible<P>::value, "");
+        static_assert(!std::is_trivially_move_constructible<P>::value, "");
+#endif // TEST_STD_VER >= 11
+        static_assert(!std::is_trivially_copy_assignable<P>::value, "");
+        static_assert(!std::is_trivially_move_assignable<P>::value, "");
+        static_assert(std::is_trivially_destructible<P>::value, "");
+    }
 }
 
 void test_layout() {
diff --git a/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp
new file mode 100644
index 0000000000000..86c014213947c
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+//
+// This test pins down the ABI of std::pair with respect to being "trivially copyable".
+//
+
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+struct trivially_copyable {
+  int arr[4];
+};
+
+struct trivially_copyable_no_assignment {
+  int arr[4];
+  trivially_copyable_no_assignment& operator=(const trivially_copyable_no_assignment&) = delete;
+};
+static_assert(std::is_trivially_copyable<trivially_copyable_no_assignment>::value, "");
+
+struct trivially_copyable_no_construction {
+  int arr[4];
+  trivially_copyable_no_construction()                                                     = default;
+  trivially_copyable_no_construction(const trivially_copyable_no_construction&)            = delete;
+  trivially_copyable_no_construction& operator=(const trivially_copyable_no_construction&) = default;
+};
+static_assert(std::is_trivially_copyable<trivially_copyable_no_construction>::value, "");
+
+static_assert((!std::is_trivially_copyable<std::pair<int&, int> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<int, int&> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<int&, int&> >::value), "");
+
+static_assert((!std::is_trivially_copyable<std::pair<int, int> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<int, char> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<char, int> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<std::pair<char, char>, int> >::value), "");
+static_assert((!std::is_trivially_copyable<std::pair<trivially_copyable, int> >::value), "");
+#if TEST_STD_VER == 03 // Known ABI difference
+static_assert((!std::is_trivially_copyable<std::pair<trivially_copyable_no_assignment, int> >::value), "");
+#else
+static_assert((std::is_trivially_copyable<std::pair<trivially_copyable_no_assignment, int> >::value), "");
+#endif
+static_assert((!std::is_trivially_copyable<std::pair<trivially_copyable_no_construction, int> >::value), "");
+
+static_assert((std::is_trivially_copy_constructible<std::pair<int, int> >::value), "");
+static_assert((std::is_trivially_move_constructible<std::pair<int, int> >::value), "");
+static_assert((!std::is_trivially_copy_assignable<std::pair<int, int> >::value), "");
+static_assert((!std::is_trivially_move_assignable<std::pair<int, int> >::value), "");
+static_assert((std::is_trivially_destructible<std::pair<int, int> >::value), "");

Copy link

github-actions bot commented Jun 13, 2024

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

@h-vetinari
Copy link
Contributor

While this is not technically an ABI break, in practice we do know that folks sometimes use a different representation based on whether a type is trivially copyable. So we're treating 83ead2b as an ABI break and this patch is fixing said breakage.

Should this then also target 18.1.8 (if it materializes)?

@ldionne
Copy link
Member Author

ldionne commented Jun 17, 2024

Should this then also target 18.1.8 (if it materializes)?

It would make sense, yes. TBH I am scared of landing these kinds of changes without any baking time in the tree though.

@ldionne ldionne force-pushed the review/fix-pair-ABI-break branch 2 times, most recently from 1f5f3b8 to 836f0ef Compare June 17, 2024 20:52
ldionne added 3 commits June 17, 2024 22:19
…hout an assignment operator

Since 83ead2b, std::pair would not be trivially copyable when it holds
a trivially copyable type without an assignment operator. That is because
pair gained an elligible copy-assignment-operator (the const version) in
83ead2b in C++ >= 23.

This means that the trivially copyable property of std::pair for such
types would be inconsistent between C++11/14/17/20 (trivially copyable)
and C++23/26 (not trivially copyable). This patch makes std::pair's
behavior consistent in all Standard modes EXCEPT C++03, which is a
pre-existing condition and we have no way of changing (also, it shouldn't
matter because the std::is_trivially_copyable trait was introduced in
C++11).

While this is not technically an ABI break, in practice we do know that
folks sometimes use a different representation based on whether a type
is trivially copyable. So we're treating 83ead2b as an ABI break and
this patch is fixing said breakage.

This patch also adds tests stolen from llvm#89652 that pin down the ABI
of std::pair with respect to being trivially copyable.

Fixes llvm#95428
@ldionne ldionne force-pushed the review/fix-pair-ABI-break branch from 836f0ef to 4f86620 Compare June 18, 2024 02:20
Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

LGTM with comments addressed.

@ldionne ldionne merged commit 02af67c into llvm:main Jun 19, 2024
54 of 57 checks passed
@ldionne ldionne deleted the review/fix-pair-ABI-break branch June 19, 2024 18:52
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…hout an assignment operator (llvm#95444)

Since 83ead2b, std::pair would not be trivially copyable when it holds a
trivially copyable type without an assignment operator. That is because
pair gained an elligible copy-assignment-operator (the const version) in
83ead2b in C++ >= 23.

This means that the trivially copyable property of std::pair for such
types would be inconsistent between C++11/14/17/20 (trivially copyable)
and C++23/26 (not trivially copyable). This patch makes std::pair's
behavior consistent in all Standard modes EXCEPT C++03, which is a
pre-existing condition and we have no way of changing (also, it
shouldn't matter because the std::is_trivially_copyable trait was
introduced in C++11).

While this is not technically an ABI break, in practice we do know that
folks sometimes use a different representation based on whether a type
is trivially copyable. So we're treating 83ead2b as an ABI break and
this patch is fixing said breakage.

This patch also adds tests stolen from llvm#89652 that pin down the ABI of
std::pair with respect to being trivially copyable.

Fixes llvm#95428
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
None yet
Development

Successfully merging this pull request may close these issues.

[libc++] ABI break between Clang 15 and Clang 16 for some trivially copiable pairs in C++ >= 23
4 participants