libstdc++: Implement P4012R1 while reverting P3844R2 (consteval simd broadcast)

P3844R2 added consteval conversion for value-preserving conversion from
constants. It had been approved by LEWG in Kona. Therefore, the current
implementation has the consteval broadcast constructor. In Croydon, LEWG
reversed the decision but changed the overload set to keep the design
space open for C++29.

This patch implements the removal of the consteval constructor and
changes the broadcast constructor according to P4012R1, to keep the
design space open.

libstdc++-v3/ChangeLog:

	* include/bits/simd_details.h (__value_preserving_cast): Remove.
	* include/bits/simd_mask.h (basic_mask): Replace plain 0 and 1
	literals with cw<0> and cw<1>. Replace explicit basic_vec
	construction from 0 and 1 with default init and broadcast from
	_Up(1).
	(_M_to_uint): Replace 1 with cw<1>.
	* include/bits/simd_vec.h (basic_vec): Remove consteval
	broadcast overload. Remove explicit broadcast from
	non-value-preserving types.
	* testsuite/std/simd/arithmetic.cc: Replace ill-formed integer
	literals with explicit cast to T or use cw.
	* testsuite/std/simd/mask.cc: Likewise.
	* testsuite/std/simd/simd_alg.cc: Likewise.
	* testsuite/std/simd/traits_common.cc: Adjust for resulting
	traits changes.
	* testsuite/std/simd/traits_math.cc: Likewise.

Signed-off-by: Matthias Kretz <m.kretz@gsi.de>
This commit is contained in:
Matthias Kretz
2026-04-15 18:04:29 +02:00
parent d92712cdcb
commit 804bde962d
8 changed files with 39 additions and 61 deletions

View File

@@ -1241,15 +1241,6 @@ namespace simd
return static_cast<_To>(__x);
}
template <typename _From, typename _To>
concept __simd_vec_bcast_consteval
= __explicitly_convertible_to<_From, _To>
&& is_arithmetic_v<remove_cvref_t<_From>> && convertible_to<_From, _To>
&& !__value_preserving_convertible_to<remove_cvref_t<_From>, _To>
&& (is_same_v<common_type_t<_From, _To>, _To>
|| (is_same_v<remove_cvref_t<_From>, int> && is_integral_v<_To>)
|| (is_same_v<remove_cvref_t<_From>, unsigned> && unsigned_integral<_To>));
/** @internal
* std::pair is not trivially copyable, this one is
*/

View File

@@ -865,11 +865,12 @@ namespace simd
using _Ip = typename _VecType::value_type;
_VecType __v0 = _Ip(__val);
constexpr int __bits_per_element = sizeof(_Ip) * __CHAR_BIT__;
constexpr _VecType __pow2 = _VecType(1) << (__iota<_VecType> % __bits_per_element);
constexpr _VecType __pow2 = _VecType(cw<1>)
<< (__iota<_VecType> % cw<__bits_per_element>);
if constexpr (_S_size < __bits_per_element)
return ((__v0 & __pow2) > 0)._M_concat_data();
return ((__v0 & __pow2) > cw<0>)._M_concat_data();
else if constexpr (_S_size == __bits_per_element)
return ((__v0 & __pow2) != 0)._M_concat_data();
return ((__v0 & __pow2) != cw<0>)._M_concat_data();
else
{
static_assert(_Bytes == 1);
@@ -886,7 +887,7 @@ namespace simd
};
__v1 *= 0x0101'0101'0101'0101ull;
__v0 = __builtin_bit_cast(_VecType, __v1);
return ((__v0 & __pow2) != 0)._M_data;
return ((__v0 & __pow2) != cw<0>)._M_data;
}
else
{
@@ -895,7 +896,7 @@ namespace simd
__v0 = _VecType::_S_static_permute(__v1, [](int __i) {
return __i / __CHAR_BIT__;
});
return ((__v0 & __pow2) != 0)._M_data;
return ((__v0 & __pow2) != cw<0>)._M_data;
}
}
}
@@ -991,7 +992,7 @@ namespace simd
else
{
using _UV = basic_vec<_Up, _UAbi>;
return __select_impl(static_cast<_UV::mask_type>(*this), _UV(1), _UV(0));
return __select_impl(static_cast<_UV::mask_type>(*this), _Up(1), _UV());
}
}
@@ -1066,7 +1067,7 @@ namespace simd
constexpr int __n = _IV::size();
if constexpr (_Bytes * __CHAR_BIT__ >= __n) // '1 << __iota' cannot overflow
{ // reduce(select(k, powers_of_2, 0))
constexpr _IV __pow2 = _IV(1) << __iota<_IV>;
constexpr _IV __pow2 = _IV(cw<1>) << __iota<_IV>;
return _Ur(_U0(__select_impl(__k, __pow2, _IV())
._M_reduce(bit_or<>()))) << _Offset;
}
@@ -1079,7 +1080,7 @@ namespace simd
}
else
{ // limit powers_of_2 to 1, 2, 4, ..., 128
constexpr _IV __pow2 = _IV(1) << (__iota<_IV> % _IV(__CHAR_BIT__));
constexpr _IV __pow2 = _IV(cw<1>) << (__iota<_IV> % _IV(cw<__CHAR_BIT__>));
_IV __x = __select_impl(__k, __pow2, _IV());
// partial reductions of 8 neighboring elements
__x |= _IV::_S_static_permute(__x, _SwapNeighbors<4>());

View File

@@ -1135,20 +1135,13 @@ namespace simd
*
* @note The constructor is implicit if the conversion (if any) is value-preserving.
*/
template <__explicitly_convertible_to<value_type> _Up>
template <__broadcast_constructible<value_type> _Up>
[[__gnu__::__always_inline__]]
constexpr explicit(!__broadcast_constructible<_Up, value_type>)
constexpr
basic_vec(_Up&& __x) noexcept
: _M_data(_DataType() == _DataType() ? static_cast<value_type>(__x) : value_type())
{}
template <__simd_vec_bcast_consteval<value_type> _Up>
consteval
basic_vec(_Up&& __x)
: _M_data(_DataType() == _DataType()
? __value_preserving_cast<value_type>(__x) : value_type())
{}
// [simd.ctor] conversion constructor -----------------------------------
template <typename _Up, typename _UAbi, _TargetTraits _Traits = {}>
requires (_S_size == _UAbi::_S_size)
@@ -2037,20 +2030,13 @@ namespace simd
{ return _M_concat_data(); }
// [simd.ctor] broadcast constructor ------------------------------------
template <__explicitly_convertible_to<value_type> _Up>
template <__broadcast_constructible<value_type> _Up>
[[__gnu__::__always_inline__]]
constexpr explicit(!__broadcast_constructible<_Up, value_type>)
constexpr
basic_vec(_Up&& __x) noexcept
: _M_data0(static_cast<value_type>(__x)), _M_data1(static_cast<value_type>(__x))
{}
template <__simd_vec_bcast_consteval<value_type> _Up>
consteval
basic_vec(_Up&& __x)
: _M_data0(__value_preserving_cast<value_type>(__x)),
_M_data1(__value_preserving_cast<value_type>(__x))
{}
// [simd.ctor] conversion constructor -----------------------------------
template <typename _Up, typename _UAbi>
requires (_S_size == _UAbi::_S_size)

View File

@@ -88,7 +88,8 @@ template <typename V>
};
ADD_TEST(multiplication) {
std::tuple {V(), V(RealV(1), RealV()), V(RealV(), RealV(1)), init_vec<V, C(0, 2), C(2, 0), C(-1, 2)>},
std::tuple {V(), V(RealV(Real(1)), RealV()), V(RealV(), RealV(Real(1))),
init_vec<V, C(0, 2), C(2, 0), C(-1, 2)>},
[](auto& t, V x, V one, V I, V z) {
t.verify_equal(x * x, x);
t.verify_equal(x * z, x);
@@ -181,11 +182,11 @@ template <typename V>
t.verify_equal(y, x - T(1));
t.verify_equal(x - x, y);
t.verify_equal(x = z - x, init_vec<V, 0, 1, 2, 3, 4, 5, 6>);
t.verify_equal(x = z - x, V(1));
t.verify_equal(x = z - x, T(1));
t.verify_equal(z -= x, init_vec<V, 0, 1, 2, 3, 4, 5, 6>);
t.verify_equal(z, init_vec<V, 0, 1, 2, 3, 4, 5, 6>);
t.verify_equal(z -= z, V(0));
t.verify_equal(z, V(0));
t.verify_equal(z -= z, V());
t.verify_equal(z, V());
}
};
@@ -259,10 +260,10 @@ template <typename V>
ADD_TEST(divide0, std::is_floating_point_v<T> && !is_iec559) {
std::tuple{T(2), init_vec<V, 1, 2, 3, 4, 5, 6, 7>},
[](auto& t, V x, V y) {
t.verify_equal_to_ulp(x / x, V(T(1)), 1);
t.verify_equal_to_ulp(T(3) / x, V(T(3) / T(2)), 1);
t.verify_equal_to_ulp(x / T(3), V(T(2) / T(3)), 1);
t.verify_equal_to_ulp(y / x, init_vec<V, .5, 1, 1.5, 2, 2.5, 3, 3.5>, 1);
t.verify_equal_to_ulp(x / x, V(T(1)), std::cw<1>);
t.verify_equal_to_ulp(T(3) / x, V(T(3) / T(2)), std::cw<1>);
t.verify_equal_to_ulp(x / T(3), V(T(2) / T(3)), std::cw<1>);
t.verify_equal_to_ulp(y / x, init_vec<V, .5, 1, 1.5, 2, 2.5, 3, 3.5>, std::cw<1>);
}
};
@@ -272,18 +273,18 @@ template <typename V>
[](auto& t, V a) {
V b = std::cw<2>;
V ref([&](int i) { return a[i] / 2; });
t.verify_equal_to_ulp(a / b, ref, 1);
t.verify_equal_to_ulp(a / b, ref, std::cw<1>);
a = select(a == std::cw<0>, T(1), a);
// -freciprocal-math together with flush-to-zero makes
// the following range restriction necessary (i.e.
// 1/|a| must be >= min). Intel vrcpps and vrcp14ps
// need some extra slack (use 1.1 instead of 1).
a = select(fabs(a) >= T(1.1) / norm_min, T(1), a);
t.verify_equal_to_ulp(a / a, V(1), 1)("\na = ", a);
t.verify_equal_to_ulp(a / a, V(std::cw<1>), std::cw<1>)("\na = ", a);
ref = V([&](int i) { return 2 / a[i]; });
t.verify_equal_to_ulp(b / a, ref, 1)("\na = ", a);
t.verify_equal_to_ulp(b /= a, ref, 1);
t.verify_equal_to_ulp(b, ref, 1);
t.verify_equal_to_ulp(b / a, ref, std::cw<1>)("\na = ", a);
t.verify_equal_to_ulp(b /= a, ref, std::cw<1>);
t.verify_equal_to_ulp(b, ref, std::cw<1>);
}
};
@@ -291,15 +292,15 @@ template <typename V>
std::tuple{T(2), init_vec<V, 1, 2, 3, 4, 5, 6, 7>, init_vec<V, T(max), T(norm_min)>,
init_vec<V, T(norm_min), T(max)>, init_vec<V, T(max), T(norm_min) + 1>},
[](auto& t, V x, V y, V z, V a, V b) {
t.verify_equal(x / x, V(1));
t.verify_equal(T(3) / x, V(T(3) / T(2)));
t.verify_equal(x / T(3), V(T(2) / T(3)));
t.verify_equal(x / x, T(1));
t.verify_equal(T(3) / x, T(T(3) / T(2)));
t.verify_equal(x / T(3), T(T(2) / T(3)));
t.verify_equal(y / x, init_vec<V, .5, 1, 1.5, 2, 2.5, 3, 3.5>);
V ref = init_vec<V, T(max / 2), T(norm_min / 2)>;
t.verify_equal(z / x, ref);
ref = init_vec<V, T(norm_min / 2), T(max / 2)>;
t.verify_equal(a / x, ref);
t.verify_equal(b / b, V(1));
t.verify_equal(b / b, T(1));
ref = init_vec<V, T(2 / max), T(2 / (norm_min + 1))>;
t.verify_equal(x / b, ref);
t.verify_equal(x /= b, ref);

View File

@@ -54,14 +54,14 @@ template <typename V>
return i % 13 == 0 || i % 7 == 0;
})},
[](auto& t, const M k, const M tr, const M fa, const M k2) {
t.verify_equal(V(+tr), V(1));
t.verify_equal(V(+tr), T(1));
t.verify_equal(V(+fa), V());
t.verify_equal(V(+k), init_vec<V, 0, 1>);
if constexpr (std::is_integral_v<T>)
{
t.verify_equal(V(~tr), ~V(1));
t.verify_equal(V(~fa), ~V(0));
t.verify_equal(V(~tr), ~V(std::cw<1>));
t.verify_equal(V(~fa), ~V());
t.verify_equal(V(~k), ~init_vec<V, 0, 1>);
}

View File

@@ -13,8 +13,7 @@ template <typename V>
using M = typename V::mask_type;
using pair = std::pair<V, V>;
static constexpr std::conditional_t<std::is_floating_point_v<T>, short, T> x_max
= test_iota_max<V, 1>;
static constexpr T x_max = test_iota_max<V, 1>;
static constexpr int x_max_int = static_cast<int>(x_max);
static constexpr V
@@ -26,7 +25,7 @@ template <typename V>
return static_cast<V>(std::to_underlying(x_max) - static_cast<Vu>(x));
}
else
return x_max - x;
return std::cw<x_max> - x;
}
ADD_TEST(Select) {

View File

@@ -59,7 +59,7 @@ namespace test02
// ensure 'true ? int : vec<float>' doesn't work
template <typename T>
concept has_type_member = requires { typename T::type; };
static_assert(has_type_member<common_type<int, simd::vec<float>>>);
static_assert(!has_type_member<common_type<int, simd::vec<float>>>);
}
#if defined __AVX__ && !defined __AVX2__
@@ -90,7 +90,7 @@ static_assert( std::convertible_to<Ic<1>, simd::vec<float>>);
static_assert(!std::convertible_to<Ic<1.1>, simd::vec<float>>);
static_assert(!std::convertible_to<simd::vec<int, 4>, simd::vec<float, 4>>);
static_assert(!std::convertible_to<simd::vec<float, 4>, simd::vec<int, 4>>);
static_assert( std::convertible_to<int, simd::vec<float>>);
static_assert(!std::convertible_to<int, simd::vec<float>>);
static_assert( std::convertible_to<simd::vec<int, 4>, simd::vec<double, 4>>);
template <typename V>

View File

@@ -25,7 +25,7 @@ namespace math_tests
concept has_deduced_vec = requires { typename simd::__deduced_vec_t<T>; };
static_assert(!has_common_type<vf2, vf4>);
static_assert( has_common_type<int, vf2>);
static_assert(!has_common_type<int, vf2>);
template <typename T, bool Strict = false>
struct holder