Skip to content

Commit 2c3d7d5

Browse files
authored
[libc++] <experimental/simd> Add unary operators for class simd (#104764)
1 parent 076d3e2 commit 2c3d7d5

File tree

6 files changed

+275
-1
lines changed

6 files changed

+275
-1
lines changed

libcxx/docs/Status/ParallelismProjects.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Section,Description,Dependencies,Assignee,Complete
2828
| `[parallel.simd.class] <https://wg21.link/N4808>`_, "`simd load constructor <https://github.com/llvm/llvm-project/pull/76610>`_", None, Yin Zhang, |Complete|
2929
| `[parallel.simd.class] <https://wg21.link/N4808>`_, "`simd subscript operators <https://github.com/llvm/llvm-project/pull/68960>`_", None, Yin Zhang, |Complete|
3030
| `[parallel.simd.class] <https://wg21.link/N4808>`_, "`simd copy functions <https://github.com/llvm/llvm-project/pull/78935>`_", None, Yin Zhang, |Complete|
31+
| `[parallel.simd.class] <https://wg21.link/N4808>`_, "`simd unary operators <https://github.com/llvm/llvm-project/pull/104764>`_", None, Yin Zhang, |Complete|
3132
| `[parallel.simd.class] <https://wg21.link/N4808>`_, "Class template simd implementation", None, Yin Zhang, |In Progress|
3233
| `[parallel.simd.nonmembers] <https://wg21.link/N4808>`_, "simd non-member operations", None, Yin Zhang, |In Progress|
3334
| `[parallel.simd.mask.class] <https://wg21.link/N4808>`_, "`Class template simd_mask declaration and alias <https://reviews.llvm.org/D144362>`_", [parallel.simd.abi], Yin Zhang, |Complete|

libcxx/include/experimental/__simd/scalar.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ struct __simd_operations<_Tp, simd_abi::__scalar> {
6868
static _LIBCPP_HIDE_FROM_ABI void __store(_SimdStorage __s, _Up* __mem) noexcept {
6969
*__mem = static_cast<_Up>(__s.__data);
7070
}
71+
72+
static _LIBCPP_HIDE_FROM_ABI void __increment(_SimdStorage& __s) noexcept { ++__s.__data; }
73+
74+
static _LIBCPP_HIDE_FROM_ABI void __decrement(_SimdStorage& __s) noexcept { --__s.__data; }
75+
76+
static _LIBCPP_HIDE_FROM_ABI _MaskStorage __negate(_SimdStorage __s) noexcept { return {!__s.__data}; }
77+
78+
static _LIBCPP_HIDE_FROM_ABI _SimdStorage __bitwise_not(_SimdStorage __s) noexcept {
79+
return {static_cast<_Tp>(~__s.__data)};
80+
}
81+
82+
static _LIBCPP_HIDE_FROM_ABI _SimdStorage __unary_minus(_SimdStorage __s) noexcept {
83+
return {static_cast<_Tp>(-__s.__data)};
84+
}
7185
};
7286

7387
template <class _Tp>

libcxx/include/experimental/__simd/simd.h

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <__config>
1414
#include <__type_traits/enable_if.h>
15+
#include <__type_traits/is_integral.h>
1516
#include <__type_traits/is_same.h>
1617
#include <__type_traits/remove_cvref.h>
1718
#include <__utility/forward.h>
@@ -26,15 +27,29 @@
2627
_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL
2728
inline namespace parallelism_v2 {
2829

30+
template <class _Simd, class _Impl, bool>
31+
class __simd_int_operators {};
32+
33+
template <class _Simd, class _Impl>
34+
class __simd_int_operators<_Simd, _Impl, true> {
35+
public:
36+
// unary operators for integral _Tp
37+
_LIBCPP_HIDE_FROM_ABI _Simd operator~() const noexcept {
38+
return _Simd(_Impl::__bitwise_not((*static_cast<const _Simd*>(this)).__s_), _Simd::__storage_tag);
39+
}
40+
};
41+
2942
// class template simd [simd.class]
3043
// TODO: implement simd class
3144
template <class _Tp, class _Abi>
32-
class simd {
45+
class simd : public __simd_int_operators<simd<_Tp, _Abi>, __simd_operations<_Tp, _Abi>, is_integral_v<_Tp>> {
3346
using _Impl = __simd_operations<_Tp, _Abi>;
3447
using _Storage = typename _Impl::_SimdStorage;
3548

3649
_Storage __s_;
3750

51+
friend class __simd_int_operators<simd, _Impl, true>;
52+
3853
public:
3954
using value_type = _Tp;
4055
using reference = __simd_reference<_Tp, _Storage, value_type>;
@@ -45,6 +60,12 @@ class simd {
4560

4661
_LIBCPP_HIDE_FROM_ABI simd() noexcept = default;
4762

63+
// explicit conversion from and to implementation-defined types
64+
struct __storage_tag_t {};
65+
static constexpr __storage_tag_t __storage_tag{};
66+
explicit _LIBCPP_HIDE_FROM_ABI operator _Storage() const { return __s_; }
67+
explicit _LIBCPP_HIDE_FROM_ABI simd(const _Storage& __s, __storage_tag_t) : __s_(__s) {}
68+
4869
// broadcast constructor
4970
template <class _Up, enable_if_t<__can_broadcast_v<value_type, __remove_cvref_t<_Up>>, int> = 0>
5071
_LIBCPP_HIDE_FROM_ABI simd(_Up&& __v) noexcept : __s_(_Impl::__broadcast(static_cast<value_type>(__v))) {}
@@ -85,6 +106,37 @@ class simd {
85106
// scalar access [simd.subscr]
86107
_LIBCPP_HIDE_FROM_ABI reference operator[](size_t __i) noexcept { return reference(__s_, __i); }
87108
_LIBCPP_HIDE_FROM_ABI value_type operator[](size_t __i) const noexcept { return __s_.__get(__i); }
109+
110+
// simd unary operators
111+
_LIBCPP_HIDE_FROM_ABI simd& operator++() noexcept {
112+
_Impl::__increment(__s_);
113+
return *this;
114+
}
115+
116+
_LIBCPP_HIDE_FROM_ABI simd operator++(int) noexcept {
117+
simd __r = *this;
118+
_Impl::__increment(__s_);
119+
return __r;
120+
}
121+
122+
_LIBCPP_HIDE_FROM_ABI simd& operator--() noexcept {
123+
_Impl::__decrement(__s_);
124+
return *this;
125+
}
126+
127+
_LIBCPP_HIDE_FROM_ABI simd operator--(int) noexcept {
128+
simd __r = *this;
129+
_Impl::__decrement(__s_);
130+
return __r;
131+
}
132+
133+
_LIBCPP_HIDE_FROM_ABI mask_type operator!() const noexcept {
134+
return mask_type(_Impl::__negate(__s_), mask_type::__storage_tag);
135+
}
136+
137+
_LIBCPP_HIDE_FROM_ABI simd operator+() const noexcept { return *this; }
138+
139+
_LIBCPP_HIDE_FROM_ABI simd operator-() const noexcept { return simd(_Impl::__unary_minus(__s_), __storage_tag); }
88140
};
89141

90142
template <class _Tp, class _Abi>

libcxx/include/experimental/__simd/simd_mask.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class simd_mask {
4242

4343
_LIBCPP_HIDE_FROM_ABI simd_mask() noexcept = default;
4444

45+
// explicit conversion from and to implementation-defined types
46+
struct __storage_tag_t {};
47+
static constexpr __storage_tag_t __storage_tag{};
48+
explicit _LIBCPP_HIDE_FROM_ABI operator _Storage() const { return __s_; }
49+
explicit _LIBCPP_HIDE_FROM_ABI simd_mask(const _Storage& __s, __storage_tag_t) : __s_(__s) {}
50+
4551
// broadcast constructor
4652
_LIBCPP_HIDE_FROM_ABI explicit simd_mask(value_type __v) noexcept : __s_(_Impl::__broadcast(__v)) {}
4753

libcxx/include/experimental/__simd/vec_ext.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ struct __simd_operations<_Tp, simd_abi::__vec_ext<_Np>> {
8787
for (size_t __i = 0; __i < _Np; __i++)
8888
__mem[__i] = static_cast<_Up>(__s.__data[__i]);
8989
}
90+
91+
static _LIBCPP_HIDE_FROM_ABI void __increment(_SimdStorage& __s) noexcept { __s.__data = __s.__data + 1; }
92+
93+
static _LIBCPP_HIDE_FROM_ABI void __decrement(_SimdStorage& __s) noexcept { __s.__data = __s.__data - 1; }
94+
95+
static _LIBCPP_HIDE_FROM_ABI _MaskStorage __negate(_SimdStorage __s) noexcept { return {!__s.__data}; }
96+
97+
static _LIBCPP_HIDE_FROM_ABI _SimdStorage __bitwise_not(_SimdStorage __s) noexcept { return {~__s.__data}; }
98+
99+
static _LIBCPP_HIDE_FROM_ABI _SimdStorage __unary_minus(_SimdStorage __s) noexcept { return {-__s.__data}; }
90100
};
91101

92102
template <class _Tp, int _Np>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14
10+
11+
// Older versions of clang may encounter a backend error (see 0295c2ad):
12+
// Pass-by-value arguments with alignment greater than register width are not supported.
13+
// XFAIL: target=powerpc{{.*}}-ibm-{{.*}} && (clang-17 || clang-18)
14+
15+
// FIXME: The following issue occurs on macOS 13.7 with Apple clang 15.0.0:
16+
// clang: error: unable to execute command: Illegal instruction: 4
17+
// XFAIL: target=x86_64-apple-macosx13.7
18+
19+
// <experimental/simd>
20+
//
21+
// [simd.class]
22+
// simd& operator++() noexcept;
23+
// simd operator++(int) noexcept;
24+
// simd& operator--() noexcept;
25+
// simd operator--(int) noexcept;
26+
// mask_type operator!() const noexcept;
27+
// simd operator~() const noexcept;
28+
// simd operator+() const noexcept;
29+
// simd operator-() const noexcept;
30+
31+
#include "../test_utils.h"
32+
#include <experimental/simd>
33+
34+
namespace ex = std::experimental::parallelism_v2;
35+
36+
template <class T, std::size_t>
37+
struct CheckSimdPrefixIncrementOperator {
38+
template <class SimdAbi>
39+
void operator()() {
40+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
41+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
42+
static_assert(noexcept(++origin_simd));
43+
std::array<T, array_size> expected_return_value, expected_value;
44+
for (size_t i = 0; i < array_size; ++i) {
45+
expected_return_value[i] = static_cast<T>(i) + 1;
46+
expected_value[i] = static_cast<T>(i) + 1;
47+
}
48+
assert_simd_values_equal<array_size>(++origin_simd, expected_return_value);
49+
assert_simd_values_equal<array_size>(origin_simd, expected_value);
50+
}
51+
};
52+
53+
template <class T, std::size_t>
54+
struct CheckSimdPostfixIncrementOperator {
55+
template <class SimdAbi>
56+
void operator()() {
57+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
58+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
59+
static_assert(noexcept(origin_simd++));
60+
std::array<T, array_size> expected_return_value, expected_value;
61+
for (size_t i = 0; i < array_size; ++i) {
62+
expected_return_value[i] = static_cast<T>(i);
63+
expected_value[i] = static_cast<T>(i) + 1;
64+
}
65+
assert_simd_values_equal<array_size>(origin_simd++, expected_return_value);
66+
assert_simd_values_equal<array_size>(origin_simd, expected_value);
67+
}
68+
};
69+
70+
template <class T, std::size_t>
71+
struct CheckSimdPrefixDecrementOperator {
72+
template <class SimdAbi>
73+
void operator()() {
74+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
75+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
76+
static_assert(noexcept(--origin_simd));
77+
std::array<T, array_size> expected_return_value, expected_value;
78+
for (size_t i = 0; i < array_size; ++i) {
79+
expected_return_value[i] = static_cast<T>(i) - 1;
80+
expected_value[i] = static_cast<T>(i) - 1;
81+
}
82+
assert_simd_values_equal<array_size>(--origin_simd, expected_return_value);
83+
assert_simd_values_equal<array_size>(origin_simd, expected_value);
84+
}
85+
};
86+
87+
template <class T, std::size_t>
88+
struct CheckSimdPostfixDecrementOperator {
89+
template <class SimdAbi>
90+
void operator()() {
91+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
92+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
93+
static_assert(noexcept(origin_simd--));
94+
std::array<T, array_size> expected_return_value, expected_value;
95+
for (size_t i = 0; i < array_size; ++i) {
96+
expected_return_value[i] = static_cast<T>(i);
97+
expected_value[i] = static_cast<T>(i) - 1;
98+
}
99+
assert_simd_values_equal<array_size>(origin_simd--, expected_return_value);
100+
assert_simd_values_equal<array_size>(origin_simd, expected_value);
101+
}
102+
};
103+
104+
template <class T, std::size_t>
105+
struct CheckSimdNegationOperator {
106+
template <class SimdAbi>
107+
void operator()() {
108+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
109+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
110+
static_assert(noexcept(!origin_simd));
111+
std::array<bool, array_size> expected_value;
112+
for (size_t i = 0; i < array_size; ++i)
113+
expected_value[i] = !static_cast<bool>(i);
114+
assert_simd_mask_values_equal<array_size>(!origin_simd, expected_value);
115+
}
116+
};
117+
118+
template <class T, std::size_t>
119+
struct CheckSimdBitwiseNotOperator {
120+
template <class SimdAbi>
121+
void operator()() {
122+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
123+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
124+
static_assert(noexcept(~origin_simd));
125+
std::array<T, array_size> expected_value;
126+
for (size_t i = 0; i < array_size; ++i)
127+
expected_value[i] = ~static_cast<T>(i);
128+
assert_simd_values_equal<array_size>(~origin_simd, expected_value);
129+
}
130+
};
131+
132+
template <class T, std::size_t>
133+
struct CheckSimdPositiveSignOperator {
134+
template <class SimdAbi>
135+
void operator()() {
136+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
137+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
138+
static_assert(noexcept(+origin_simd));
139+
std::array<T, array_size> expected_value;
140+
for (size_t i = 0; i < array_size; ++i)
141+
expected_value[i] = +static_cast<T>(i);
142+
assert_simd_values_equal<array_size>(+origin_simd, expected_value);
143+
}
144+
};
145+
146+
template <class T, std::size_t>
147+
struct CheckSimdNegativeSignOperator {
148+
template <class SimdAbi>
149+
void operator()() {
150+
constexpr std::size_t array_size = ex::simd_size_v<T, SimdAbi>;
151+
ex::simd<T, SimdAbi> origin_simd([](T i) { return i; });
152+
static_assert(noexcept(-origin_simd));
153+
std::array<T, array_size> expected_value;
154+
for (size_t i = 0; i < array_size; ++i)
155+
expected_value[i] = -static_cast<T>(i);
156+
assert_simd_values_equal<array_size>(-origin_simd, expected_value);
157+
}
158+
};
159+
160+
template <class T, class SimdAbi = ex::simd_abi::compatible<T>, class = void>
161+
struct has_bitwise_not_op : std::false_type {};
162+
163+
template <class T, class SimdAbi>
164+
struct has_bitwise_not_op<T, SimdAbi, std::void_t<decltype(~std::declval<ex::simd<T, SimdAbi>>())>> : std::true_type {};
165+
166+
template <class T, std::size_t>
167+
struct CheckSimdBitwiseNotTraits {
168+
template <class SimdAbi>
169+
void operator()() {
170+
// This function shall not participate in overload resolution unless
171+
// T is an integral type.
172+
if constexpr (std::is_integral_v<T>)
173+
static_assert(has_bitwise_not_op<T, SimdAbi>::value);
174+
// T is not an integral type.
175+
else
176+
static_assert(!has_bitwise_not_op<T, SimdAbi>::value);
177+
}
178+
};
179+
180+
int main(int, char**) {
181+
test_all_simd_abi<CheckSimdPrefixIncrementOperator>();
182+
test_all_simd_abi<CheckSimdPostfixIncrementOperator>();
183+
test_all_simd_abi<CheckSimdPrefixDecrementOperator>();
184+
test_all_simd_abi<CheckSimdPostfixDecrementOperator>();
185+
test_all_simd_abi<CheckSimdNegationOperator>();
186+
types::for_each(types::integer_types(), TestAllSimdAbiFunctor<CheckSimdBitwiseNotOperator>());
187+
test_all_simd_abi<CheckSimdPositiveSignOperator>();
188+
test_all_simd_abi<CheckSimdNegativeSignOperator>();
189+
test_all_simd_abi<CheckSimdBitwiseNotTraits>();
190+
return 0;
191+
}

0 commit comments

Comments
 (0)