Author: James Player Date: 2021-01-16T09:37:04-05:00 New Revision: 25c1578a46ff93f920b7ad4e3057465902ced8f5
URL: https://github.com/llvm/llvm-project/commit/25c1578a46ff93f920b7ad4e3057465902ced8f5 DIFF: https://github.com/llvm/llvm-project/commit/25c1578a46ff93f920b7ad4e3057465902ced8f5.diff LOG: Fix llvm::Optional build breaks in MSVC using std::is_trivially_copyable Current code breaks this version of MSVC due to a mismatch between `std::is_trivially_copyable` and `llvm::is_trivially_copyable` for `std::pair` instantiations. Hence I was attempting to use `std::is_trivially_copyable` to set `llvm::is_trivially_copyable<T>::value`. I spent some time root causing an `llvm::Optional` build error on MSVC 16.8.3 related to the change described above: ``` 62>C:\src\ocg_llvm\llvm-project\llvm\include\llvm/ADT/BreadthFirstIterator.h(96,12): error C2280: 'llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>> &llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>>::operator =(const llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>> &)': attempting to reference a deleted function (compiling source file C:\src\ocg_llvm\llvm-project\llvm\unittests\ADT\BreadthFirstIteratorTest.cpp) ... ``` The "trivial" specialization of `optional_detail::OptionalStorage` assumes that the value type is trivially copy constructible and trivially copy assignable. The specialization is invoked based on a check of `is_trivially_copyable` alone, which does not imply both `is_trivially_copy_assignable` and `is_trivially_copy_constructible` are true. [[ https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable | According to the spec ]], a deleted assignment operator does not make `is_trivially_copyable` false. So I think all these properties need to be checked explicitly in order to specialize `OptionalStorage` to the "trivial" version: ``` /// Storage for any type. template <typename T, bool = std::is_trivially_copy_constructible<T>::value && std::is_trivially_copy_assignable<T>::value> class OptionalStorage { ``` Above fixed my build break in MSVC, but I think we need to explicitly check `is_trivially_copy_constructible` too since it might be possible the copy constructor is deleted. Also would be ideal to move over to `std::is_trivially_copyable` instead of the `llvm` namespace verson. Reviewed By: dblaikie Differential Revision: https://reviews.llvm.org/D93510 Added: Modified: llvm/include/llvm/ADT/Optional.h llvm/unittests/ADT/OptionalTest.cpp Removed: ################################################################################ diff --git a/llvm/include/llvm/ADT/Optional.h b/llvm/include/llvm/ADT/Optional.h index daa9ee627fa9..a285c81d1be8 100644 --- a/llvm/include/llvm/ADT/Optional.h +++ b/llvm/include/llvm/ADT/Optional.h @@ -33,7 +33,30 @@ namespace optional_detail { struct in_place_t {}; /// Storage for any type. -template <typename T, bool = is_trivially_copyable<T>::value> +// +// The specialization condition intentionally uses +// llvm::is_trivially_copy_constructible instead of +// std::is_trivially_copy_constructible. GCC versions prior to 7.4 may +// instantiate the copy constructor of `T` when +// std::is_trivially_copy_constructible is instantiated. This causes +// compilation to fail if we query the trivially copy constructible property of +// a class which is not copy constructible. +// +// The current implementation of OptionalStorage insists that in order to use +// the trivial specialization, the value_type must be trivially copy +// constructible and trivially copy assignable due to =default implementations +// of the copy/move constructor/assignment. It does not follow that this is +// necessarily the case std::is_trivially_copyable is true (hence the expanded +// specialization condition). +// +// The move constructible / assignable conditions emulate the remaining behavior +// of std::is_trivially_copyable. +template <typename T, bool = (llvm::is_trivially_copy_constructible<T>::value && + std::is_trivially_copy_assignable<T>::value && + (std::is_trivially_move_constructible<T>::value || + !std::is_move_constructible<T>::value) && + (std::is_trivially_move_assignable<T>::value || + !std::is_move_assignable<T>::value))> class OptionalStorage { union { char empty; diff --git a/llvm/unittests/ADT/OptionalTest.cpp b/llvm/unittests/ADT/OptionalTest.cpp index c7fa796a2d7f..d40e21255f80 100644 --- a/llvm/unittests/ADT/OptionalTest.cpp +++ b/llvm/unittests/ADT/OptionalTest.cpp @@ -8,6 +8,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest-spi.h" #include "gtest/gtest.h" @@ -390,6 +391,143 @@ TEST(OptionalTest, ImmovableEmplace) { EXPECT_EQ(0u, Immovable::Destructions); } +// Craft a class which is_trivially_copyable, but not +// is_trivially_copy_constructible. +struct NonTCopy { + NonTCopy() = default; + + // Delete the volatile copy constructor to engage the "rule of 3" and delete + // any unspecified copy assignment or constructor. + NonTCopy(volatile NonTCopy const &) = delete; + + // Leave the non-volatile default copy constructor unspecified (deleted by + // rule of 3) + + // This template can serve as the copy constructor, but isn't chosen + // by =default in a class with a 'NonTCopy' member. + template <typename Self = NonTCopy> + NonTCopy(Self const &Other) : Val(Other.Val) {} + + NonTCopy &operator=(NonTCopy const &) = default; + + int Val{0}; +}; + +#if defined(_MSC_VER) && _MSC_VER >= 1927 && !defined(__clang__) +// Currently only true on recent MSVC releases. +static_assert(std::is_trivially_copyable<NonTCopy>::value, + "Expect NonTCopy to be trivially copyable"); + +static_assert(!std::is_trivially_copy_constructible<NonTCopy>::value, + "Expect NonTCopy not to be trivially copy constructible."); +#endif // defined(_MSC_VER) && _MSC_VER >= 1927 + +TEST(OptionalTest, DeletedCopyConstructor) { + + // Expect compile to fail if 'trivial' version of + // optional_detail::OptionalStorage is chosen. + using NonTCopyOptT = Optional<NonTCopy>; + NonTCopyOptT NonTCopy1; + + // Check that the Optional can be copy constructed. + NonTCopyOptT NonTCopy2{NonTCopy1}; + + // Check that the Optional can be copy assigned. + NonTCopy1 = NonTCopy2; +} + +// Craft a class which is_trivially_copyable, but not +// is_trivially_copy_assignable. +class NonTAssign { +public: + NonTAssign() = default; + NonTAssign(NonTAssign const &) = default; + + // Delete the volatile copy assignment to engage the "rule of 3" and delete + // any unspecified copy assignment or constructor. + NonTAssign &operator=(volatile NonTAssign const &) = delete; + + // Leave the non-volatile default copy assignment unspecified (deleted by rule + // of 3). + + // This template can serve as the copy assignment, but isn't chosen + // by =default in a class with a 'NonTAssign' member. + template <typename Self = NonTAssign> + NonTAssign &operator=(Self const &Other) { + A = Other.A; + return *this; + } + + int A{0}; +}; + +#if defined(_MSC_VER) && _MSC_VER >= 1927 && !defined(__clang__) +// Currently only true on recent MSVC releases. +static_assert(std::is_trivially_copyable<NonTAssign>::value, + "Expect NonTAssign to be trivially copyable"); + +static_assert(!std::is_trivially_copy_assignable<NonTAssign>::value, + "Expect NonTAssign not to be trivially assignable."); +#endif // defined(_MSC_VER) && _MSC_VER >= 1927 + +TEST(OptionalTest, DeletedCopyAssignment) { + + // Expect compile to fail if 'trivial' version of + // optional_detail::OptionalStorage is chosen. + using NonTAssignOptT = Optional<NonTAssign>; + NonTAssignOptT NonTAssign1; + + // Check that the Optional can be copy constructed. + NonTAssignOptT NonTAssign2{NonTAssign1}; + + // Check that the Optional can be copy assigned. + NonTAssign1 = NonTAssign2; +} + +struct NoTMove { + NoTMove() = default; + NoTMove(NoTMove const &) = default; + NoTMove &operator=(NoTMove const &) = default; + + // Delete move constructor / assignment. Compiler should fall-back to the + // trivial copy constructor / assignment in the trivial OptionalStorage + // specialization. + NoTMove(NoTMove &&) = delete; + NoTMove &operator=(NoTMove &&) = delete; + + int Val{0}; +}; + +TEST(OptionalTest, DeletedMoveConstructor) { + using NoTMoveOptT = Optional<NoTMove>; + + NoTMoveOptT NonTMove1; + NoTMoveOptT NonTMove2{std::move(NonTMove1)}; + + NonTMove1 = std::move(NonTMove2); + + static_assert( + std::is_trivially_copyable<NoTMoveOptT>::value, + "Expect Optional<NoTMove> to still use the trivial specialization " + "of OptionalStorage despite the deleted move constructor / assignment."); +} + +class NoCopyStringMap { +public: + NoCopyStringMap() = default; + +private: + llvm::StringMap<std::unique_ptr<int>> Map; +}; + +TEST(OptionalTest, DeletedCopyStringMap) { + // Old versions of gcc (7.3 and prior) instantiate the copy constructor when + // std::is_trivially_copyable is instantiated. This test will fail + // compilation if std::is_trivially_copyable is used in the OptionalStorage + // specialization condition by gcc <= 7.3. + Optional<NoCopyStringMap> TestInstantiation; +} + #if LLVM_HAS_RVALUE_REFERENCE_THIS TEST(OptionalTest, MoveGetValueOr) { _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits