[libcxx-commits] [libcxx] [libc++] Fix triviality of std::pair for trivially copyable types without an assignment operator (PR #95444)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Mon Jun 17 13:52:46 PDT 2024


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/95444

>From 9facb7729f96dbc99739b68f5acb97a8d7c4ccf7 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 13 Jun 2024 13:31:55 -0400
Subject: [PATCH 1/2] [libc++] Fix triviality of std::pair for trivially
 copyable types without 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 #89652 that pin down the ABI
of std::pair with respect to being trivially copyable.

Fixes #95428
---
 libcxx/include/__utility/pair.h               |  2 +
 ...cpp => abi.non_trivial_copy_move.pass.cpp} |  0
 ...ass.cpp => abi.trivial_copy_move.pass.cpp} | 31 ++++++++++
 .../abi.trivially_copyable.compile.pass.cpp   | 56 +++++++++++++++++++
 4 files changed, 89 insertions(+)
 rename libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/{non_trivial_copy_move_ABI.pass.cpp => abi.non_trivial_copy_move.pass.cpp} (100%)
 rename libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/{trivial_copy_move_ABI.pass.cpp => abi.trivial_copy_move.pass.cpp} (79%)
 create mode 100644 libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp

diff --git a/libcxx/include/__utility/pair.h b/libcxx/include/__utility/pair.h
index 7e17c4c415c41..6f293e1b5d65c 100644
--- a/libcxx/include/__utility/pair.h
+++ b/libcxx/include/__utility/pair.h
@@ -267,6 +267,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>)
@@ -276,6 +277,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), "");

>From 836f0efbf1eeaa00b8933c242d85bae3ba6c6b4d Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 17 Jun 2024 16:52:32 -0400
Subject: [PATCH 2/2] UNSUPPORT on FreeBSD

---
 .../pairs/pairs.pair/abi.trivially_copyable.compile.pass.cpp  | 4 ++++
 libcxx/utils/libcxx/test/features.py                          | 1 +
 2 files changed, 5 insertions(+)

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
index 86c014213947c..821c103519714 100644
--- 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
@@ -10,6 +10,10 @@
 // This test pins down the ABI of std::pair with respect to being "trivially copyable".
 //
 
+// This test doesn't work when the deprecated ABI to turn off pair triviality is enabled.
+// See libcxx/test/libcxx/utilities/utility/pairs/pairs.pair/abi.non_trivial_copy_move.pass.cpp instead.
+// UNSUPPORTED: libcpp-deprecated-abi-disable-pair-trivial-copy-ctor
+
 #include <type_traits>
 #include <utility>
 
diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py
index 7a9631a56e4bb..a3395436a9bc0 100644
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -352,6 +352,7 @@ def _mingwSupportsModules(cfg):
     "_LIBCPP_NO_VCRUNTIME": "libcpp-no-vcruntime",
     "_LIBCPP_ABI_VERSION": "libcpp-abi-version",
     "_LIBCPP_ABI_BOUNDED_ITERATORS": "libcpp-has-abi-bounded-iterators",
+    "_LIBCPP_DEPRECATED_ABI_DISABLE_PAIR_TRIVIAL_COPY_CTOR": "libcpp-deprecated-abi-disable-pair-trivial-copy-ctor",
     "_LIBCPP_HAS_NO_FILESYSTEM": "no-filesystem",
     "_LIBCPP_HAS_NO_RANDOM_DEVICE": "no-random-device",
     "_LIBCPP_HAS_NO_LOCALIZATION": "no-localization",



More information about the libcxx-commits mailing list