Skip to content

[libc++] Fix copy_backward for vector<bool> with small storage types #131560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions libcxx/include/__algorithm/copy_backward.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
difference_type __dn = std::min(static_cast<difference_type>(__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;
Expand All @@ -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;
Expand All @@ -94,12 +94,12 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
difference_type __dn = std::min(static_cast<difference_type>(__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<difference_type>(__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_);
Expand All @@ -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<unsigned>(-__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_);
Expand All @@ -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;
Expand All @@ -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<difference_type>(__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<unsigned>(((-__dn & (__bits_per_word - 1)) + __result.__ctz_) % __bits_per_word);
Expand All @@ -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<unsigned>(-__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));
}
Expand Down
11 changes: 9 additions & 2 deletions libcxx/include/__bit_reference
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class _StorageType>
_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 <class _StorageType>
_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`,
Expand Down
3 changes: 3 additions & 0 deletions libcxx/include/__fwd/bit_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ __fill_masked_range(_StoragePointer __word, unsigned __ctz, unsigned __clz, bool
template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask(unsigned __clz);

template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz);

template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
// constexpr OutIter // constexpr after C++17
// copy_backward(InIter first, InIter last, OutIter result);

// XFAIL: FROZEN-CXX03-HEADERS-FIXME

#include <algorithm>
#include <cassert>
#include <vector>

#include "sized_allocator.h"
#include "test_macros.h"
#include "test_iterators.h"
#include "type_algorithms.h"
Expand Down Expand Up @@ -111,6 +114,146 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(test_vector_bool(256));
}

// Validate std::copy_backward with std::vector<bool> 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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(7, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(8, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> out(8, true, Alloc(1));
std::copy_backward(in.end() - 4, in.end(), out.end());
for (std::size_t i = 0; i < static_cast<std::size_t>(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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(17, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> 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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(14, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(16, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> out(16, true, Alloc(1));
std::copy_backward(in.end() - 8, in.end(), out.end());
for (std::size_t i = 0; i < static_cast<std::size_t>(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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(34, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> 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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(8, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(8, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint8_t, std::int8_t>;
std::vector<bool, Alloc> in(16, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> 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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(16, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(16, false, Alloc(1));
std::vector<bool, Alloc> 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<bool, std::uint16_t, std::int16_t>;
std::vector<bool, Alloc> in(32, false, Alloc(1));
for (std::size_t i = 0; i < in.size(); i += 2)
in[i] = true;
std::vector<bool, Alloc> 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;
}

Expand Down
Loading
Loading