diff --git a/libcxx/include/__algorithm/equal.h b/libcxx/include/__algorithm/equal.h index 4cac9652d55ed..5a8c9504ede1d 100644 --- a/libcxx/include/__algorithm/equal.h +++ b/libcxx/include/__algorithm/equal.h @@ -54,24 +54,27 @@ __equal_unaligned(__bit_iterator<_Cp, _IsConst1> __first1, unsigned __clz_f = __bits_per_word - __first1.__ctz_; difference_type __dn = std::min(static_cast(__clz_f), __n); __n -= __dn; - __storage_type __m = (~__storage_type(0) << __first1.__ctz_) & (~__storage_type(0) >> (__clz_f - __dn)); + __storage_type __m = std::__middle_mask<__storage_type>(__clz_f - __dn, __first1.__ctz_); __storage_type __b = *__first1.__seg_ & __m; unsigned __clz_r = __bits_per_word - __first2.__ctz_; __storage_type __ddn = std::min<__storage_type>(__dn, __clz_r); - __m = (~__storage_type(0) << __first2.__ctz_) & (~__storage_type(0) >> (__clz_r - __ddn)); + __m = std::__middle_mask<__storage_type>(__clz_r - __ddn, __first2.__ctz_); if (__first2.__ctz_ > __first1.__ctz_) { - if ((*__first2.__seg_ & __m) != (__b << (__first2.__ctz_ - __first1.__ctz_))) + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != + static_cast<__storage_type>(__b << (__first2.__ctz_ - __first1.__ctz_))) return false; } else { - if ((*__first2.__seg_ & __m) != (__b >> (__first1.__ctz_ - __first2.__ctz_))) + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != + static_cast<__storage_type>(__b >> (__first1.__ctz_ - __first2.__ctz_))) return false; } __first2.__seg_ += (__ddn + __first2.__ctz_) / __bits_per_word; __first2.__ctz_ = static_cast((__ddn + __first2.__ctz_) % __bits_per_word); __dn -= __ddn; if (__dn > 0) { - __m = ~__storage_type(0) >> (__bits_per_word - __dn); - if ((*__first2.__seg_ & __m) != (__b >> (__first1.__ctz_ + __ddn))) + __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != + static_cast<__storage_type>(__b >> (__first1.__ctz_ + __ddn))) return false; __first2.__ctz_ = static_cast(__dn); } @@ -81,29 +84,30 @@ __equal_unaligned(__bit_iterator<_Cp, _IsConst1> __first1, // __first1.__ctz_ == 0; // do middle words unsigned __clz_r = __bits_per_word - __first2.__ctz_; - __storage_type __m = ~__storage_type(0) << __first2.__ctz_; + __storage_type __m = std::__leading_mask<__storage_type>(__first2.__ctz_); for (; __n >= __bits_per_word; __n -= __bits_per_word, ++__first1.__seg_) { __storage_type __b = *__first1.__seg_; - if ((*__first2.__seg_ & __m) != (__b << __first2.__ctz_)) + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != static_cast<__storage_type>(__b << __first2.__ctz_)) return false; ++__first2.__seg_; - if ((*__first2.__seg_ & ~__m) != (__b >> __clz_r)) + if (static_cast<__storage_type>(*__first2.__seg_ & static_cast<__storage_type>(~__m)) != + static_cast<__storage_type>(__b >> __clz_r)) return false; } // do last word if (__n > 0) { - __m = ~__storage_type(0) >> (__bits_per_word - __n); + __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); __storage_type __b = *__first1.__seg_ & __m; __storage_type __dn = std::min(__n, static_cast(__clz_r)); - __m = (~__storage_type(0) << __first2.__ctz_) & (~__storage_type(0) >> (__clz_r - __dn)); - if ((*__first2.__seg_ & __m) != (__b << __first2.__ctz_)) + __m = std::__middle_mask<__storage_type>(__clz_r - __dn, __first2.__ctz_); + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != static_cast<__storage_type>(__b << __first2.__ctz_)) return false; __first2.__seg_ += (__dn + __first2.__ctz_) / __bits_per_word; __first2.__ctz_ = static_cast((__dn + __first2.__ctz_) % __bits_per_word); __n -= __dn; if (__n > 0) { - __m = ~__storage_type(0) >> (__bits_per_word - __n); - if ((*__first2.__seg_ & __m) != (__b >> __dn)) + __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); + if (static_cast<__storage_type>(*__first2.__seg_ & __m) != static_cast<__storage_type>(__b >> __dn)) return false; } } @@ -128,7 +132,7 @@ __equal_aligned(__bit_iterator<_Cp, _IsConst1> __first1, unsigned __clz = __bits_per_word - __first1.__ctz_; difference_type __dn = std::min(static_cast(__clz), __n); __n -= __dn; - __storage_type __m = (~__storage_type(0) << __first1.__ctz_) & (~__storage_type(0) >> (__clz - __dn)); + __storage_type __m = std::__middle_mask<__storage_type>(__clz - __dn, __first1.__ctz_); if ((*__first2.__seg_ & __m) != (*__first1.__seg_ & __m)) return false; ++__first2.__seg_; @@ -144,7 +148,7 @@ __equal_aligned(__bit_iterator<_Cp, _IsConst1> __first1, return false; // do last word if (__n > 0) { - __storage_type __m = ~__storage_type(0) >> (__bits_per_word - __n); + __storage_type __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); if ((*__first2.__seg_ & __m) != (*__first1.__seg_ & __m)) return false; } diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference index 58cbd4d51c88e..f5c22fc0a3ade 100644 --- a/libcxx/include/__bit_reference +++ b/libcxx/include/__bit_reference @@ -82,13 +82,20 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask return static_cast<_StorageType>(~static_cast<_StorageType>(0)) >> __clz; } +// Creates a mask of type `_StorageType` with a specified number of trailing zeros (__ctz) and sets all remaining +// bits to one. +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz) { + static_assert(is_unsigned<_StorageType>::value, "__leading_mask only works with unsigned types"); + return static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz; +} + // Creates a mask of type `_StorageType` with a specified number of leading zeros (__clz), a specified number of // trailing zeros (__ctz), and sets all bits in between to one. template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz) { static_assert(is_unsigned<_StorageType>::value, "__middle_mask only works with unsigned types"); - return (static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz) & - std::__trailing_mask<_StorageType>(__clz); + return std::__leading_mask<_StorageType>(__ctz) & std::__trailing_mask<_StorageType>(__clz); } // This function is designed to operate correctly even for smaller integral types like `uint8_t`, `uint16_t`, diff --git a/libcxx/include/__fwd/bit_reference.h b/libcxx/include/__fwd/bit_reference.h index e212667f3de1f..99d1fdbc41959 100644 --- a/libcxx/include/__fwd/bit_reference.h +++ b/libcxx/include/__fwd/bit_reference.h @@ -33,6 +33,9 @@ __fill_masked_range(_StoragePointer __word, unsigned __ctz, unsigned __clz, bool template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask(unsigned __clz); +template +_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz); + template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz); diff --git a/libcxx/include/__vector/comparison.h b/libcxx/include/__vector/comparison.h index 960663e6ab62a..27178e41ec513 100644 --- a/libcxx/include/__vector/comparison.h +++ b/libcxx/include/__vector/comparison.h @@ -29,20 +29,6 @@ operator==(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __ return __sz == __y.size() && std::equal(__x.begin(), __x.end(), __y.begin()); } -// FIXME: Remove this `vector` overload once #126369 is resolved, reverting to the generic `operator==` -// with `std::equal` for better performance. -template -_LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI bool -operator==(const vector& __x, const vector& __y) { - const typename vector::size_type __sz = __x.size(); - if (__sz != __y.size()) - return false; - for (typename vector::size_type __i = 0; __i < __sz; ++__i) - if (__x[__i] != __y[__i]) - return false; - return true; -} - #if _LIBCPP_STD_VER <= 17 template diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.pass.cpp index b37d788c80d3a..02cc84c288828 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/equal.pass.cpp @@ -24,12 +24,14 @@ // MSVC warning C4244: 'argument': conversion from 'wchar_t' to 'const _Ty', possible loss of data // MSVC warning C4389: '==': signed/unsigned mismatch // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4242 /wd4244 /wd4389 +// XFAIL: FROZEN-CXX03-HEADERS-FIXME #include #include #include #include +#include "sized_allocator.h" #include "test_iterators.h" #include "test_macros.h" #include "type_algorithms.h" @@ -173,6 +175,90 @@ TEST_CONSTEXPR_CXX20 bool test() { test_vector_bool<256>(); } + // Make sure std::equal behaves properly with std::vector iterators with custom size types. + // See issue: https://github.com/llvm/llvm-project/issues/126369. + { + //// Tests for std::equal with aligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(6, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + assert(std::equal(in.begin() + 4, in.end(), expected.begin() + 4)); + } + { // Test the last word for uint8_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin())); + } + { // Test middle words for uint8_t + using Alloc = sized_allocator; + std::vector in(24, true, Alloc(1)); + std::vector expected(29, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin())); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + assert(std::equal(in.begin() + 4, in.end(), expected.begin() + 4)); + } + { // Test the last word for uint16_t + using Alloc = sized_allocator; + std::vector in(24, true, Alloc(1)); + std::vector expected(32, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin())); + } + { // Test middle words for uint16_t + using Alloc = sized_allocator; + std::vector in(48, true, Alloc(1)); + std::vector expected(55, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin())); + } + + //// Tests for std::equal with unaligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(6, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + assert(std::equal(in.begin() + 4, in.end(), expected.begin())); + } + { // Test the last word for uint8_t + using Alloc = sized_allocator; + std::vector in(4, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin() + 3)); + } + { // Test middle words for uint8_t + using Alloc = sized_allocator; + std::vector in(16, true, Alloc(1)); + std::vector expected(24, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin() + 4)); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + assert(std::equal(in.begin() + 4, in.end(), expected.begin())); + } + { // Test the last word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin() + 3)); + } + { // Test the middle words for uint16_t + using Alloc = sized_allocator; + std::vector in(32, true, Alloc(1)); + std::vector expected(64, true, Alloc(1)); + assert(std::equal(in.begin(), in.end(), expected.begin() + 4)); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/ranges.equal.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/ranges.equal.pass.cpp index 4c1c29c72cab0..3f3af71de6a1e 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/ranges.equal.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.equal/ranges.equal.pass.cpp @@ -31,6 +31,7 @@ #include #include "almost_satisfies_types.h" +#include "sized_allocator.h" #include "test_iterators.h" #include "test_macros.h" @@ -432,15 +433,123 @@ constexpr bool test() { assert(projCount == 6); } } + } + + { // Test vector::iterator optimization + test_vector_bool<8>(); + test_vector_bool<19>(); + test_vector_bool<32>(); + test_vector_bool<49>(); + test_vector_bool<64>(); + test_vector_bool<199>(); + test_vector_bool<256>(); + } + + // Make sure std::equal behaves properly with std::vector iterators with custom size types. + // See issue: https://github.com/llvm/llvm-project/issues/126369. + { + //// Tests for std::equal with aligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(6, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin() + 4, in.end()); + auto b = std::ranges::subrange(expected.begin() + 4, expected.begin() + 4 + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test the last word for uint8_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test middle words for uint8_t + using Alloc = sized_allocator; + std::vector in(24, true, Alloc(1)); + std::vector expected(29, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } - { // Test vector::iterator optimization - test_vector_bool<8>(); - test_vector_bool<19>(); - test_vector_bool<32>(); - test_vector_bool<49>(); - test_vector_bool<64>(); - test_vector_bool<199>(); - test_vector_bool<256>(); + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin() + 4, in.end()); + auto b = std::ranges::subrange(expected.begin() + 4, expected.begin() + 4 + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test the last word for uint16_t + using Alloc = sized_allocator; + std::vector in(24, true, Alloc(1)); + std::vector expected(32, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test middle words for uint16_t + using Alloc = sized_allocator; + std::vector in(48, true, Alloc(1)); + std::vector expected(55, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } + + //// Tests for std::equal with unaligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(6, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin() + 4, in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test the last word for uint8_t + using Alloc = sized_allocator; + std::vector in(4, true, Alloc(1)); + std::vector expected(8, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin() + 3, expected.begin() + 3 + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test middle words for uint8_t + using Alloc = sized_allocator; + std::vector in(16, true, Alloc(1)); + std::vector expected(24, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin() + 4, expected.begin() + 4 + a.size()); + assert(std::ranges::equal(a, b)); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin() + 4, in.end()); + auto b = std::ranges::subrange(expected.begin(), expected.begin() + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test the last word for uint16_t + using Alloc = sized_allocator; + std::vector in(12, true, Alloc(1)); + std::vector expected(16, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin() + 3, expected.begin() + 3 + a.size()); + assert(std::ranges::equal(a, b)); + } + { // Test the middle words for uint16_t + using Alloc = sized_allocator; + std::vector in(32, true, Alloc(1)); + std::vector expected(64, true, Alloc(1)); + auto a = std::ranges::subrange(in.begin(), in.end()); + auto b = std::ranges::subrange(expected.begin() + 4, expected.begin() + 4 + a.size()); + assert(std::ranges::equal(a, b)); } }