[PATCH v4 1/2] libstdc++: Refactor _ScalarAbi<N> into _Abi<N, N>
Jonathan Wakely
jwakely@redhat.com
Wed Jul 1 10:42:41 GMT 2026
On Wed, 03 Jun 2026 at 05:42 +0000, Matthias Kretz via Sourceware Forge wrote:
>From: Matthias Kretz <m.kretz@gsi.de>
>
>Before this change _Ap::_S_is_bitmask would pick up false from
>_ScalarAbi<N>. After __scalar_abi_tag now identifies any _Abi<N, N, V>,
>where V can also identify bit-masks, the short-cut of setting
>_S_use_bitmask to _Ap::_S_is_bitmask is wrong. It would be correct to
>have it say _Ap::_S_is_bitmask && !__scalar_abi_tag<_Ap>. I decided to
>implement the latter only in the _S_nreg == 1 specialization and have
>the higher ups inherit the value from their vec/mask member. The
>_S_is_bitmask bit is not erased for __scalar_abi_tag since it makes a
>difference for __abi_rebind.
>
>Signed-off-by: Matthias Kretz <m.kretz@gsi.de>
>
>libstdc++-v3/ChangeLog:
>
> * include/bits/simd_details.h (_ScalarAbi): Remove.
> (__scalar_abi_tag): Identify _Abi<N, N> as scalar now.
> (__native_abi): Replace _ScalarAbi<1> with _Abi_t<1, 1, ...>.
> (__abi_rebind): Refactor rebinding from/to __scalar_abi_tag.
> * include/bits/simd_mask.h (_S_use_bitmask): Only true if
> !_S_is_scalar.
> (_M_and_neighbors, _M_or_neighbors): Add case for _S_is_scalar
> where the and/or must be executed one step earlier.
> (_M_reduce_min_index, _M_reduce_max_index): Delete dead code.
> * include/bits/simd_vec.h (_S_use_bitmask): Inherit the value
> from the first data member.
> * testsuite/std/simd/traits_impl.cc: Adjust for the removal of
> _ScalarAbi.
>---
> libstdc++-v3/include/bits/simd_details.h | 86 +++++++------------
> libstdc++-v3/include/bits/simd_mask.h | 38 +++++---
> libstdc++-v3/include/bits/simd_vec.h | 2 +-
> .../testsuite/std/simd/traits_impl.cc | 11 +--
> 4 files changed, 58 insertions(+), 79 deletions(-)
>
>diff --git a/libstdc++-v3/include/bits/simd_details.h b/libstdc++-v3/include/bits/simd_details.h
>index a1acc5bd9464..e6185fac64a3 100644
>--- a/libstdc++-v3/include/bits/simd_details.h
>+++ b/libstdc++-v3/include/bits/simd_details.h
>@@ -241,46 +241,21 @@ namespace simd
> #endif
>
> /** @internal
>- * This ABI tag describes basic_vec objects that store one element per data member and basic_mask
>- * objects that store one bool data members.
>+ * @brief This ABI tag determines the data member(s) of basic_vec and basic_mask.
> *
>- * @tparam _Np The number of elements, which also matches the number of data members in
>- * basic_vec and basic_mask.
>- */
>- template <int _Np = 1>
>- struct _ScalarAbi
>- {
>- static constexpr int _S_size = _Np;
>-
>- static constexpr int _S_nreg = _Np;
>-
>- static constexpr _AbiVariant _S_variant = {};
>-
>- template <typename _Tp>
>- using _DataType = __canonical_vec_type_t<_Tp>;
>-
>- static constexpr bool _S_is_vecmask = false;
>-
>- // in principle a bool is a 1-bit bitmask, but this is asking for an AVX512 bitmask
>- static constexpr bool _S_is_bitmask = false;
>-
>- template <size_t>
>- using _MaskDataType = bool;
>-
>- template <int _N2, int _Nreg2 = _N2>
>- static consteval _ScalarAbi<_N2>
>- _S_resize()
>- {
>- static_assert(_N2 == _Nreg2);
>- return {};
>- }
>- };
>-
>- /** @internal
>- * This ABI tag describes basic_vec objects that store one or more objects declared with the
>- * [[gnu::vector_size(N)]] attribute.
>- * Applied to basic_mask objects, this ABI tag either describes corresponding vector-mask objects
>- * or bit-mask objects. Which one is used is determined via @p _Var.
>+ * @p _Nreg determines the number of recursive basic_vec/basic_mask data members where @p _Nreg is
>+ * equal to 1. With @p _Nreg equal to 1, the basic_vec/basic_mask holds one vector builtin (@p
>+ * _Np greater than 1) or a scalar (@ _Np equal to 1).
Does this work correctly with Doxygen, having a line break between the
@p and the _Np name? There's certainly a 'p' missing in the last @p
here. You could drop the @p and use `_Np` instead.
The patch is OK for trunk with the missing 'p' fixed, and preferably
just switching to markdown.
>+ * @f$\lceil\frac{\mathtt{Np}}{\mathtt{Nreg}}\rceil@f$ therefore determines the number of elements
>+ * in a register (except for a remainder where it can be smaller). If @p _Np equals @p _Nreg, (the
>+ * aforementioned quotient is 1), then basic_vec (recursively) holds non-vector data members and
>+ * basic_mask holds bools.
>+ *
>+ * The @p _Var parameter determines details about the data member in the one register case. Masks
>+ * can be represented as vector masks (the default comparison result of GNU vector builtins),
>+ * bit-masks as used by AVX-512, bit-masks as used by ARM SVE (not yet implemented), or a single
>+ * bool (for the @p _Np equals 1 case). For basic_mask it determines the actual data layout and
>+ * for basic_mask it determines the result of compares.
> *
> * @tparam _Np The number of elements.
> * @tparam _Nreg The number of registers needed to store @p _Np elements.
>@@ -391,9 +366,13 @@ namespace simd
> { __x.template _S_resize<_Tp::_S_size, _Tp::_S_nreg>() } -> same_as<_Tp>;
> };
>
>+ /** @internal
>+ * Satisfied if @p _Tp is a valid simd ABI tag and one element is stored per register (number of
>+ * registers equals size).
>+ */
> template <typename _Tp>
> concept __scalar_abi_tag
>- = same_as<_Tp, _ScalarAbi<_Tp::_S_size>> && __abi_tag<_Tp>;
>+ = same_as<_Tp, _Abi_t<_Tp::_S_size, _Tp::_S_size, _Tp::_S_variant>> && __abi_tag<_Tp>;
>
> // Determine if math functions must *raise* floating-point exceptions.
> // math_errhandling may expand to an extern symbol, in which case we must assume fp exceptions
>@@ -760,7 +739,7 @@ namespace simd
> else if constexpr (_Traits._M_have_avx512f())
> return _Abi_t<64 / __adj_sizeof, 1, _AbiVariant::_BitMask>();
> else if constexpr (is_same_v<_Tp, _Float16> && !_Traits._M_have_f16c())
>- return _ScalarAbi<1>();
>+ return _Abi_t<1, 1>();
> else if constexpr (_Traits._M_have_avx2())
> return _Abi_t<32 / __adj_sizeof, 1>();
> else if constexpr (_Traits._M_have_avx() && is_floating_point_v<_Tp>)
>@@ -772,7 +751,7 @@ namespace simd
> return _Abi_t<16 / __adj_sizeof, 1>();
> // no MMX: we can't emit EMMS where it would be necessary
> else
>- return _ScalarAbi<1>();
>+ return _Abi_t<1, 1>();
> }
>
> #else
>@@ -794,7 +773,7 @@ namespace simd
> if constexpr (!__vectorizable<_Tp>)
> return _InvalidAbi();
> else
>- return _ScalarAbi<1>();
>+ return _Abi_t<1, 1>();
> }
>
> #endif
>@@ -850,17 +829,19 @@ namespace simd
> if constexpr (_Np <= 0 || !__vectorizable<_Tp>)
> return _InvalidAbi();
>
>- else if constexpr (__scalar_abi_tag<_A0>)
>- return _A0::template _S_resize<_Np>();
>-
> else
> {
> using _Native = remove_const_t<decltype(std::simd::__native_abi<_Tp>())>;
> static_assert(0 != _Native::_S_size);
> constexpr int __nreg = __div_ceil(_Np, _Native::_S_size);
>
>- if constexpr (__scalar_abi_tag<_Native>)
>- return _Native::template _S_resize<_Np>();
>+ // __scalar_abi_tag is sticky (unless we reach size 1, where we can't know whether it was
>+ // an explicit __scalar_abi_tag before some resize_t)
>+ if constexpr (__scalar_abi_tag<_Native> || (__scalar_abi_tag<_A0> && _A0::_S_size >= 2))
>+ {
>+ return _A0::template _S_resize<_Np, _Np>();
>+ }
>+
> else
> return _Abi_t<_Native::_S_size, 1, __filter_abi_variant(_A0::_S_variant,
> _AbiVariant::_MaskVariants)
>@@ -885,9 +866,6 @@ namespace simd
> if constexpr (_Bytes == 0 || _Np <= 0)
> return _InvalidAbi();
>
>- else if constexpr (__scalar_abi_tag<_A0>)
>- return _A0::template _S_resize<_Np>();
>-
> #if _GLIBCXX_X86
> // AVX w/o AVX2:
> // e.g. resize_t<8, mask<float, Whatever>> needs to be _Abi<8, 1> not _Abi<8, 2>
>@@ -939,12 +917,6 @@ namespace simd
> if (__b0 != __b1)
> return true;
>
>- // everything is better than _ScalarAbi, except when converting to a single bool
>- if constexpr (__scalar_abi_tag<_To>)
>- return __n > 1;
>- else if constexpr (__scalar_abi_tag<_From>)
>- return true;
>-
> // converting to a bit-mask is better
> else if constexpr (_To::_S_is_vecmask != _From::_S_is_vecmask)
> return _To::_S_is_vecmask; // to vector-mask is explicit
>diff --git a/libstdc++-v3/include/bits/simd_mask.h b/libstdc++-v3/include/bits/simd_mask.h
>index 0a7cfa03cedd..81a0825ec6ce 100644
>--- a/libstdc++-v3/include/bits/simd_mask.h
>+++ b/libstdc++-v3/include/bits/simd_mask.h
>@@ -543,7 +543,7 @@ namespace simd
>
> static constexpr bool _S_is_scalar = _S_has_bool_member;
>
>- static constexpr bool _S_use_bitmask = _Ap::_S_is_bitmask;
>+ static constexpr bool _S_use_bitmask = _Ap::_S_is_bitmask && !_S_is_scalar;
>
> static constexpr int _S_full_size = [] {
> if constexpr (_S_is_scalar)
>@@ -1519,8 +1519,16 @@ namespace simd
> constexpr basic_mask&
> _M_and_neighbors()
> {
>- _M_data0._M_and_neighbors();
>- _M_data1._M_and_neighbors();
>+ if constexpr (_S_size == 2)
>+ {
>+ static_assert(_S_is_scalar);
>+ _M_data0 = _M_data1 = _M_data0 && _M_data1;
>+ }
>+ else
>+ {
>+ _M_data0._M_and_neighbors();
>+ _M_data1._M_and_neighbors();
>+ }
> return *this;
> }
>
>@@ -1528,8 +1536,16 @@ namespace simd
> constexpr basic_mask&
> _M_or_neighbors()
> {
>- _M_data0._M_or_neighbors();
>- _M_data1._M_or_neighbors();
>+ if constexpr (_S_size == 2)
>+ {
>+ static_assert(_S_is_scalar);
>+ _M_data0 = _M_data1 = _M_data0 || _M_data1;
>+ }
>+ else
>+ {
>+ _M_data0._M_or_neighbors();
>+ _M_data1._M_or_neighbors();
>+ }
> return *this;
> }
>
>@@ -1650,7 +1666,7 @@ namespace simd
> else if constexpr (_M_data1._S_has_bool_member)
> // in some cases the last element can be 'bool' instead of bit-/vector-mask;
> // e.g. mask<short, 17> is {mask<short, 16>, mask<short, 1>}, where the latter uses
>- // _ScalarAbi<1>, which is stored as 'bool'
>+ // _Abi<1, 1>, which is stored as 'bool'
> return __i < _N0 ? _M_data0[__i] : _M_data1[__i - _N0];
> else if constexpr (abi_type::_S_is_bitmask)
> {
>@@ -1929,10 +1945,7 @@ namespace simd
> {
> const auto __bits = _M_to_uint();
> __glibcxx_simd_precondition(__bits, "An empty mask does not have a min_index.");
>- if constexpr (_S_size == 1)
>- return 0;
>- else
>- return __countr_zero(_M_to_uint());
>+ return __countr_zero(_M_to_uint());
> }
> else if (_M_data0._M_none_of())
> return _M_data1._M_reduce_min_index() + _N0;
>@@ -1948,10 +1961,7 @@ namespace simd
> {
> const auto __bits = _M_to_uint();
> __glibcxx_simd_precondition(__bits, "An empty mask does not have a max_index.");
>- if constexpr (_S_size == 1)
>- return 0;
>- else
>- return __highest_bit(_M_to_uint());
>+ return __highest_bit(_M_to_uint());
> }
> else if (_M_data1._M_none_of())
> return _M_data0._M_reduce_max_index();
>diff --git a/libstdc++-v3/include/bits/simd_vec.h b/libstdc++-v3/include/bits/simd_vec.h
>index 5f3bd7fd2f61..5624ec781426 100644
>--- a/libstdc++-v3/include/bits/simd_vec.h
>+++ b/libstdc++-v3/include/bits/simd_vec.h
>@@ -1776,7 +1776,7 @@ namespace simd
>
> _DataType1 _M_data1;
>
>- static constexpr bool _S_use_bitmask = _Ap::_S_is_bitmask;
>+ static constexpr bool _S_use_bitmask = _DataType0::_S_use_bitmask;
>
> static constexpr bool _S_is_partial = _DataType1::_S_is_partial;
>
>diff --git a/libstdc++-v3/testsuite/std/simd/traits_impl.cc b/libstdc++-v3/testsuite/std/simd/traits_impl.cc
>index 2f705c7df2f7..94c6843b6228 100644
>--- a/libstdc++-v3/testsuite/std/simd/traits_impl.cc
>+++ b/libstdc++-v3/testsuite/std/simd/traits_impl.cc
>@@ -49,24 +49,21 @@ void test()
> static_assert(sizeof(_Bitmask<3>) == 1);
> static_assert(sizeof(_Bitmask<30>) == 4);
>
>- static_assert(__scalar_abi_tag<_ScalarAbi<1>>);
>- static_assert(__scalar_abi_tag<_ScalarAbi<2>>);
>- static_assert(!__scalar_abi_tag<_Abi_t<1, 1>>);
>-
>- static_assert(__abi_tag<_ScalarAbi<1>>);
>- static_assert(__abi_tag<_ScalarAbi<2>>);
>+ static_assert(__scalar_abi_tag<_Abi_t<1, 1>>);
>+ static_assert(__scalar_abi_tag<_Abi_t<2, 2>>);
>+ static_assert(!__scalar_abi_tag<_Abi_t<2, 1>>);
>
> using AN = decltype(__native_abi<float>());
> using A1 = decltype(__native_abi<float>()._S_resize<1>());
> static_assert(A1::_S_size == 1);
> static_assert(A1::_S_nreg == 1);
> static_assert(A1::_S_variant == AN::_S_variant);
>- static_assert(__scalar_abi_tag<A1> == __scalar_abi_tag<AN>);
> static_assert(std::is_same_v<decltype(__abi_rebind<float, AN::_S_size, A1>()), AN>);
> if constexpr (AN::_S_size >= 2) // the target has SIMD support for float
> {
> {
> using A2 = decltype(__abi_rebind<float, 2, AN>());
>+ static_assert(__scalar_abi_tag<A2> == __scalar_abi_tag<AN>);
> static_assert(A2::_S_size == 2);
> static_assert(A2::_S_nreg == 1);
> static_assert(A2::_S_variant == AN::_S_variant);
>--
>2.54.0
>
>
More information about the Libstdc++
mailing list