diff --git a/libcxx/include/__algorithm/copy_backward.h b/libcxx/include/__algorithm/copy_backward.h index 02ffc14361e6a..9f890645a41d3 100644 --- a/libcxx/include/__algorithm/copy_backward.h +++ b/libcxx/include/__algorithm/copy_backward.h @@ -52,7 +52,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ difference_type __dn = std::min(static_cast(__last.__ctz_), __n); __n -= __dn; unsigned __clz = __bits_per_word - __last.__ctz_; - __storage_type __m = (~__storage_type(0) << (__last.__ctz_ - __dn)) & (~__storage_type(0) >> __clz); + __storage_type __m = std::__middle_mask<__storage_type>(__clz, __last.__ctz_ - __dn); __storage_type __b = *__last.__seg_ & __m; *__result.__seg_ &= ~__m; *__result.__seg_ |= __b; @@ -69,7 +69,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ __n -= __nw * __bits_per_word; // do last word if (__n > 0) { - __storage_type __m = ~__storage_type(0) << (__bits_per_word - __n); + __storage_type __m = std::__leading_mask<__storage_type>(__bits_per_word - __n); __storage_type __b = *--__last.__seg_ & __m; *--__result.__seg_ &= ~__m; *__result.__seg_ |= __b; @@ -94,12 +94,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ difference_type __dn = std::min(static_cast(__last.__ctz_), __n); __n -= __dn; unsigned __clz_l = __bits_per_word - __last.__ctz_; - __storage_type __m = (~__storage_type(0) << (__last.__ctz_ - __dn)) & (~__storage_type(0) >> __clz_l); + __storage_type __m = std::__middle_mask<__storage_type>(__clz_l, __last.__ctz_ - __dn); __storage_type __b = *__last.__seg_ & __m; unsigned __clz_r = __bits_per_word - __result.__ctz_; __storage_type __ddn = std::min(__dn, static_cast(__result.__ctz_)); if (__ddn > 0) { - __m = (~__storage_type(0) << (__result.__ctz_ - __ddn)) & (~__storage_type(0) >> __clz_r); + __m = std::__middle_mask<__storage_type>(__clz_r, __result.__ctz_ - __ddn); *__result.__seg_ &= ~__m; if (__result.__ctz_ > __last.__ctz_) *__result.__seg_ |= __b << (__result.__ctz_ - __last.__ctz_); @@ -112,7 +112,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ // __result.__ctz_ == 0 --__result.__seg_; __result.__ctz_ = static_cast(-__dn & (__bits_per_word - 1)); - __m = ~__storage_type(0) << __result.__ctz_; + __m = std::__leading_mask<__storage_type>(__result.__ctz_); *__result.__seg_ &= ~__m; __last.__ctz_ -= __dn + __ddn; *__result.__seg_ |= __b << (__result.__ctz_ - __last.__ctz_); @@ -123,7 +123,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ // __result.__ctz_ != 0 || __n == 0 // do middle words unsigned __clz_r = __bits_per_word - __result.__ctz_; - __storage_type __m = ~__storage_type(0) >> __clz_r; + __storage_type __m = std::__trailing_mask<__storage_type>(__clz_r); for (; __n >= __bits_per_word; __n -= __bits_per_word) { __storage_type __b = *--__last.__seg_; *__result.__seg_ &= ~__m; @@ -133,11 +133,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ } // do last word if (__n > 0) { - __m = ~__storage_type(0) << (__bits_per_word - __n); + __m = std::__leading_mask<__storage_type>(__bits_per_word - __n); __storage_type __b = *--__last.__seg_ & __m; __clz_r = __bits_per_word - __result.__ctz_; __storage_type __dn = std::min(__n, static_cast(__result.__ctz_)); - __m = (~__storage_type(0) << (__result.__ctz_ - __dn)) & (~__storage_type(0) >> __clz_r); + __m = std::__middle_mask<__storage_type>(__clz_r, __result.__ctz_ - __dn); *__result.__seg_ &= ~__m; *__result.__seg_ |= __b >> (__bits_per_word - __result.__ctz_); __result.__ctz_ = static_cast(((-__dn & (__bits_per_word - 1)) + __result.__ctz_) % __bits_per_word); @@ -146,7 +146,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ // __result.__ctz_ == 0 --__result.__seg_; __result.__ctz_ = static_cast(-__n & (__bits_per_word - 1)); - __m = ~__storage_type(0) << __result.__ctz_; + __m = std::__leading_mask<__storage_type>(__result.__ctz_); *__result.__seg_ &= ~__m; *__result.__seg_ |= __b << (__result.__ctz_ - (__bits_per_word - __n - __dn)); } 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/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp index 8a528a96f5294..def192d4d6637 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy_backward.pass.cpp @@ -13,10 +13,13 @@ // constexpr OutIter // constexpr after C++17 // copy_backward(InIter first, InIter last, OutIter result); +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include #include #include +#include "sized_allocator.h" #include "test_macros.h" #include "test_iterators.h" #include "type_algorithms.h" @@ -111,6 +114,146 @@ TEST_CONSTEXPR_CXX20 bool test() { assert(test_vector_bool(256)); } + // Validate std::copy_backward with std::vector iterators and custom storage types. + // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged. + // Related issue: https://github.com/llvm/llvm-project/issues/131718. + { + //// Tests for std::copy_backward with aligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(7, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::copy_backward(in.begin(), in.begin() + 1, out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(8, true, Alloc(1)); + std::copy_backward(in.end() - 4, in.end(), out.end()); + for (std::size_t i = 0; i < static_cast(in.size() - 4); ++i) + assert(out[i] == true); + for (std::size_t i = in.size() + 4; i < out.size(); ++i) + assert(in[i] == out[i]); + } + { // Test the middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(17, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(24, true, Alloc(1)); + std::copy_backward(in.begin(), in.end(), out.begin() + in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i]); + for (std::size_t i = in.size(); i < out.size(); ++i) + assert(out[i] == true); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(14, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::copy_backward(in.begin(), in.begin() + 2, out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(16, true, Alloc(1)); + std::copy_backward(in.end() - 8, in.end(), out.end()); + for (std::size_t i = 0; i < static_cast(in.size() - 8); ++i) + assert(out[i] == true); + for (std::size_t i = in.size() + 8; i < out.size(); ++i) + assert(in[i] == out[i]); + } + { // Test the middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(34, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(48, true, Alloc(1)); + std::copy_backward(in.begin(), in.end(), out.begin() + in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i]); + for (std::size_t i = in.size(); i < out.size(); ++i) + assert(out[i] == true); + } + + //// Tests for std::copy_backward with unaligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::copy_backward(in.begin(), in.begin() + 1, out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::copy_backward(in.end() - 1, in.end(), out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(17, true, Alloc(1)); + std::copy_backward(in.begin(), in.end(), out.end()); + assert(out[0] == true); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 1]); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::copy_backward(in.begin(), in.begin() + 2, out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::copy_backward(in.end() - 2, in.end(), out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(32, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(33, true, Alloc(1)); + std::copy_backward(in.begin(), in.end(), out.end()); + assert(out[0] == true); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 1]); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp index a7fa3db23e6ba..e7251ab905db5 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy_backward.pass.cpp @@ -28,6 +28,7 @@ #include #include "almost_satisfies_types.h" +#include "sized_allocator.h" #include "test_iterators.h" #include "test_macros.h" @@ -355,6 +356,146 @@ constexpr bool test() { assert(test_vector_bool(199)); assert(test_vector_bool(256)); } + + // Validate std::ranges::copy_backward with std::vector iterators and custom storage types. + // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged. + // Related issue: https://github.com/llvm/llvm-project/issues/131718. + { + //// Tests for std::ranges::copy_backward with aligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(7, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(8, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.end() - 4, in.end()), out.end()); + for (std::size_t i = 0; i < static_cast(in.size() - 4); ++i) + assert(out[i] == true); + for (std::size_t i = in.size() + 4; i < out.size(); ++i) + assert(in[i] == out[i]); + } + { // Test the middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(17, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(24, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.begin() + in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i]); + for (std::size_t i = in.size(); i < out.size(); ++i) + assert(out[i] == true); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(14, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(16, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.end() - 8, in.end()), out.end()); + for (std::size_t i = 0; i < static_cast(in.size() - 8); ++i) + assert(out[i] == true); + for (std::size_t i = in.size() + 8; i < out.size(); ++i) + assert(in[i] == out[i]); + } + { // Test the middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(34, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(48, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.begin() + in.size()); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i]); + for (std::size_t i = in.size(); i < out.size(); ++i) + assert(out[i] == true); + } + + //// Tests for std::ranges::copy_backward with unaligned bits + + { // Test the first (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint8_t + using Alloc = sized_allocator; + std::vector in(8, false, Alloc(1)); + std::vector out(8, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.end() - 1, in.end()), out.begin() + 1); + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(17, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.end()); + assert(out[0] == true); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 1]); + } + + { // Test the first (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the last (partial) word for uint16_t + using Alloc = sized_allocator; + std::vector in(16, false, Alloc(1)); + std::vector out(16, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.end() - 2, in.end()), out.begin() + 2); + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) + assert(out[i] == true); + } + { // Test the middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(32, false, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = true; + std::vector out(33, true, Alloc(1)); + std::ranges::copy_backward(std::ranges::subrange(in.begin(), in.end()), out.end()); + assert(out[0] == true); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 1]); + } + } #endif return true;