[libcxx-commits] [libcxx] [libc++] Fix copy for vector<bool> with small storage types (PR #131545)
Peng Liu via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Mar 16 17:14:42 PDT 2025
https://github.com/winner245 created https://github.com/llvm/llvm-project/pull/131545
None
>From 07566587b7673555ca7af4e06b2b1379a7d9a5ba Mon Sep 17 00:00:00 2001
From: Peng Liu <winner245 at hotmail.com>
Date: Sun, 16 Mar 2025 16:46:10 -0400
Subject: [PATCH] Fix copy for vector<bool> with small storage types
---
libcxx/include/__algorithm/copy.h | 18 +--
libcxx/include/__bit_reference | 8 ++
libcxx/include/__fwd/bit_reference.h | 3 +
.../alg.copy/copy.pass.cpp | 128 ++++++++++++++++++
.../alg.copy/ranges.copy.pass.cpp | 128 ++++++++++++++++++
5 files changed, 276 insertions(+), 9 deletions(-)
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<difference_type>(__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<difference_type>(__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<unsigned>((__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<unsigned>(__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<difference_type>(__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<unsigned>((__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<unsigned>(__n);
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 58cbd4d51c88e..490a4678e874a 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -82,6 +82,14 @@ _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>
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 <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);
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..d611027cb27df 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
@@ -16,6 +16,7 @@
#include <cassert>
#include <vector>
+#include "sized_allocator.h"
#include "test_macros.h"
#include "test_iterators.h"
#include "type_algorithms.h"
@@ -109,6 +110,133 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(test_vector_bool(256));
}
+ // Make sure std::copy behaves properly with std::vector<bool> iterators with custom size types.
+ {
+ //// Tests for std::copy 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(6, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::copy(in.begin() + 4, in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin() + 4, in.end(), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin());
+ // assert(std::equal(in.begin(), in.end(), out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test middle words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(24, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(29, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin());
+ // assert(std::equal(in.begin(), in.end(), out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::copy(in.begin() + 4, in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin() + 4, in.end(), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(24, true, Alloc(1));
+ std::vector<bool, Alloc> out(32, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin());
+ // assert(std::equal(in.begin(), in.end(), out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test middle words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(48, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(55, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin());
+ // assert(std::equal(in.begin(), in.end(), out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+
+ //// Tests for std::copy 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(6, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::copy(in.begin() + 4, in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin() + 4, in.end(), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(4, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 3);
+ // assert(std::equal(in.begin(), in.end(), out.begin() + 3));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 3]);
+ }
+ { // Test middle words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(16, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(24, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin(), in.end(), out.begin() + 4));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::copy(in.begin() + 4, in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin() + 4, in.end(), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 3);
+ // assert(std::equal(in.begin(), in.end(), out.begin() + 3));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 3]);
+ }
+ { // Test the middle words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(32, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(64, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 4);
+ // assert(std::equal(in.begin(), in.end(), out.begin() + 4));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ }
+ }
+
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..fcaf4d7ee28c8 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 <vector>
#include "almost_satisfies_types.h"
+#include "sized_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
#include "type_algorithms.h"
@@ -237,6 +238,133 @@ constexpr bool test() {
assert(test_vector_bool(199));
assert(test_vector_bool(256));
}
+
+ // Make sure std::ranges::copy behaves properly with std::vector<bool> iterators with custom size types.
+ {
+ //// Tests for std::ranges::copy 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(6, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4);
+ // assert(std::ranges::equal(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::ranges::copy(in, out.begin());
+ // assert(std::ranges::equal(in, out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test middle words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(24, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(29, false, Alloc(1));
+ std::ranges::copy(in, out.begin());
+ // assert(std::ranges::equal(in, out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4);
+ // assert(std::ranges::equal(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(24, true, Alloc(1));
+ std::vector<bool, Alloc> out(32, false, Alloc(1));
+ std::ranges::copy(in, out.begin());
+ // assert(std::ranges::equal(in, out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test middle words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(48, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(55, false, Alloc(1));
+ std::ranges::copy(in, out.begin());
+ // assert(std::ranges::equal(in, out.begin()));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+
+ //// Tests for std::ranges::copy 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(6, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4);
+ // assert(std::ranges::equal(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(4, true, Alloc(1));
+ std::vector<bool, Alloc> out(8, false, Alloc(1));
+ std::ranges::copy(in, out.begin() + 3);
+ // assert(std::ranges::equal(in, out.begin() + 3));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 3]);
+ }
+ { // Test middle words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(16, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(24, false, Alloc(1));
+ std::ranges::copy(in, out.begin() + 4);
+ // assert(std::ranges::equal(in, out.begin() + 4));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4);
+ // assert(std::ranges::equal(std::ranges::subrange(in.begin() + 4, in.end()), out.begin() + 4));
+ for (std::size_t i = 4; i < in.size(); ++i)
+ assert(in[i] == out[i]);
+ }
+ { // Test the last word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(12, true, Alloc(1));
+ std::vector<bool, Alloc> out(16, false, Alloc(1));
+ std::ranges::copy(in, out.begin() + 3);
+ // assert(std::ranges::equal(in, out.begin() + 3));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 3]);
+ }
+ { // Test the middle words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(32, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(64, false, Alloc(1));
+ std::ranges::copy(in, out.begin() + 4);
+ // assert(std::ranges::equal(in, out.begin() + 4));
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ }
+ }
#endif
return true;
More information about the libcxx-commits
mailing list