From 982404a83590a421dc286fb414fc5b18117b1cec Mon Sep 17 00:00:00 2001 From: Denis Yaroshevskiy Date: Mon, 30 Sep 2024 12:34:40 -0700 Subject: [PATCH 1/2] rework simd traits to be c++17 friendly Summary: previous version of simd contains was rolled back because of no c++17 support. This should hopefully rectify that. Differential Revision: D63635013 --- folly/algorithm/simd/FindFixed.h | 3 +- folly/algorithm/simd/detail/BUCK | 1 + folly/algorithm/simd/detail/Traits.h | 107 ++++++----- folly/algorithm/simd/detail/test/BUCK | 1 + .../algorithm/simd/detail/test/TraitsTest.cpp | 181 +++++++++++------- folly/container/BUCK | 3 +- folly/container/Iterator.h | 6 + folly/container/span.h | 17 ++ folly/container/test/span_test.cpp | 40 ++++ 9 files changed, 241 insertions(+), 118 deletions(-) diff --git a/folly/algorithm/simd/FindFixed.h b/folly/algorithm/simd/FindFixed.h index 547b02a6b3a..455799ad206 100644 --- a/folly/algorithm/simd/FindFixed.h +++ b/folly/algorithm/simd/FindFixed.h @@ -293,7 +293,8 @@ constexpr std::optional findFixed(std::span where, U x) return find_fixed_detail::findFixedConstexpr(std::span(where), x); } else { return find_fixed_detail::findFixedDispatch( - detail::asSimdFriendlyUint(where), detail::asSimdFriendlyUint(x)); + simd::detail::asSimdFriendlyUint(where), + simd::detail::asSimdFriendlyUint(x)); } } diff --git a/folly/algorithm/simd/detail/BUCK b/folly/algorithm/simd/detail/BUCK index 53a172a2e6b..7a347fb5333 100644 --- a/folly/algorithm/simd/detail/BUCK +++ b/folly/algorithm/simd/detail/BUCK @@ -40,6 +40,7 @@ cpp_library( name = "traits", headers = ["Traits.h"], exported_deps = [ + "//folly:c_portability", "//folly:memory", "//folly:traits", "//folly/container:span", diff --git a/folly/algorithm/simd/detail/Traits.h b/folly/algorithm/simd/detail/Traits.h index ee2ef3d5105..f3e34cb7eee 100644 --- a/folly/algorithm/simd/detail/Traits.h +++ b/folly/algorithm/simd/detail/Traits.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include @@ -23,7 +24,7 @@ #include #include -namespace folly::detail { +namespace folly::simd::detail { template auto findSimdFriendlyEquivalent() { @@ -36,65 +37,75 @@ auto findSimdFriendlyEquivalent() { return double{}; } } else if constexpr (std::is_signed_v) { - if constexpr (sizeof(T) == 1) { - return std::int8_t{}; - } else if constexpr (sizeof(T) == 2) { - return std::int16_t{}; - } else if constexpr (sizeof(T) == 4) { - return std::int32_t{}; - } else if constexpr (sizeof(T) == 8) { - return std::int64_t{}; - } + return int_bits_t{}; } else if constexpr (std::is_unsigned_v) { - if constexpr (sizeof(T) == 1) { - return std::uint8_t{}; - } else if constexpr (sizeof(T) == 2) { - return std::uint16_t{}; - } else if constexpr (sizeof(T) == 4) { - return std::uint32_t{}; - } else if constexpr (sizeof(T) == 8) { - return std::uint64_t{}; - } + return uint_bits_t{}; } } template -concept has_simd_friendly_equivalent = +constexpr bool has_simd_friendly_equivalent_scalar = !std::is_same_v())>; -template -using simd_friendly_equivalent_t = folly::like_t< // - T, - decltype(findSimdFriendlyEquivalent>())>; +template +using simd_friendly_equivalent_scalar_t = std::enable_if_t< + has_simd_friendly_equivalent_scalar, + like_t>())>>; template -concept has_integral_simd_friendly_equivalent = - has_simd_friendly_equivalent && // have to explicitly specify this for - // subsumption to work - std::integral>; +constexpr bool has_integral_simd_friendly_equivalent_scalar = + std::is_integral_v< // void will return false + decltype(findSimdFriendlyEquivalent>())>; -template -using integral_simd_friendly_equivalent = simd_friendly_equivalent_t; +template +using unsigned_simd_friendly_equivalent_scalar_t = std::enable_if_t< + has_integral_simd_friendly_equivalent_scalar, + like_t>>; -template -auto asSimdFriendly(folly::span s) { - return folly::reinterpret_span_cast>(s); -} +template +using span_for = decltype(folly::span(std::declval())); -template -constexpr auto asSimdFriendly(T x) { - return static_cast>(x); -} +struct AsSimdFriendlyFn { + template + FOLLY_ERASE auto operator()(folly::span s) const + -> folly::span, extent> { + return reinterpret_span_cast>(s); + } -template -auto asSimdFriendlyUint(folly::span s) { - return folly::reinterpret_span_cast< - folly::like_t>>(s); -} + template + FOLLY_ERASE auto operator()(R&& r) const + -> decltype(operator()(span_for(r))) { + return operator()(folly::span(r)); + } -template -constexpr auto asSimdFriendlyUint(T x) { - return static_cast>(x); -} + template + FOLLY_ERASE constexpr auto operator()(T x) const + -> simd_friendly_equivalent_scalar_t { + return static_cast>(x); + } +}; +inline constexpr AsSimdFriendlyFn asSimdFriendly; + +struct AsSimdFriendlyUintFn { + template + FOLLY_ERASE auto operator()(folly::span s) const + -> folly::span, extent> { + return reinterpret_span_cast>( + s); + } + + template + FOLLY_ERASE auto operator()(R&& r) const + -> decltype(operator()(span_for(r))) { + return operator()(folly::span(r)); + } + + template + FOLLY_ERASE constexpr auto operator()(T x) const + -> unsigned_simd_friendly_equivalent_scalar_t { + return static_cast>(x); + } +}; +inline constexpr AsSimdFriendlyUintFn asSimdFriendlyUint; -} // namespace folly::detail +} // namespace folly::simd::detail diff --git a/folly/algorithm/simd/detail/test/BUCK b/folly/algorithm/simd/detail/test/BUCK index 6661c2ce012..dea91d505cf 100644 --- a/folly/algorithm/simd/detail/test/BUCK +++ b/folly/algorithm/simd/detail/test/BUCK @@ -27,6 +27,7 @@ cpp_unittest( cpp_unittest( name = "traits_test", srcs = ["TraitsTest.cpp"], + compiler_flags = ["--std=c++17"], deps = [ "//folly/algorithm/simd/detail:traits", "//folly/portability:gmock", diff --git a/folly/algorithm/simd/detail/test/TraitsTest.cpp b/folly/algorithm/simd/detail/test/TraitsTest.cpp index 5d01d9630df..23247b0c6f5 100644 --- a/folly/algorithm/simd/detail/test/TraitsTest.cpp +++ b/folly/algorithm/simd/detail/test/TraitsTest.cpp @@ -19,84 +19,147 @@ #include #include -namespace folly::detail { +#include +#include + +namespace folly::simd::detail { struct FollySimdTraitsTest : testing::Test {}; -namespace simd_friendly_equivalent_test { +namespace simd_friendly_equivalent_scalar_test { // ints -static_assert( - std::is_same_v>); -static_assert( - std::is_same_v>); +static_assert(std::is_same_v< + std::int8_t, + simd_friendly_equivalent_scalar_t>); +static_assert(std::is_same_v< + std::uint8_t, + simd_friendly_equivalent_scalar_t>); -static_assert(std::is_same_v>); static_assert( - std::is_same_v>); + std::is_same_v>); +static_assert(std::is_same_v< + std::uint16_t, + simd_friendly_equivalent_scalar_t>); -static_assert(std::is_same_v>); static_assert( - std::is_same_v>); - -static_assert( - std::is_same_v>); -static_assert( - std::is_same_v>); + std::is_same_v>); +static_assert(std::is_same_v< + std::uint32_t, + simd_friendly_equivalent_scalar_t>); + +static_assert(std::is_same_v< + std::int64_t, + simd_friendly_equivalent_scalar_t>); +static_assert(std::is_same_v< + std::uint64_t, + simd_friendly_equivalent_scalar_t>); // floats -static_assert(std::is_same_v>); -static_assert(std::is_same_v>); +static_assert(std::is_same_v>); +static_assert( + std::is_same_v>); // enum enum SomeInt {}; enum class SomeIntClass : std::int32_t {}; static_assert( - std::is_same_v>); -static_assert( - std::is_same_v>); + std::is_same_v>); +static_assert(std::is_same_v< + std::int32_t, + simd_friendly_equivalent_scalar_t>); // const -static_assert( - std::is_same_v>); +static_assert(std::is_same_v< + const std::int32_t, + simd_friendly_equivalent_scalar_t>); // sfinae -constexpr auto sfinae_call = - [](T) -> simd_friendly_equivalent_t { return {}; }; -static_assert(std::invocable); +struct sfinae_call { + template + simd_friendly_equivalent_scalar_t operator()(T) const { + return {}; + } +}; + +static_assert(std::is_invocable_v); struct NotSimdFriendly {}; -static_assert(!std::invocable); +static_assert(!std::is_invocable_v); -} // namespace simd_friendly_equivalent_test +} // namespace simd_friendly_equivalent_scalar_test -namespace integral_simd_friendly_equivalent_test { +namespace as_simd_friendly_type_test { -static_assert(std::is_same_v< // - std::int8_t, - integral_simd_friendly_equivalent>); +template +using asSimdFriendlyResult = std::invoke_result_t; -struct Overloading { - constexpr int operator()(auto) { return 0; } - constexpr int operator()(has_simd_friendly_equivalent auto) { return 1; } - constexpr int operator()(has_integral_simd_friendly_equivalent auto) { - return 2; - } -}; +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyResult>>); -// Subsumption tests -struct NotSimdFriendly {}; -enum class SomeInt {}; +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyResult>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyResult&>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyResult&>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyResult&>>); + +static_assert(std::is_same_v>); -static_assert(Overloading{}(NotSimdFriendly{}) == 0); -static_assert(Overloading{}(float{}) == 1); -static_assert(Overloading{}(int{}) == 2); -static_assert(Overloading{}(SomeInt{}) == 2); +static_assert(!std::is_invocable_v>); -} // namespace integral_simd_friendly_equivalent_test +} // namespace as_simd_friendly_type_test + +namespace as_simd_friendly_uint_type_test { + +template +using asSimdFriendlyUintResult = std::invoke_result_t; + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyUintResult>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyUintResult>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyUintResult>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyUintResult&>>); + +static_assert(std::is_same_v< + folly::span, + asSimdFriendlyUintResult&>>); + +static_assert( + std::is_same_v>); + +static_assert( + !std::is_invocable_v&>); + +static_assert( + !std::is_invocable_v&>); + +static_assert(!std::is_invocable_v>); + +} // namespace as_simd_friendly_uint_type_test TEST_F(FollySimdTraitsTest, AsSimdFriendly) { enum SomeEnum : int { Foo = 1, Bar, Baz }; @@ -108,32 +171,14 @@ TEST_F(FollySimdTraitsTest, AsSimdFriendly) { ASSERT_THAT(castSpan, testing::ElementsAre(1, 2, 3)); } -template -void isSameTest(const T&, const U&) = delete; - -template -void isSameTest(const T&, const T&) {} - -template -void asSimdFriendlyUintTypeTest() { - isSameTest(asSimdFriendlyUint(From{}), To{}); - isSameTest(asSimdFriendlyUint(std::span{}), std::span{}); - isSameTest( - asSimdFriendlyUint(std::span{}), std::span{}); -} - TEST_F(FollySimdTraitsTest, AsSimdFriendlyUint) { enum SomeEnum : int { Foo = 1, Bar, Baz }; static_assert(asSimdFriendlyUint(SomeEnum::Foo) == 1U); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); - asSimdFriendlyUintTypeTest(); + std::array arr{SomeEnum::Foo, SomeEnum::Bar, SomeEnum::Baz}; + folly::span castSpan = asSimdFriendlyUint(folly::span(arr)); + ASSERT_THAT(castSpan, testing::ElementsAre(1, 2, 3)); } -} // namespace folly::detail +} // namespace folly::simd::detail diff --git a/folly/container/BUCK b/folly/container/BUCK index 528f8b81fad..61ab6de5431 100644 --- a/folly/container/BUCK +++ b/folly/container/BUCK @@ -172,11 +172,12 @@ cpp_library( name = "span", headers = ["span.h"], exported_deps = [ + ":access", + ":iterator", "//folly:cpp_attributes", "//folly:portability", "//folly:traits", "//folly:utility", - "//folly/container:access", "//folly/functional:invoke", "//folly/portability:constexpr", ], diff --git a/folly/container/Iterator.h b/folly/container/Iterator.h index 68e1baacf9a..bfecb51e753 100644 --- a/folly/container/Iterator.h +++ b/folly/container/Iterator.h @@ -112,6 +112,12 @@ inline constexpr bool iterator_category_matches_v = template using iterator_value_type_t = typename std::iterator_traits::value_type; +// iterator_reference_type_t +// +// Extracts reference from an iterator (C++20 iter_reference_t backported) +template +using iterator_reference_t = decltype(*std::declval()); + // iterator_key_type_t // // Extracts a key type from an iterator, leverages the knowledge that diff --git a/folly/container/span.h b/folly/container/span.h index f326bcf0cbf..50aa4eb7468 100644 --- a/folly/container/span.h +++ b/folly/container/span.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -247,6 +248,22 @@ class span { } }; +template +span(T*, EndOrSize) -> span; + +template +span(T (&)[N]) -> span; + +template +span(std::array&) -> span; + +template +span(const std::array&) -> span; + +template +span(R&&) -> span()))>>>; + } // namespace fallback_span } // namespace detail diff --git a/folly/container/test/span_test.cpp b/folly/container/test/span_test.cpp index 226c7d05cee..5d09f165391 100644 --- a/folly/container/test/span_test.cpp +++ b/folly/container/test/span_test.cpp @@ -324,6 +324,46 @@ TYPED_TEST_P(SpanTest, fix_as_bytes) { EXPECT_EQ(20, wbytes.size()); } +namespace fallback_span_ctad { + +namespace fallback = folly::detail::fallback_span; + +template +using deduced_for = decltype(fallback::span(std::declval()...)); + +static_assert( // + std::is_same_v< + fallback::span, + deduced_for>); + +static_assert( // + std::is_same_v< + fallback::span, + deduced_for&>>); + +static_assert( // + std::is_same_v, deduced_for&>>); + +static_assert( + std::is_same_v, deduced_for&>>); + +static_assert( // + std::is_same_v< + fallback::span, + deduced_for&>>); + +int arr1[3]; +static_assert( // + std::is_same_v, decltype(fallback::span(arr1))>); + +constexpr int arr2[3]{0, 1, 2}; +static_assert( // + std::is_same_v< + fallback::span, + decltype(fallback::span(arr2))>); + +} // namespace fallback_span_ctad + // clang-format off REGISTER_TYPED_TEST_SUITE_P( SpanTest From 6c482617bb92e427f8755aa45700ce0d4380c3ca Mon Sep 17 00:00:00 2001 From: Denis Yaroshevskiy Date: Tue, 1 Oct 2024 02:09:10 -0700 Subject: [PATCH 2/2] clearing bit utils (#2301) Summary: Pull Request resolved: https://github.com/facebook/folly/pull/2301 n_least_significant_bits n_most_significant_bits clear_n_least_significant_bits set_n_least_significant_bits clear_n_most_significant_bits set_n_most_significant_bits Simple utils that correctly handle corner cases, such as shift == 64. I looked at the assembly a bit, probably that's ok. For x86 I used bmi2 where was appropriate. Reviewed By: Gownta Differential Revision: D63329499 --- folly/lang/Bits.h | 151 +++++++++++++++++++++++ folly/lang/test/BitsTest.cpp | 224 +++++++++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+) diff --git a/folly/lang/Bits.h b/folly/lang/Bits.h index 981feab9be7..2380d516dd3 100644 --- a/folly/lang/Bits.h +++ b/folly/lang/Bits.h @@ -67,6 +67,10 @@ #include #include +#ifdef __BMI2__ +#include +#endif + #if __has_include() && (__cplusplus >= 202002L || (defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L)) #include #endif @@ -106,6 +110,11 @@ constexpr std::make_unsigned_t bits_to_unsigned(Src const s) { static_assert(std::is_unsigned::value, "signed type"); return static_cast(to_unsigned(s)); } + +template +inline constexpr bool supported_in_bits_operations_v = + std::is_unsigned_v && sizeof(T) <= 8; + } // namespace detail /// findFirstSet @@ -223,6 +232,148 @@ inline constexpr T strictPrevPowTwo(T const v) { return v > 1 ? prevPowTwo(T(v - 1)) : T(0); } +/// n_least_significant_bits +/// n_least_significant_bits_fn +/// +/// Returns an unsigned integer of type T, where n +/// least significant (right) bits are set and others are not. +template +struct n_least_significant_bits_fn { + static_assert(detail::supported_in_bits_operations_v, ""); + + FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const { + if (!folly::is_constant_evaluated_or(true)) { + compiler_may_unsafely_assume(n <= sizeof(T) * 8); + +#ifdef __BMI2__ + if constexpr (sizeof(T) <= 4) { + return static_cast(_bzhi_u32(static_cast(-1), n)); + } + return static_cast(_bzhi_u64(static_cast(-1), n)); +#endif + } + + if (sizeof(T) == 8 && n == 64) { + return static_cast(-1); + } + return static_cast((std::uint64_t{1} << n) - 1); + } +}; + +template +inline constexpr n_least_significant_bits_fn n_least_significant_bits; + +/// n_most_significant_bits +/// n_most_significant_bits_fn +/// +/// Returns an unsigned integer of type T, where n +/// most significant bits (left) are set and others are not. +template +struct n_most_significant_bits_fn { + static_assert(detail::supported_in_bits_operations_v, ""); + + FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const { + if (!folly::is_constant_evaluated_or(true)) { + compiler_may_unsafely_assume(n <= sizeof(T) * 8); + +#ifdef __BMI2__ + // assembler looks smaller here, if we use bzhi from `set_lowest_n_bits` + if constexpr (sizeof(T) == 8) { + return static_cast(~n_least_significant_bits(64 - n)); + } +#endif + } + + if (sizeof(T) == 8 && n == 0) { + return 0; + } + n = sizeof(T) * 8 - n; + + std::uint64_t ones = static_cast(~0); + return static_cast(ones << n); + } +}; + +template +inline constexpr n_most_significant_bits_fn n_most_significant_bits; + +/// clear_n_least_significant_bits +/// clear_n_least_significant_bits_fn +/// +/// Clears n least significant (right) bits. Other bits stay the same. +struct clear_n_least_significant_bits_fn { + template + FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const { + static_assert(detail::supported_in_bits_operations_v, ""); + + // alternative is to do two shifts but that has + // a dependency between them, so is likely worse + return x & n_most_significant_bits(sizeof(T) * 8 - n); + } +}; + +inline constexpr clear_n_least_significant_bits_fn + clear_n_least_significant_bits; + +/// set_n_least_significant_bits +/// set_n_least_significant_bits_fn +/// +/// Sets n least significant (right) bits. Other bits stay the same. +struct set_n_least_significant_bits_fn { + template + FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const { + static_assert(detail::supported_in_bits_operations_v, ""); + + // alternative is to do two shifts but that has + // a dependency between them, so is likely worse + return x | n_least_significant_bits(n); + } +}; + +inline constexpr set_n_least_significant_bits_fn set_n_least_significant_bits; + +/// clear_n_most_significant_bits +/// clear_n_most_significant_bits_fn +/// +/// Clears n most significant (left) bits. Other bits stay the same. +struct clear_n_most_significant_bits_fn { + template + FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const { + static_assert(detail::supported_in_bits_operations_v, ""); + + if (!folly::is_constant_evaluated_or(true)) { + compiler_may_unsafely_assume(n <= sizeof(T) * 8); + +#ifdef __BMI2__ + if constexpr (sizeof(T) <= 4) { + return static_cast(_bzhi_u32(x, sizeof(T) * 8 - n)); + } + return static_cast(_bzhi_u64(x, sizeof(T) * 8 - n)); +#endif + } + + // alternative is to do two shifts but that has + // a dependency between them, so is likely worse + return x & n_least_significant_bits(sizeof(T) * 8 - n); + } +}; + +inline constexpr clear_n_most_significant_bits_fn clear_n_most_significant_bits; + +/// set_n_most_significant_bits +/// set_n_most_significant_bits_fn +/// +/// Sets n most significant (left) bits. Other bits stay the same. +struct set_n_most_significant_bits_fn { + template + FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const { + static_assert(detail::supported_in_bits_operations_v, ""); + return x | n_most_significant_bits(n); + } +}; + +inline constexpr set_n_most_significant_bits_fn set_n_most_significant_bits; + /** * Endianness detection and manipulation primitives. */ diff --git a/folly/lang/test/BitsTest.cpp b/folly/lang/test/BitsTest.cpp index 5888c748872..b27fee8bde5 100644 --- a/folly/lang/test/BitsTest.cpp +++ b/folly/lang/test/BitsTest.cpp @@ -74,6 +74,14 @@ void testEFS() { } } +template +struct BitsAllUintsTest : ::testing::Test {}; + +using UintsToTest = + ::testing::Types; + +TYPED_TEST_SUITE(BitsAllUintsTest, UintsToTest); + } // namespace TEST(Bits, FindFirstSet) { @@ -350,4 +358,220 @@ TEST(Bits, LoadUnalignedUB) { EXPECT_EQ(0, x); } +TYPED_TEST(BitsAllUintsTest, NLeastSignificantBits) { + using T = TypeParam; + + static_assert(n_least_significant_bits(0) == 0b0, ""); + static_assert(n_least_significant_bits(1) == 0b1, ""); + static_assert(n_least_significant_bits(2) == 0b11, ""); + static_assert(n_least_significant_bits(3) == 0b111, ""); + static_assert(n_least_significant_bits(4) == 0b1111, ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= std::min(sizeof(T) * 8, 63UL); ++i) { + std::uint64_t expected = (std::uint64_t{1} << i) - 1; + T actual = n_least_significant_bits(i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countr_one(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countr_one(expected)) << i; + return false; + } +#endif + } + + if (sizeof(T) == 8) { + std::uint64_t expected = std::numeric_limits::max(); + T actual = n_least_significant_bits(64); + if (expected != actual) { + EXPECT_EQ(expected, actual) << 64; + return false; + } + } + + return true; + }; + + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + +TYPED_TEST(BitsAllUintsTest, NMostSignificantBits) { + using T = TypeParam; + + constexpr std::size_t kBitSize = sizeof(T) * 8; + + static_assert( + n_most_significant_bits(kBitSize) == static_cast(~0b0), ""); + static_assert( + n_most_significant_bits(kBitSize - 1) == static_cast(~0b1), ""); + static_assert( + n_most_significant_bits(kBitSize - 2) == static_cast(~0b11), ""); + static_assert( + n_most_significant_bits(kBitSize - 3) == static_cast(~0b111), ""); + static_assert( + n_most_significant_bits(kBitSize - 4) == static_cast(~0b1111), ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= kBitSize; ++i) { + T expected = ~n_least_significant_bits(kBitSize - i); + T actual = n_most_significant_bits(i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countl_one(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countl_one(expected)) << i; + return false; + } +#endif + } + return true; + }; + + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + +TYPED_TEST(BitsAllUintsTest, ClearNLeastSignificantBits) { + using T = TypeParam; + + constexpr std::size_t kBitSize = sizeof(T) * 8; + + static_assert(clear_n_least_significant_bits(T{0b11U}, 1U) == 0b10U, ""); + static_assert(clear_n_least_significant_bits(T{0b101U}, 1U) == 0b100U, ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= kBitSize; ++i) { + T expected = n_most_significant_bits(kBitSize - i); + T actual = clear_n_least_significant_bits(static_cast(-1), i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countr_zero(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countr_zero(expected)) << i; + return false; + } +#endif + } + return true; + }; + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + +TYPED_TEST(BitsAllUintsTest, SetNLeastSignificantBits) { + using T = TypeParam; + + constexpr std::size_t kBitSize = sizeof(T) * 8; + + static_assert(set_n_least_significant_bits(T{0b10U}, 1U) == 0b11U, ""); + static_assert(set_n_least_significant_bits(T{0b100U}, 1U) == 0b101U, ""); + static_assert(set_n_least_significant_bits(T{0b100U}, 2U) == 0b111U, ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= kBitSize; ++i) { + T expected = n_least_significant_bits(i); + T actual = set_n_least_significant_bits(T{}, i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countr_one(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countr_one(expected)) << i; + return false; + } +#endif + } + return true; + }; + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + +TYPED_TEST(BitsAllUintsTest, ClearNMostSignificantBits) { + using T = TypeParam; + + constexpr std::size_t kBitSize = sizeof(T) * 8; + + static_assert( + clear_n_most_significant_bits(T{0b101U}, kBitSize - 1) == 0b1U, ""); + static_assert( + clear_n_most_significant_bits(T{0b1100U}, kBitSize - 3) == 0b100U, ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= kBitSize; ++i) { + T expected = n_least_significant_bits(kBitSize - i); + T actual = clear_n_most_significant_bits(static_cast(-1), i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countl_zero(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countl_zero(expected)) << i; + return false; + } +#endif + } + return true; + }; + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + +TYPED_TEST(BitsAllUintsTest, SetNMostSignificantBits) { + using T = TypeParam; + + constexpr std::size_t kBitSize = sizeof(T) * 8; + + static_assert( + set_n_most_significant_bits(T{0b1}, kBitSize - 2) == + static_cast(~0b10), + ""); + static_assert( + set_n_most_significant_bits(T{0b1100U}, kBitSize - 3) == + static_cast(~0b11), + ""); + + auto test = [] { + for (std::uint32_t i = 0; i <= kBitSize; ++i) { + T expected = n_most_significant_bits(i); + T actual = set_n_most_significant_bits(static_cast(0), i); + if (expected != actual) { + EXPECT_EQ(expected, actual) << i; + return false; + } +#ifdef __cpp_lib_bitops + if (std::countl_one(expected) != static_cast(i)) { + EXPECT_EQ(i, std::countl_one(expected)) << i; + return false; + } +#endif + } + return true; + }; + static_assert(test(), ""); + + // runtime can use a different implementation + EXPECT_TRUE(test()); +} + } // namespace folly