[v3,3/3] libstdc++: Fix [simd.bit] constraints and implementation

Message ID bmm.hj3j4tfj9i.gcc.gcc-TEST.mkretz.145.3.3@forge-stage.sourceware.org
State New
Headers
Series std-simd-complex |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed

Commit Message

Matthias Kretz via Sourceware Forge June 2, 2026, 10:44 a.m. UTC
  From: Matthias Kretz <m.kretz@gsi.de>

The previous commit on [simd.bit] was incomplete and not fully tested.
Now, the functions actually implement the constraints as given in the
standard.
The functions returning signed integer types, require an explicit type
cast, since the generator constructor only accepts value-preserving
conversions.

libstdc++-v3/ChangeLog:

	* include/bits/simd_bit.h (byteswap): Fix constraint. Use
	trivial implementation for 1 byte types.
	(bit_ceil, bit_floor, has_single_bit, rotl, rotr, bit_width)
	(countl_zero, countl_one, countr_zero, countr_one, popcount):
	Require unsigned integer types.
	(rotl, rotr): Require integral matching types for the second
	argument.
	(bit_width, countl_zero, countl_one, countr_zero, countr_one)
	(popcount): Fix generator return type to use a signed integer
	type.
	* testsuite/std/simd/simd_bit.cc: Test constraints. Simplify
	construction of expected values.
	Add tests for bit_width, countl_zero, countl_one, countr_zero,
	countr_one, and popcount.
	* testsuite/std/simd/test_setup.h (any_type_of): New.

Signed-off-by: Matthias Kretz <m.kretz@gsi.de>
---
 libstdc++-v3/include/bits/simd_bit.h         |  57 +++++++--
 libstdc++-v3/testsuite/std/simd/simd_bit.cc  | 126 +++++++++++++++----
 libstdc++-v3/testsuite/std/simd/test_setup.h |   3 +
 3 files changed, 150 insertions(+), 36 deletions(-)
  

Patch

diff --git a/libstdc++-v3/include/bits/simd_bit.h b/libstdc++-v3/include/bits/simd_bit.h
index 543f0c65c954..dd81c5d6c586 100644
--- a/libstdc++-v3/include/bits/simd_bit.h
+++ b/libstdc++-v3/include/bits/simd_bit.h
@@ -44,12 +44,19 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 namespace simd
 {
   template<__simd_vec_type _Vp>
+    requires integral<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr _Vp
     byteswap(const _Vp& __v) noexcept
-    { return _Vp([&](int __i) { return std::byteswap(__v[__i]); }); }
+    {
+      if constexpr (sizeof(typename _Vp::value_type) == 1)
+	return __v;
+      else
+	return _Vp([&](int __i) { return std::byteswap(__v[__i]); });
+    }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr _Vp
     bit_ceil(const _Vp& __v)
@@ -61,98 +68,122 @@  namespace simd
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr _Vp
     bit_floor(const _Vp& __v) noexcept
     { return _Vp([&](int __i) { return std::bit_floor(__v[__i]); }); }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr typename _Vp::mask_type
     has_single_bit(const _Vp& __v) noexcept
     { return typename _Vp::mask_type([&](int __i) { return std::has_single_bit(__v[__i]); }); }
 
   template<__simd_vec_type _V0, __simd_vec_type _V1>
+    requires __unsigned_integer<typename _V0::value_type>
+      && integral<typename _V1::value_type>
+      && (_V0::size() == _V1::size())
+      && (sizeof(typename _V0::value_type) == sizeof(typename _V1::value_type))
     [[__gnu__::__always_inline__]]
     constexpr _V0
     rotl(const _V0& __v, const _V1& __s) noexcept
     { return _V0([&](int __i) { return std::rotl(__v[__i], __s[__i]); }); }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr _Vp
     rotl(const _Vp& __v, int __s) noexcept
     { return _Vp([&](int __i) { return std::rotl(__v[__i], __s); }); }
 
   template<__simd_vec_type _V0, __simd_vec_type _V1>
+    requires __unsigned_integer<typename _V0::value_type>
+      && integral<typename _V1::value_type>
+      && (_V0::size() == _V1::size())
+      && (sizeof(typename _V0::value_type) == sizeof(typename _V1::value_type))
     [[__gnu__::__always_inline__]]
     constexpr _V0
     rotr(const _V0& __v, const _V1& __s) noexcept
     { return _V0([&](int __i) { return std::rotr(__v[__i], __s[__i]); }); }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr _Vp
     rotr(const _Vp& __v, int __s) noexcept
     { return _Vp([&](int __i) { return std::rotr(__v[__i], __s); }); }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     bit_width(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::bit_width(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::bit_width(__v[__i]));
 	     });
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     countl_zero(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::countl_zero(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::countl_zero(__v[__i]));
 	     });
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     countl_one(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::countl_one(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::countl_one(__v[__i]));
 	     });
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     countr_zero(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::countr_zero(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::countr_zero(__v[__i]));
 	     });
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     countr_one(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::countr_one(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::countr_one(__v[__i]));
 	     });
     }
 
   template<__simd_vec_type _Vp>
+    requires __unsigned_integer<typename _Vp::value_type>
     [[__gnu__::__always_inline__]]
     constexpr rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>
     popcount(const _Vp& __v) noexcept
     {
-      return rebind_t<make_signed_t<typename _Vp::value_type>, _Vp>([&](int __i) {
-	       return std::popcount(__v[__i]);
+      using _Ip = make_signed_t<typename _Vp::value_type>;
+      return rebind_t<_Ip, _Vp>([&](int __i) {
+	       return static_cast<_Ip>(std::popcount(__v[__i]));
 	     });
     }
 } // namespace simd
diff --git a/libstdc++-v3/testsuite/std/simd/simd_bit.cc b/libstdc++-v3/testsuite/std/simd/simd_bit.cc
index 5d2b77c30865..21c78ae9333f 100644
--- a/libstdc++-v3/testsuite/std/simd/simd_bit.cc
+++ b/libstdc++-v3/testsuite/std/simd/simd_bit.cc
@@ -5,9 +5,32 @@ 
 #include "test_setup.h"
 #include <climits>
 
+template <typename V>
+  struct CheckInvocable
+  {
+    using T = typename V::value_type;
+    static constexpr bool unsigned_integer
+      = any_type_of<T, unsigned char, unsigned short, unsigned int, unsigned long,
+		    unsigned long long>;
+    static_assert(std::integral<T> == requires(V x) { std::byteswap(x); });
+    static_assert(unsigned_integer == requires(V x) { std::bit_ceil(x); });
+    static_assert(unsigned_integer == requires(V x) { std::bit_floor(x); });
+    static_assert(unsigned_integer == requires(V x) { std::has_single_bit(x); });
+    static_assert(unsigned_integer == requires(V x, V y) { std::rotl(x, y); });
+    static_assert(unsigned_integer == requires(V x, int y) { std::rotl(x, y); });
+    static_assert(unsigned_integer == requires(V x, V y) { std::rotr(x, y); });
+    static_assert(unsigned_integer == requires(V x, int y) { std::rotr(x, y); });
+    static_assert(unsigned_integer == requires(V x) { std::bit_width(x); });
+    static_assert(unsigned_integer == requires(V x) { std::countl_zero(x); });
+    static_assert(unsigned_integer == requires(V x) { std::countl_one(x); });
+    static_assert(unsigned_integer == requires(V x) { std::countr_zero(x); });
+    static_assert(unsigned_integer == requires(V x) { std::countr_one(x); });
+    static_assert(unsigned_integer == requires(V x) { std::popcount(x); });
+  };
+
 template <typename V>
   requires std::integral<typename V::value_type>
-  struct Tests<V>
+  struct Tests<V> : CheckInvocable<V>
   {
     using T = typename V::value_type;
     using M = typename V::mask_type;
@@ -41,14 +64,7 @@  template <typename V>
 	t.verify_equal(bit_ceil(b), select(b == T(), T(1), b));
 	t.verify_equal(std::bit_ceil(a), bit_ceil(a));
 	t.verify_equal(std::simd::bit_ceil(a), bit_ceil(a));
-	t.verify_equal(bit_ceil(a),
-		       V([](T i) {
-			 if (i > msb)
-			   i -= msb + 1;
-			 while (!std::has_single_bit(i))
-			   i = (i | (i >> 1)) + 1;
-			 return T(i);
-		       }));
+	t.verify_equal(bit_ceil(a), V([&](int i) { return std::bit_ceil(a[i]); }));
       }
     };
 
@@ -59,15 +75,7 @@  template <typename V>
 	t.verify_equal(bit_floor(b), b);
 	t.verify_equal(std::bit_floor(a), bit_floor(a));
 	t.verify_equal(std::simd::bit_floor(a), bit_floor(a));
-	t.verify_equal(bit_floor(a),
-		       V([](T i) -> T {
-			 if (i == 0)
-			   return 0;
-			 int shift = 0;
-			 while ((i >> shift) > 1)
-			   ++shift;
-			 return T(1) << shift;
-		       }));
+	t.verify_equal(bit_floor(a), V([&](int i) { return std::bit_floor(a[i]); }));
       }
     };
 
@@ -82,12 +90,19 @@  template <typename V>
       }
     };
 
-    ADD_TEST(RotateLeft, std::__unsigned_integer<T>) {
+    ADD_TEST(FullRotate, std::__unsigned_integer<T>) {
       std::tuple {test_iota<V, 0, 0>},
       [](auto& t, const V a) {
-	t.verify_equal(rotl(a, sizeof(T) * CHAR_BIT), a);
-	t.verify_equal(std::rotl(a, sizeof(T) * CHAR_BIT), a);
-	t.verify_equal(std::simd::rotl(a, sizeof(T) * CHAR_BIT), a);
+	constexpr int digits = std::numeric_limits<T>::digits;
+	template for (int n : {0, digits, 5 * digits})
+	  {
+	    t.verify_equal(rotl(a, n), a);
+	    t.verify_equal(std::rotl(a, n), a);
+	    t.verify_equal(std::simd::rotl(a, n), a);
+	    t.verify_equal(rotr(a, n), a);
+	    t.verify_equal(std::rotr(a, n), a);
+	    t.verify_equal(std::simd::rotr(a, n), a);
+	  }
       }
     };
 
@@ -114,10 +129,75 @@  template <typename V>
 	t.verify_equal(rotr(x, I(sizeof(T) * CHAR_BIT) - vshiftx), refx);
       }
     };
+
+    // The value-type of reference is always going to be 'int', forcing a conversion in verify_equal
+    // (unless V::value_type is 'unsigned int'). That's intentional, since we thus can find
+    // (hypothetical) cases of value-changing conversions in the implementation.
+#define REFERENCE(x, fun) simd::rebind_t<decltype(fun(x[0])), V>([&](int i) { return fun(x[i]); })
+
+    ADD_TEST(BitWidth, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::bit_width(x), REFERENCE(x, std::bit_width));
+	t.verify_equal(simd::bit_width(x), REFERENCE(x, std::bit_width));
+	t.verify_equal(std::bit_width(y), REFERENCE(y, std::bit_width));
+	t.verify_equal(simd::bit_width(y), REFERENCE(y, std::bit_width));
+      }
+    };
+
+    ADD_TEST(CountLZero, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::countl_zero(x), REFERENCE(x, std::countl_zero));
+	t.verify_equal(simd::countl_zero(x), REFERENCE(x, std::countl_zero));
+	t.verify_equal(std::countl_zero(y), REFERENCE(y, std::countl_zero));
+	t.verify_equal(simd::countl_zero(y), REFERENCE(y, std::countl_zero));
+      }
+    };
+
+    ADD_TEST(CountLOne, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::countl_one(x), REFERENCE(x, std::countl_one));
+	t.verify_equal(simd::countl_one(x), REFERENCE(x, std::countl_one));
+	t.verify_equal(std::countl_one(y), REFERENCE(y, std::countl_one));
+	t.verify_equal(simd::countl_one(y), REFERENCE(y, std::countl_one));
+      }
+    };
+
+    ADD_TEST(CountRZero, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::countr_zero(x), REFERENCE(x, std::countr_zero));
+	t.verify_equal(simd::countr_zero(x), REFERENCE(x, std::countr_zero));
+	t.verify_equal(std::countr_zero(y), REFERENCE(y, std::countr_zero));
+	t.verify_equal(simd::countr_zero(y), REFERENCE(y, std::countr_zero));
+      }
+    };
+
+    ADD_TEST(CountROne, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::countr_one(x), REFERENCE(x, std::countr_one));
+	t.verify_equal(simd::countr_one(x), REFERENCE(x, std::countr_one));
+	t.verify_equal(std::countr_one(y), REFERENCE(y, std::countr_one));
+	t.verify_equal(simd::countr_one(y), REFERENCE(y, std::countr_one));
+      }
+    };
+
+    ADD_TEST(PopCount, std::__unsigned_integer<T>) {
+      std::tuple {test_iota<V>, msb - test_iota<V>},
+      [](auto& t, const V x, const V y) {
+	t.verify_equal(std::popcount(x), REFERENCE(x, std::popcount));
+	t.verify_equal(simd::popcount(x), REFERENCE(x, std::popcount));
+	t.verify_equal(std::popcount(y), REFERENCE(y, std::popcount));
+	t.verify_equal(simd::popcount(y), REFERENCE(y, std::popcount));
+      }
+    };
   };
 
 template <typename V>
-  struct Tests
+  struct Tests : CheckInvocable<V>
   {};
 
 #include "create_tests.h"
diff --git a/libstdc++-v3/testsuite/std/simd/test_setup.h b/libstdc++-v3/testsuite/std/simd/test_setup.h
index d562a845a5af..9c4fc6025a30 100644
--- a/libstdc++-v3/testsuite/std/simd/test_setup.h
+++ b/libstdc++-v3/testsuite/std/simd/test_setup.h
@@ -73,6 +73,9 @@  static std::string_view test_name = "unknown";
 
 namespace simd = std::simd;
 
+template <typename T, typename... Us>
+  concept any_type_of = (std::same_as<T, Us> || ...);
+
 template <typename T>
   concept complex_like = std::simd::__complex_like<T>;