diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 984878026db..873af91a435 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -5616,6 +5616,10 @@ namespace ranges { } // namespace ranges #endif // _HAS_CXX20 +template +struct _Is_trivially_copy_assignable_returning_same_reference + : bool_constant<_Is_trivially_assignable_returning_same_reference_v<_Ty&, const _Ty&>> {}; + _EXPORT_STD template _CONSTEXPR20 _OutIt reverse_copy(_BidIt _First, _BidIt _Last, _OutIt _Dest) { // copy reversing elements in [_First, _Last) @@ -5629,8 +5633,8 @@ _CONSTEXPR20 _OutIt reverse_copy(_BidIt _First, _BidIt _Last, _OutIt _Dest) { using _Elem = remove_reference_t<_Iter_ref_t>>; using _DestElem = remove_reference_t<_Iter_ref_t>; constexpr bool _Allow_vectorization = conjunction_v, _DestElem>, - bool_constant<_Iterators_are_contiguous>, is_trivially_copyable<_Elem>, - negation>>; + bool_constant<_Iterators_are_contiguous>, + _Is_trivially_copy_assignable_returning_same_reference<_Elem>, negation>>; constexpr size_t _Nx = sizeof(_Elem); if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { @@ -5719,7 +5723,7 @@ namespace ranges { using _Elem = remove_reference_t>; using _DestElem = remove_reference_t>; constexpr bool _Allow_vectorization = conjunction_v, _DestElem>, - is_trivially_copyable<_Elem>, negation>>; + _Is_trivially_copy_assignable_returning_same_reference<_Elem>, negation>>; constexpr size_t _Nx = sizeof(_Elem); if constexpr (_Allow_vectorization && _Nx <= 8 && (_Nx & (_Nx - 1)) == 0) { diff --git a/stl/inc/xutility b/stl/inc/xutility index 854ae81b26c..61f8e1a620d 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -4683,6 +4683,25 @@ constexpr bool _Is_pointer_address_convertible = is_void_v<_Source> #endif // defined(__cpp_lib_is_pointer_interconvertible) ; +#pragma warning(push) +#pragma warning(disable : 4242) // '%s': conversion from '%s' to '%s', possible loss of data +#pragma warning(disable : 4244) // '%s': conversion from '%s' to '%s', possible loss of data (Yes, duplicated message.) +#pragma warning(disable : 4267) // '%s': conversion from '%s' to '%s', possible loss of data (Yes, duplicated message!) +#pragma warning(disable : 4365) // '%s': conversion from '%s' to '%s', signed/unsigned mismatch +#pragma warning(disable : 5267) // definition of implicit assignment operator for '%s' is deprecated because it has a + // user-provided copy constructor + +// Determines whether _DestRef is trivially assignable from _SourceRef, and if _Remove_cvref_t<_DestRef> is a class, the +// assignment uses _Remove_cvref_t<_DestRef>::operator=. +// Not pedantically reliable for vectorization, but working due to bugs of MSVC, Clang, and EDG. See LLVM-37038. +template > +constexpr bool _Is_trivially_assignable_returning_same_reference_v = + is_same_v<_DestRef, decltype(_STD declval<_DestRef>() = _STD declval<_SourceRef>())>; +template +constexpr bool _Is_trivially_assignable_returning_same_reference_v<_DestRef, _SourceRef, false> = false; + +#pragma warning(pop) + template struct _Trivial_cat { using _USource = _Unwrap_enum_t<_Source>; @@ -4701,7 +4720,7 @@ struct _Trivial_cat { _Same_size_and_compatible && is_trivially_constructible_v<_Dest, _SourceRef>; static constexpr bool _Bitcopy_assignable = - _Same_size_and_compatible && is_trivially_assignable_v<_DestRef, _SourceRef>; + _Same_size_and_compatible && _Is_trivially_assignable_returning_same_reference_v<_DestRef, _SourceRef>; }; template @@ -4710,7 +4729,8 @@ struct _Trivial_cat<_Source*, _Dest*, _SourceRef, _DestRef> { _Is_pointer_address_convertible<_Source, _Dest> && is_trivially_constructible_v<_Dest*, _SourceRef>; static constexpr bool _Bitcopy_assignable = - _Is_pointer_address_convertible<_Source, _Dest> && is_trivially_assignable_v<_DestRef, _SourceRef>; + _Is_pointer_address_convertible<_Source, _Dest> + && _Is_trivially_assignable_returning_same_reference_v<_DestRef, _SourceRef>; }; struct _False_trivial_cat { diff --git a/tests/std/test.lst b/tests/std/test.lst index 87f84f36107..4c7c40603d1 100644 --- a/tests/std/test.lst +++ b/tests/std/test.lst @@ -252,6 +252,7 @@ tests\GH_004609_heterogeneous_cmp_overloads tests\GH_004618_mixed_operator_usage_keeps_statistical_properties tests\GH_004618_normal_distribution_avoids_resets tests\GH_004657_expected_constraints_permissive +tests\GH_004686_vectorization_on_trivial_assignability tests\GH_004845_logical_operator_traits_with_non_bool_constant tests\GH_004929_internal_tag_constructors tests\GH_004930_char_traits_user_specialization diff --git a/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/env.lst b/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/env.lst new file mode 100644 index 00000000000..19f025bd0e6 --- /dev/null +++ b/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/env.lst @@ -0,0 +1,4 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +RUNALL_INCLUDE ..\usual_matrix.lst diff --git a/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/test.cpp b/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/test.cpp new file mode 100644 index 00000000000..370291f437c --- /dev/null +++ b/tests/std/tests/GH_004686_vectorization_on_trivial_assignability/test.cpp @@ -0,0 +1,256 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include + +#if _HAS_CXX20 +#define CONSTEXPR20 constexpr +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv +#define CONSTEXPR20 inline +#endif // ^^^ !_HAS_CXX20 ^^^ + +using namespace std; + +struct Cat {}; +struct Leopard : Cat { + int spots_; + + Leopard() = default; + Leopard(const Leopard&) = default; + Leopard(Leopard&&) = default; + Leopard& operator=(Leopard&) && = delete; + using Cat::operator=; +}; + +constexpr pair expected_results[]{{5, 6}, {3, 4}, {1, 2}}; + +CONSTEXPR20 void test_reverse_copy() { + { + pair src[] = {{1, 2}, {3, 4}, {5, 6}}; + pair dst[] = {{3, 1}, {4, 1}, {5, 9}}; + pair srcref[] = { + {src[0].first, src[0].second}, {src[1].first, src[1].second}, {src[2].first, src[2].second}}; + pair dstref[] = { + {dst[0].first, dst[0].second}, {dst[1].first, dst[1].second}, {dst[2].first, dst[2].second}}; + + reverse_copy(begin(srcref), end(srcref), dstref); + assert(equal(begin(dst), end(dst), begin(expected_results), end(expected_results))); + } +#if _HAS_CXX20 + { + pair src[] = {{1, 2}, {3, 4}, {5, 6}}; + pair dst[] = {{3, 1}, {4, 1}, {5, 9}}; + pair srcref[] = { + {src[0].first, src[0].second}, {src[1].first, src[1].second}, {src[2].first, src[2].second}}; + pair dstref[] = { + {dst[0].first, dst[0].second}, {dst[1].first, dst[1].second}, {dst[2].first, dst[2].second}}; + + ranges::reverse_copy(srcref, dstref); + assert(ranges::equal(dst, expected_results)); + } +#endif // _HAS_CXX20 +#if _HAS_CXX23 + { + pair src[] = {{1, 2}, {3, 4}, {5, 6}}; + pair dst[] = {{3, 1}, {4, 1}, {5, 9}}; + pair srcref[] = {src[0], src[1], src[2]}; + pair dstref[] = {dst[0], dst[1], dst[2]}; + + reverse_copy(begin(srcref), end(srcref), dstref); + assert(equal(begin(dst), end(dst), begin(expected_results), end(expected_results))); + } + { + pair src[] = {{1, 2}, {3, 4}, {5, 6}}; + pair dst[] = {{3, 1}, {4, 1}, {5, 9}}; + pair srcref[] = {src[0], src[1], src[2]}; + pair dstref[] = {dst[0], dst[1], dst[2]}; + + ranges::reverse_copy(srcref, dstref); + assert(ranges::equal(dst, expected_results)); + } +#endif // _HAS_CXX23 +} + +constexpr Leopard make_leopard(const int n) noexcept { + Leopard result{}; + result.spots_ = n; + return result; +} + +CONSTEXPR20 void test_copy_move_leopards() { + constexpr Leopard expected_leopards[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + constexpr Leopard zero_leopards[6]{}; + auto equal_leopard = [](const Leopard& lhs, const Leopard& rhs) { return lhs.spots_ == rhs.spots_; }; + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + copy(begin(zero_leopards), end(zero_leopards), begin(dst)); + assert(equal(begin(dst), end(dst), begin(expected_leopards), end(expected_leopards), equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + copy_n(begin(zero_leopards), size(zero_leopards), begin(dst)); + assert(equal(begin(dst), end(dst), begin(expected_leopards), end(expected_leopards), equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + copy_backward(begin(zero_leopards), end(zero_leopards), end(dst)); + assert(equal(begin(dst), end(dst), begin(expected_leopards), end(expected_leopards), equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + move(begin(zero_leopards), end(zero_leopards), begin(dst)); + assert(equal(begin(dst), end(dst), begin(expected_leopards), end(expected_leopards), equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + move_backward(begin(zero_leopards), end(zero_leopards), end(dst)); + assert(equal(begin(dst), end(dst), begin(expected_leopards), end(expected_leopards), equal_leopard)); + } +#if _HAS_CXX20 + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + ranges::copy(zero_leopards, dst); + assert(ranges::equal(dst, expected_leopards, equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + ranges::copy_n(ranges::begin(zero_leopards), ranges::distance(zero_leopards), dst); + assert(ranges::equal(dst, expected_leopards, equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + ranges::copy_backward(zero_leopards, ranges::end(dst)); + assert(ranges::equal(dst, expected_leopards, equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + ranges::move(zero_leopards, dst); + assert(ranges::equal(dst, expected_leopards, equal_leopard)); + } + { + Leopard dst[]{ + make_leopard(3), make_leopard(1), make_leopard(4), make_leopard(1), make_leopard(5), make_leopard(9)}; + ranges::move_backward(zero_leopards, ranges::end(dst)); + assert(ranges::equal(dst, expected_leopards, equal_leopard)); + } +#endif // _HAS_CXX20 +} + +// Pedantically, all of MSVC, Clang, and EDG are currently wrong on this, see LLVM-37038. +// However, if compilers get corrected, the assignment operators of `DerivedLeopard` and `LeopardHouse` will be trivial +// but no-op, and the library side can't correctly conclude that assignments for them shouldn't be vectorized. +// As a result, we keep this as a regression test. +CONSTEXPR20 void test_llvm_37038() { + struct DerivedLeopard : Leopard {}; + static_assert(is_trivially_move_assignable_v, ""); + + auto make_derived_leopard = [](int n) { + DerivedLeopard ret{}; + ret.spots_ = n; + return ret; + }; + + auto equal_derived = [](const DerivedLeopard& lhs, const DerivedLeopard& rhs) { return lhs.spots_ == rhs.spots_; }; + + { + DerivedLeopard src[]{ + make_derived_leopard(1), make_derived_leopard(7), make_derived_leopard(2), make_derived_leopard(9)}; + DerivedLeopard dst[4]{}; + move(begin(src), end(src), dst); + assert(equal(begin(dst), end(dst), begin(src), end(src), equal_derived)); + } + { + DerivedLeopard src[]{ + make_derived_leopard(1), make_derived_leopard(7), make_derived_leopard(2), make_derived_leopard(9)}; + DerivedLeopard dst[4]{}; + move_backward(begin(src), end(src), end(dst)); + assert(equal(begin(dst), end(dst), begin(src), end(src), equal_derived)); + } +#if _HAS_CXX20 + { + DerivedLeopard src[]{ + make_derived_leopard(1), make_derived_leopard(7), make_derived_leopard(2), make_derived_leopard(9)}; + DerivedLeopard dst[4]{}; + ranges::move(src, dst); + assert(ranges::equal(src, dst, equal_derived)); + } + { + DerivedLeopard src[]{ + make_derived_leopard(1), make_derived_leopard(7), make_derived_leopard(2), make_derived_leopard(9)}; + DerivedLeopard dst[4]{}; + ranges::move_backward(src, ranges::end(dst)); + assert(ranges::equal(src, dst, equal_derived)); + } +#endif // _HAS_CXX20 + + struct LeopardHouse { + Leopard bigcat_; + }; + static_assert(is_trivially_move_assignable_v, ""); + + auto make_leopard_house = [](int n) { + LeopardHouse ret{}; + ret.bigcat_.spots_ = n; + return ret; + }; + + auto equal_house = [](const LeopardHouse& lhs, const LeopardHouse& rhs) { + return lhs.bigcat_.spots_ == rhs.bigcat_.spots_; + }; + + { + LeopardHouse src[]{make_leopard_house(1), make_leopard_house(7), make_leopard_house(2), make_leopard_house(9)}; + LeopardHouse dst[4]{}; + move(begin(src), end(src), dst); + assert(equal(begin(dst), end(dst), begin(src), end(src), equal_house)); + } + { + LeopardHouse src[]{make_leopard_house(1), make_leopard_house(7), make_leopard_house(2), make_leopard_house(9)}; + LeopardHouse dst[4]{}; + move_backward(begin(src), end(src), end(dst)); + assert(equal(begin(dst), end(dst), begin(src), end(src), equal_house)); + } +#if _HAS_CXX20 + { + LeopardHouse src[]{make_leopard_house(1), make_leopard_house(7), make_leopard_house(2), make_leopard_house(9)}; + LeopardHouse dst[4]{}; + ranges::move(src, dst); + assert(ranges::equal(src, dst, equal_house)); + } + { + LeopardHouse src[]{make_leopard_house(1), make_leopard_house(7), make_leopard_house(2), make_leopard_house(9)}; + LeopardHouse dst[4]{}; + ranges::move_backward(src, ranges::end(dst)); + assert(ranges::equal(src, dst, equal_house)); + } +#endif // _HAS_CXX20 +} + +CONSTEXPR20 bool test() { + test_reverse_copy(); + test_copy_move_leopards(); + test_llvm_37038(); + + return true; +} + +#if _HAS_CXX20 +static_assert(test()); +#endif // _HAS_CXX20 + +int main() { + test(); +}