diff --git a/libcxx/include/__algorithm/copy.h b/libcxx/include/__algorithm/copy.h index 7454c874a4d93..ea98031df11ad 100644 --- a/libcxx/include/__algorithm/copy.h +++ b/libcxx/include/__algorithm/copy.h @@ -53,7 +53,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ unsigned __clz = __bits_per_word - __first.__ctz_; difference_type __dn = std::min(static_cast(__clz), __n); __n -= __dn; - __storage_type __m = (~__storage_type(0) << __first.__ctz_) & (~__storage_type(0) >> (__clz - __dn)); + __storage_type __m = std::__middle_mask<__storage_type>(__clz - __dn, __first.__ctz_); __storage_type __b = *__first.__seg_ & __m; *__result.__seg_ &= ~__m; *__result.__seg_ |= __b; @@ -73,7 +73,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ // do last word if (__n > 0) { __first.__seg_ += __nw; - __storage_type __m = ~__storage_type(0) >> (__bits_per_word - __n); + __storage_type __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); __storage_type __b = *__first.__seg_ & __m; *__result.__seg_ &= ~__m; *__result.__seg_ |= __b; @@ -98,11 +98,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ unsigned __clz_f = __bits_per_word - __first.__ctz_; difference_type __dn = std::min(static_cast(__clz_f), __n); __n -= __dn; - __storage_type __m = (~__storage_type(0) << __first.__ctz_) & (~__storage_type(0) >> (__clz_f - __dn)); + __storage_type __m = std::__middle_mask<__storage_type>(__clz_f - __dn, __first.__ctz_); __storage_type __b = *__first.__seg_ & __m; unsigned __clz_r = __bits_per_word - __result.__ctz_; __storage_type __ddn = std::min<__storage_type>(__dn, __clz_r); - __m = (~__storage_type(0) << __result.__ctz_) & (~__storage_type(0) >> (__clz_r - __ddn)); + __m = std::__middle_mask<__storage_type>(__clz_r - __ddn, __result.__ctz_); *__result.__seg_ &= ~__m; if (__result.__ctz_ > __first.__ctz_) *__result.__seg_ |= __b << (__result.__ctz_ - __first.__ctz_); @@ -112,7 +112,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ __result.__ctz_ = static_cast((__ddn + __result.__ctz_) % __bits_per_word); __dn -= __ddn; if (__dn > 0) { - __m = ~__storage_type(0) >> (__bits_per_word - __dn); + __m = std::__trailing_mask<__storage_type>(__bits_per_word - __dn); *__result.__seg_ &= ~__m; *__result.__seg_ |= __b >> (__first.__ctz_ + __ddn); __result.__ctz_ = static_cast(__dn); @@ -123,7 +123,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _ // __first.__ctz_ == 0; // do middle words unsigned __clz_r = __bits_per_word - __result.__ctz_; - __storage_type __m = ~__storage_type(0) << __result.__ctz_; + __storage_type __m = std::__leading_mask<__storage_type>(__result.__ctz_); for (; __n >= __bits_per_word; __n -= __bits_per_word, ++__first.__seg_) { __storage_type __b = *__first.__seg_; *__result.__seg_ &= ~__m; @@ -134,17 +134,17 @@ _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::__trailing_mask<__storage_type>(__bits_per_word - __n); __storage_type __b = *__first.__seg_ & __m; __storage_type __dn = std::min(__n, static_cast(__clz_r)); - __m = (~__storage_type(0) << __result.__ctz_) & (~__storage_type(0) >> (__clz_r - __dn)); + __m = std::__middle_mask<__storage_type>(__clz_r - __dn, __result.__ctz_); *__result.__seg_ &= ~__m; *__result.__seg_ |= __b << __result.__ctz_; __result.__seg_ += (__dn + __result.__ctz_) / __bits_per_word; __result.__ctz_ = static_cast((__dn + __result.__ctz_) % __bits_per_word); __n -= __dn; if (__n > 0) { - __m = ~__storage_type(0) >> (__bits_per_word - __n); + __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n); *__result.__seg_ &= ~__m; *__result.__seg_ |= __b >> __dn; __result.__ctz_ = static_cast(__n); 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..36058d59cc22a 100644 --- a/libcxx/include/__fwd/bit_reference.h +++ b/libcxx/include/__fwd/bit_reference.h @@ -28,11 +28,14 @@ struct __size_difference_type_traits; template _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void -__fill_masked_range(_StoragePointer __word, unsigned __ctz, unsigned __clz, bool __fill_val); +__fill_masked_range(_StoragePointer __word, unsigned __clz, unsigned __ctz, bool __fill_val); 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.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp index 3d4ee23a5a7ff..cfcaf1c8a6dd7 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp @@ -12,10 +12,13 @@ // constexpr OutIter // constexpr after C++17 // copy(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" @@ -109,6 +112,153 @@ TEST_CONSTEXPR_CXX20 bool test() { assert(test_vector_bool(256)); } + // Validate std::copy 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/131692. + { + //// Tests for std::copy with aligned 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(in.begin() + 1, in.begin() + 2, out.begin() + 1); // out[1] = false + assert(out[1] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1) + 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(in.begin(), in.begin() + 1, out.begin()); // out[0] = false + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + assert(out[i] == true); + } + { // Test middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(32, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(32, false, Alloc(1)); + std::copy(in.begin() + 4, in.end() - 4, out.begin() + 4); + for (std::size_t i = 4; i < static_cast(in.size() - 4); ++i) + assert(in[i] == out[i]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + for (std::size_t i = 28; i < out.size(); ++i) + assert(out[i] == false); + } + + { // 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(in.begin() + 1, in.begin() + 3, out.begin() + 1); // out[1..2] = false + assert(out[1] == false); + assert(out[2] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1 && i != 2) + 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(in.begin(), in.begin() + 2, out.begin()); // out[0..1] = false + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + assert(out[i] == true); + } + { // Test middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(64, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(64, false, Alloc(1)); + std::copy(in.begin() + 8, in.end() - 8, out.begin() + 8); + for (std::size_t i = 8; i < static_cast(in.size() - 8); ++i) + assert(in[i] == out[i]); + for (std::size_t i = 0; i < 8; ++i) + assert(out[i] == false); + for (std::size_t i = static_cast(out.size() - 8); i < out.size(); ++i) + assert(out[i] == false); + } + + //// Tests for std::copy 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(in.begin() + 7, in.end(), out.begin()); // out[0] = false + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + 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(in.begin(), in.begin() + 1, out.begin() + 2); // out[2] = false + assert(out[2] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 2) + assert(out[i] == true); + } + { // Test middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(36, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(40, false, Alloc(1)); + std::copy(in.begin(), in.end(), out.begin() + 4); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 4]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + } + + { // 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(in.begin() + 14, in.end(), out.begin()); // out[0..1] = false + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + 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(in.begin(), in.begin() + 2, out.begin() + 1); // out[1..2] = false + assert(out[1] == false); + assert(out[2] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1 && i != 2) + assert(out[i] == true); + } + { // Test middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(72, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(80, false, Alloc(1)); + std::copy(in.begin(), in.end(), out.begin() + 4); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 4]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + for (std::size_t i = in.size() + 4; i < out.size(); ++i) + assert(out[i] == false); + } + } + return true; } diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp index 68356c80ba7f6..f541c914b401b 100644 --- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp @@ -25,6 +25,7 @@ #include #include "almost_satisfies_types.h" +#include "sized_allocator.h" #include "test_iterators.h" #include "test_macros.h" #include "type_algorithms.h" @@ -237,6 +238,153 @@ constexpr bool test() { assert(test_vector_bool(199)); assert(test_vector_bool(256)); } + + // Validate std::ranges::copy 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/131692. + { + //// Tests for std::ranges::copy with aligned 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(std::ranges::subrange(in.begin() + 1, in.begin() + 2), out.begin() + 1); // out[1] = false + assert(out[1] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1) + 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(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin()); // out[0] = false + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + assert(out[i] == true); + } + { // Test middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(32, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(32, false, Alloc(1)); + std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end() - 4), out.begin() + 4); + for (std::size_t i = 4; i < static_cast(in.size() - 4); ++i) + assert(in[i] == out[i]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + for (std::size_t i = 28; i < out.size(); ++i) + assert(out[i] == false); + } + + { // 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(std::ranges::subrange(in.begin() + 1, in.begin() + 3), out.begin() + 1); // out[1..2] = false + assert(out[1] == false); + assert(out[2] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1 && i != 2) + 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(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin()); // out[0..1] = false + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + assert(out[i] == true); + } + { // Test middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(64, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(64, false, Alloc(1)); + std::ranges::copy(std::ranges::subrange(in.begin() + 8, in.end() - 8), out.begin() + 8); + for (std::size_t i = 8; i < static_cast(in.size() - 8); ++i) + assert(in[i] == out[i]); + for (std::size_t i = 0; i < 8; ++i) + assert(out[i] == false); + for (std::size_t i = static_cast(out.size() - 8); i < out.size(); ++i) + assert(out[i] == false); + } + + //// Tests for std::ranges::copy 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(std::ranges::subrange(in.begin() + 7, in.end()), out.begin()); // out[0] = false + assert(out[0] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + 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(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin() + 2); // out[2] = false + assert(out[2] == false); + for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 2) + assert(out[i] == true); + } + { // Test middle (whole) words for uint8_t + using Alloc = sized_allocator; + std::vector in(36, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(40, false, Alloc(1)); + std::ranges::copy(std::ranges::subrange(in.begin(), in.end()), out.begin() + 4); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 4]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + } + + { // 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(std::ranges::subrange(in.begin() + 14, in.end()), out.begin()); // out[0..1] = false + assert(out[0] == false); + assert(out[1] == false); + for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + 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(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin() + 1); // out[1..2] = false + assert(out[1] == false); + assert(out[2] == false); + for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged + if (i != 1 && i != 2) + assert(out[i] == true); + } + { // Test middle (whole) words for uint16_t + using Alloc = sized_allocator; + std::vector in(72, true, Alloc(1)); + for (std::size_t i = 0; i < in.size(); i += 2) + in[i] = false; + std::vector out(80, false, Alloc(1)); + std::ranges::copy(std::ranges::subrange(in.begin(), in.end()), out.begin() + 4); + for (std::size_t i = 0; i < in.size(); ++i) + assert(in[i] == out[i + 4]); + for (std::size_t i = 0; i < 4; ++i) + assert(out[i] == false); + for (std::size_t i = in.size() + 4; i < out.size(); ++i) + assert(out[i] == false); + } + } #endif return true;