Core: Count bits with the CUDA intrinsics in device code libeigen/eigen!3339 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com>
diff --git a/Eigen/src/Core/MathFunctions.h b/Eigen/src/Core/MathFunctions.h index 7ede5d8..ad3118d 100644 --- a/Eigen/src/Core/MathFunctions.h +++ b/Eigen/src/Core/MathFunctions.h
@@ -666,7 +666,49 @@ return count_bits_impl<BitsType>::popcount(bits); } -#if EIGEN_COMP_GNUC || EIGEN_COMP_CLANG +#if defined(EIGEN_CUDA_ARCH) + +// nvcc's device code has only __builtin_clz, __builtin_clzll, __builtin_popcount and __builtin_popcountll. The other +// builtins are host functions there, and with warning 20014 silenced, a call from here compiles and returns garbage: +// ctz at every width, and clz and popcount where long has 64 bits. __clz and __clzll return the full width for zero. +// From CUDA 13.2, their parameter and return types are unsigned on aarch64 with GCC 11.4 or later as the host compiler +// and signed elsewhere and in earlier toolkits; the conversions below keep the bits under either declaration. +template <typename BitsType> +struct count_bits_impl< + BitsType, std::enable_if_t<std::is_integral<BitsType>::value && sizeof(BitsType) <= sizeof(unsigned int)>> { + static constexpr int kNumBits = static_cast<int>(sizeof(BitsType) * CHAR_BIT); + static EIGEN_DEVICE_FUNC inline int clz(BitsType bits) { + static constexpr int kLeadingBitsOffset = (sizeof(unsigned int) - sizeof(BitsType)) * CHAR_BIT; + return static_cast<int>(__clz(static_cast<unsigned int>(bits))) - kLeadingBitsOffset; + } + + static EIGEN_DEVICE_FUNC inline int ctz(BitsType bits) { + return bits == 0 ? kNumBits : __ffs(static_cast<int>(static_cast<unsigned int>(bits))) - 1; + } + + static EIGEN_DEVICE_FUNC inline int popcount(BitsType bits) { return __popc(static_cast<unsigned int>(bits)); } +}; + +template <typename BitsType> +struct count_bits_impl<BitsType, + std::enable_if_t<std::is_integral<BitsType>::value && sizeof(unsigned int) < sizeof(BitsType) && + sizeof(BitsType) <= sizeof(unsigned long long)>> { + static constexpr int kNumBits = static_cast<int>(sizeof(BitsType) * CHAR_BIT); + static EIGEN_DEVICE_FUNC inline int clz(BitsType bits) { + static constexpr int kLeadingBitsOffset = (sizeof(unsigned long long) - sizeof(BitsType)) * CHAR_BIT; + return static_cast<int>(__clzll(static_cast<unsigned long long>(bits))) - kLeadingBitsOffset; + } + + static EIGEN_DEVICE_FUNC inline int ctz(BitsType bits) { + return bits == 0 ? kNumBits : __ffsll(static_cast<long long>(static_cast<unsigned long long>(bits))) - 1; + } + + static EIGEN_DEVICE_FUNC inline int popcount(BitsType bits) { + return __popcll(static_cast<unsigned long long>(bits)); + } +}; + +#elif EIGEN_COMP_GNUC || EIGEN_COMP_CLANG template <typename BitsType> struct count_bits_impl< @@ -795,7 +837,7 @@ #endif // _WIN64 -#endif // EIGEN_COMP_GNUC || EIGEN_COMP_CLANG +#endif // EIGEN_CUDA_ARCH template <typename BitsType> struct log_2_impl {
diff --git a/test/gpu_basic.cu b/test/gpu_basic.cu index 319d45d..83a1694 100644 --- a/test/gpu_basic.cu +++ b/test/gpu_basic.cu
@@ -685,6 +685,46 @@ #endif } +// clz, ctz and popcount of the low 8, 16, 32 and 64 bits of each input. The clz test checks the host counts. +struct bit_count_test { + template <typename T> + EIGEN_DEVICE_FUNC static void count(T bits, int* out) { + out[0] = internal::clz(bits); + out[1] = internal::ctz(bits); + out[2] = internal::popcount(bits); + } + + EIGEN_DEVICE_FUNC void operator()(int i, const uint64_t* in, int* out) const { + const uint64_t bits = in[i]; + count(static_cast<uint8_t>(bits), out + 12 * i); + count(static_cast<uint16_t>(bits), out + 12 * i + 3); + count(static_cast<uint32_t>(bits), out + 12 * i + 6); + count(bits, out + 12 * i + 9); + } +}; + +void test_bit_count() { + const int n = 3 * 64 + 2; + Eigen::Array<uint64_t, Eigen::Dynamic, 1> in(n); + Eigen::ArrayXi out_ref(12 * n), out_gpu(12 * n); + for (int k = 0; k < 64; ++k) { + in(3 * k) = uint64_t(1) << k; + in(3 * k + 1) = ~(uint64_t(1) << k); + in(3 * k + 2) = internal::random<uint64_t>(); + } + in(n - 2) = 0; + in(n - 1) = ~uint64_t(0); + out_ref.setConstant(-1); + out_gpu.setConstant(-1); + + run_on_cpu(bit_count_test(), n, in, out_ref); + run_on_gpu(bit_count_test(), n, in, out_gpu); + +#if !defined(EIGEN_GPU_COMPILE_PHASE) + VERIFY_IS_CWISE_EQUAL(out_ref, out_gpu); +#endif +} + template <typename Type1, typename Type2> bool verifyIsApproxWithInfsNans(const Type1& a, const Type2& b, typename Type1::Scalar* = 0) // Enabled for Eigen's type only @@ -892,6 +932,7 @@ CALL_SUBTEST(test_custom_less_scalar_minmax()); CALL_SUBTEST(test_float_nan_minmax()); + CALL_SUBTEST(test_bit_count()); // `preverse` on every GPU packet type. 32 elements cover several packets of // each, including `Packet4h2`, the widest at 8.