Skip to content

Commit 4ec181e

Browse files
committed
Unify __countr_zero_impl for C++03 and later
1 parent a250c1b commit 4ec181e

File tree

4 files changed

+70
-100
lines changed

4 files changed

+70
-100
lines changed

libcxx/include/__bit/countr.h

Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
#ifndef _LIBCPP___BIT_COUNTR_H
1313
#define _LIBCPP___BIT_COUNTR_H
1414

15+
#include <__assert>
1516
#include <__bit/rotate.h>
1617
#include <__concepts/arithmetic.h>
1718
#include <__config>
18-
#include <__type_traits/enable_if.h>
1919
#include <__type_traits/is_unsigned.h>
2020
#include <limits>
2121

@@ -40,74 +40,37 @@ _LIBCPP_BEGIN_NAMESPACE_STD
4040
return __builtin_ctzll(__x);
4141
}
4242

43-
#ifndef _LIBCPP_CXX03_LANG
44-
// constexpr implementation for C++11 and later
45-
43+
// A constexpr implementation for C++11 and later (using clang extensions for constexpr support)
4644
// Precondition: __t != 0 (the caller __countr_zero handles __t == 0 as a special case)
4745
template <class _Tp>
48-
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI constexpr int __countr_zero_impl(_Tp __t) _NOEXCEPT {
46+
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int __countr_zero_impl(_Tp __t) _NOEXCEPT {
47+
_LIBCPP_ASSERT_INTERNAL(__t != 0, "__countr_zero_impl called with zero value");
4948
static_assert(is_unsigned<_Tp>::value, "__countr_zero_impl only works with unsigned types");
50-
if constexpr (sizeof(_Tp) <= sizeof(unsigned int)) {
49+
// Use constexpr if as a C++17 extension for clang
50+
if _LIBCPP_CONSTEXPR (sizeof(_Tp) <= sizeof(unsigned int)) {
5151
return std::__libcpp_ctz(static_cast<unsigned int>(__t));
52-
} else if constexpr (sizeof(_Tp) <= sizeof(unsigned long)) {
52+
} else if _LIBCPP_CONSTEXPR (sizeof(_Tp) <= sizeof(unsigned long)) {
5353
return std::__libcpp_ctz(static_cast<unsigned long>(__t));
54-
} else if constexpr (sizeof(_Tp) <= sizeof(unsigned long long)) {
54+
} else if _LIBCPP_CONSTEXPR (sizeof(_Tp) <= sizeof(unsigned long long)) {
5555
return std::__libcpp_ctz(static_cast<unsigned long long>(__t));
5656
} else {
57-
# if _LIBCPP_STD_VER == 11
58-
// A recursive constexpr implementation for C++11
57+
#if _LIBCPP_STD_VER == 11
58+
// A constexpr implementation for C++11 using variable declaration as a C++14 extension for clang
5959
unsigned long long __ull = static_cast<unsigned long long>(__t);
6060
const unsigned int __ulldigits = numeric_limits<unsigned long long>::digits;
6161
return __ull == 0ull ? __ulldigits + std::__countr_zero_impl<_Tp>(__t >> __ulldigits) : std::__libcpp_ctz(__ull);
62-
# else
62+
#else
6363
int __ret = 0;
6464
const unsigned int __ulldigits = numeric_limits<unsigned long long>::digits;
6565
while (static_cast<unsigned long long>(__t) == 0uLL) {
6666
__ret += __ulldigits;
6767
__t >>= __ulldigits;
6868
}
6969
return __ret + std::__libcpp_ctz(static_cast<unsigned long long>(__t));
70-
# endif
71-
}
72-
}
73-
74-
#else
75-
// implementation for C++03
76-
77-
template < class _Tp, __enable_if_t<is_unsigned<_Tp>::value && sizeof(_Tp) <= sizeof(unsigned int), int> = 0>
78-
_LIBCPP_HIDE_FROM_ABI int __countr_zero_impl(_Tp __t) {
79-
return std::__libcpp_ctz(static_cast<unsigned int>(__t));
80-
}
81-
82-
template < class _Tp,
83-
__enable_if_t<is_unsigned<_Tp>::value && (sizeof(_Tp) > sizeof(unsigned int)) &&
84-
sizeof(_Tp) <= sizeof(unsigned long),
85-
int> = 0 >
86-
_LIBCPP_HIDE_FROM_ABI int __countr_zero_impl(_Tp __t) {
87-
return std::__libcpp_ctz(static_cast<unsigned long>(__t));
88-
}
89-
90-
template < class _Tp,
91-
__enable_if_t<is_unsigned<_Tp>::value && (sizeof(_Tp) > sizeof(unsigned long)) &&
92-
sizeof(_Tp) <= sizeof(unsigned long long),
93-
int> = 0 >
94-
_LIBCPP_HIDE_FROM_ABI int __countr_zero_impl(_Tp __t) {
95-
return std::__libcpp_ctz(static_cast<unsigned long long>(__t));
96-
}
97-
98-
template < class _Tp, __enable_if_t<is_unsigned<_Tp>::value && (sizeof(_Tp) > sizeof(unsigned long long)), int> = 0 >
99-
_LIBCPP_HIDE_FROM_ABI int __countr_zero_impl(_Tp __t) {
100-
int __ret = 0;
101-
const unsigned int __ulldigits = numeric_limits<unsigned long long>::digits;
102-
while (static_cast<unsigned long long>(__t) == 0uLL) {
103-
__ret += __ulldigits;
104-
__t >>= __ulldigits;
70+
#endif
10571
}
106-
return __ret + std::__libcpp_ctz(static_cast<unsigned long long>(__t));
10772
}
10873

109-
#endif // _LIBCPP_CXX03_LANG
110-
11174
template <class _Tp>
11275
[[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int __countr_zero(_Tp __t) _NOEXCEPT {
11376
static_assert(is_unsigned<_Tp>::value, "__countr_zero only works with unsigned types");

libcxx/include/__bit_reference

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <__algorithm/min.h>
1919
#include <__assert>
2020
#include <__bit/countr.h>
21-
#include <__bit/invert_if.h>
2221
#include <__compare/ordering.h>
2322
#include <__config>
2423
#include <__cstddef/ptrdiff_t.h>
@@ -68,12 +67,21 @@ struct __size_difference_type_traits<_Cp, __void_t<typename _Cp::difference_type
6867
using size_type = typename _Cp::size_type;
6968
};
7069

70+
// The `__x_mask` functions are designed to work exclusively with any unsigned `_StorageType`s, including small
71+
// integral types such as unsigned char/short, `uint8_t`, and `uint16_t`. To prevent undefined behaviors or
72+
// ambiguities due to integral promotions for the small integral types, all bitwise operations are explicitly
73+
// cast back to the unsigned `_StorageType`.
74+
75+
// Creates a mask of type `_StorageType` with a specified number of leading zeros (__clz) and sets all remaining
76+
// bits to one
7177
template <class _StorageType>
7278
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask(unsigned __clz) {
7379
static_assert(is_unsigned<_StorageType>::value, "__trailing_mask only works with unsigned types");
7480
return static_cast<_StorageType>(static_cast<_StorageType>(~static_cast<_StorageType>(0)) >> __clz);
7581
}
7682

83+
// Creates a mask of type `_StorageType` with a specified number of leading zeros (__clz), a specified number of
84+
// trailing zeros (__ctz), and sets all bits in between to one
7785
template <class _StorageType>
7886
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz) {
7987
static_assert(is_unsigned<_StorageType>::value, "__middle_mask only works with unsigned types");

libcxx/test/std/algorithms/alg.nonmodifying/alg.find/find.pass.cpp

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,29 @@ struct TestIntegerPromotions {
211211
}
212212
};
213213

214-
TEST_CONSTEXPR_CXX20 void test_bit_iterator_with_custom_sized_types() {
214+
TEST_CONSTEXPR_CXX20 bool test() {
215+
types::for_each(types::integer_types(), TestTypes<char>());
216+
types::for_each(types::integer_types(), TestTypes<int>());
217+
types::for_each(types::integer_types(), TestTypes<long long>());
218+
#if TEST_STD_VER >= 20
219+
Test<TriviallyComparable<char>, TriviallyComparable<char>>().operator()<TriviallyComparable<char>*>();
220+
Test<TriviallyComparable<wchar_t>, TriviallyComparable<wchar_t>>().operator()<TriviallyComparable<wchar_t>*>();
221+
#endif
222+
223+
// TODO: Remove the `_LIBCPP_ENABLE_EXPERIMENTAL` check once we have the FTM guarded or views::join isn't
224+
// experimental anymore
225+
#if TEST_STD_VER >= 20 && (!defined(_LIBCPP_VERSION) || defined(_LIBCPP_ENABLE_EXPERIMENTAL))
226+
{
227+
std::vector<std::vector<int>> vec = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
228+
auto view = vec | std::views::join;
229+
assert(std::find(view.begin(), view.end(), 4) == std::next(view.begin(), 3));
230+
}
231+
#endif
232+
233+
types::for_each(types::integral_types(), TestIntegerPromotions());
234+
235+
// Verify that the std::vector<bool>::iterator optimization works properly for allocators with custom size types
236+
// See https://github.com/llvm/llvm-project/issues/122528
215237
{
216238
using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
217239
std::vector<bool, Alloc> in(100, false, Alloc(1));
@@ -236,27 +258,6 @@ TEST_CONSTEXPR_CXX20 void test_bit_iterator_with_custom_sized_types() {
236258
in[in.size() - 2] = true;
237259
assert(std::find(in.begin(), in.end(), true) == in.end() - 2);
238260
}
239-
}
240-
241-
TEST_CONSTEXPR_CXX20 bool test() {
242-
types::for_each(types::integer_types(), TestTypes<char>());
243-
types::for_each(types::integer_types(), TestTypes<int>());
244-
types::for_each(types::integer_types(), TestTypes<long long>());
245-
#if TEST_STD_VER >= 20
246-
Test<TriviallyComparable<char>, TriviallyComparable<char>>().operator()<TriviallyComparable<char>*>();
247-
Test<TriviallyComparable<wchar_t>, TriviallyComparable<wchar_t>>().operator()<TriviallyComparable<wchar_t>*>();
248-
#endif
249-
250-
#if TEST_STD_VER >= 20
251-
{
252-
std::vector<std::vector<int>> vec = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
253-
auto view = vec | std::views::join;
254-
assert(std::find(view.begin(), view.end(), 4) == std::next(view.begin(), 3));
255-
}
256-
#endif
257-
258-
types::for_each(types::integral_types(), TestIntegerPromotions());
259-
test_bit_iterator_with_custom_sized_types();
260261

261262
{ // Test vector<bool>::iterator optimization
262263
std::vector<bool> vec(256 + 8);

libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -124,33 +124,6 @@ class TriviallyComparable {
124124
bool operator==(const TriviallyComparable&) const = default;
125125
};
126126

127-
constexpr void test_bit_iterator_with_custom_sized_types() {
128-
{
129-
using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
130-
std::vector<bool, Alloc> in(100, false, Alloc(1));
131-
in[in.size() - 2] = true;
132-
assert(std::ranges::find(in, true) == in.end() - 2);
133-
}
134-
{
135-
using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
136-
std::vector<bool, Alloc> in(199, false, Alloc(1));
137-
in[in.size() - 2] = true;
138-
assert(std::ranges::find(in, true) == in.end() - 2);
139-
}
140-
{
141-
using Alloc = sized_allocator<bool, std::uint32_t, std::int32_t>;
142-
std::vector<bool, Alloc> in(200, false, Alloc(1));
143-
in[in.size() - 2] = true;
144-
assert(std::ranges::find(in, true) == in.end() - 2);
145-
}
146-
{
147-
using Alloc = sized_allocator<bool, std::uint64_t, std::int64_t>;
148-
std::vector<bool, Alloc> in(257, false, Alloc(1));
149-
in[in.size() - 2] = true;
150-
assert(std::ranges::find(in, true) == in.end() - 2);
151-
}
152-
}
153-
154127
constexpr bool test() {
155128
types::for_each(types::type_list<char, wchar_t, int, long, TriviallyComparable<char>, TriviallyComparable<wchar_t>>{},
156129
[]<class T> {
@@ -245,7 +218,32 @@ constexpr bool test() {
245218
}
246219
}
247220

248-
test_bititer_with_custom_sized_types();
221+
// Verify that the std::vector<bool>::iterator optimization works properly for allocators with custom size types
222+
// See https://github.com/llvm/llvm-project/issues/122528
223+
{
224+
using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
225+
std::vector<bool, Alloc> in(100, false, Alloc(1));
226+
in[in.size() - 2] = true;
227+
assert(std::ranges::find(in, true) == in.end() - 2);
228+
}
229+
{
230+
using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
231+
std::vector<bool, Alloc> in(199, false, Alloc(1));
232+
in[in.size() - 2] = true;
233+
assert(std::ranges::find(in, true) == in.end() - 2);
234+
}
235+
{
236+
using Alloc = sized_allocator<bool, std::uint32_t, std::int32_t>;
237+
std::vector<bool, Alloc> in(200, false, Alloc(1));
238+
in[in.size() - 2] = true;
239+
assert(std::ranges::find(in, true) == in.end() - 2);
240+
}
241+
{
242+
using Alloc = sized_allocator<bool, std::uint64_t, std::int64_t>;
243+
std::vector<bool, Alloc> in(257, false, Alloc(1));
244+
in[in.size() - 2] = true;
245+
assert(std::ranges::find(in, true) == in.end() - 2);
246+
}
249247

250248
return true;
251249
}

0 commit comments

Comments
 (0)