Skip to content

Commit 0c7bd24

Browse files
[SYCL] Add has_known_identity/known_identity (#2528)
* [SYCL] Add has_known_identity/known_identity type traits These traits have been requested by developers who would like to determine at compile-time whether the reduction() interface supports their use-case, or whether they need to fall back to some other implementation. Signed-off-by: John Pennycook <john.pennycook@intel.com> Co-authored-by: Roland Schulz <roland.schulz@intel.com>
1 parent ad8c9d1 commit 0c7bd24

File tree

4 files changed

+163
-43
lines changed

4 files changed

+163
-43
lines changed

sycl/doc/extensions/Reduction/Reduction.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,31 @@ unspecified reduction(span<T, Extent> var, const T& identity, BinaryOperation co
3838
3939
The exact behavior of a reduction is specific to an implementation; the only interface exposed to the user is the set of functions above, which construct an unspecified `reduction` object encapsulating the reduction variable, an optional operator identity and the reduction operator. For user-defined binary operations, an implementation should issue a compile-time warning if an identity is not specified and this is known to negatively impact performance (e.g. as a result of the implementation choosing a different reduction algorithm). For standard binary operations (e.g. `std::plus`) on arithmetic types, the implementation must determine the correct identity automatically in order to avoid performance penalties.
4040
41+
If an implementation can identify the identity value for a given combination of accumulator type `AccumulatorT` and function object type `BinaryOperation`, the value is defined as a member of the `known_identity` trait class:
42+
```c++
43+
template <typename BinaryOperation, typename AccumulatorT>
44+
struct known_identity {
45+
static constexpr AccumulatorT value;
46+
};
47+
48+
// Available if C++17
49+
template <typename BinaryOperation, typename AccumulatorT>
50+
inline constexpr AccumulatorT known_identity_v = known_identity<BinaryOperation, AccumulatorT>::value;
51+
```
52+
53+
Whether `known_identity<BinaryOperation, AccumulatorT>::value` exists can be tested using the `has_known_identity` trait class:
54+
55+
```c++
56+
template <typename BinaryOperation, typename AccumulatorT>
57+
struct has_known_identity {
58+
static constexpr bool value;
59+
};
60+
61+
// Available if C++17
62+
template <typename BinaryOperation, typename AccumulatorT>
63+
inline constexpr bool has_known_identity_v = has_known_identity<BinaryOperation, AccumulatorT>::value;
64+
```
65+
4166
The dimensionality of the `accessor` passed to the `reduction` function specifies the dimensionality of the reduction variable: a 0-dimensional `accessor` represents a scalar reduction, and any other dimensionality represents an array reduction. Specifying an array reduction of size N is functionally equivalent to specifying N independent scalar reductions. The access mode of the accessor determines whether the reduction variable's original value is included in the reduction (i.e. for `access::mode::read_write` it is included, and for `access::mode::discard_write` it is not). Multiple reductions aliasing the same output results in undefined behavior.
4267
4368
`T` must be trivially copyable, permitting an implementation to (optionally) use atomic operations to implement the reduction. This restriction is aligned with `std::atomic<T>` and `std::atomic_ref<T>`.

sycl/include/CL/sycl/ONEAPI/reduction.hpp

Lines changed: 101 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,85 @@ using IsKnownIdentityOp =
144144
IsMinimumIdentityOp<T, BinaryOperation>::value ||
145145
IsMaximumIdentityOp<T, BinaryOperation>::value>;
146146

147+
template <typename BinaryOperation, typename AccumulatorT>
148+
struct has_known_identity_impl
149+
: std::integral_constant<
150+
bool, IsKnownIdentityOp<AccumulatorT, BinaryOperation>::value> {};
151+
152+
template <typename BinaryOperation, typename AccumulatorT, typename = void>
153+
struct known_identity_impl {};
154+
155+
/// Returns zero as identity for ADD, OR, XOR operations.
156+
template <typename BinaryOperation, typename AccumulatorT>
157+
struct known_identity_impl<BinaryOperation, AccumulatorT,
158+
typename std::enable_if<IsZeroIdentityOp<
159+
AccumulatorT, BinaryOperation>::value>::type> {
160+
static constexpr AccumulatorT value = 0;
161+
};
162+
163+
template <typename BinaryOperation>
164+
struct known_identity_impl<BinaryOperation, half,
165+
typename std::enable_if<IsZeroIdentityOp<
166+
half, BinaryOperation>::value>::type> {
167+
static constexpr half value =
168+
#ifdef __SYCL_DEVICE_ONLY__
169+
0;
170+
#else
171+
cl::sycl::detail::host_half_impl::half(static_cast<uint16_t>(0));
172+
#endif
173+
};
174+
175+
/// Returns one as identify for MULTIPLY operations.
176+
template <typename BinaryOperation, typename AccumulatorT>
177+
struct known_identity_impl<BinaryOperation, AccumulatorT,
178+
typename std::enable_if<IsOneIdentityOp<
179+
AccumulatorT, BinaryOperation>::value>::type> {
180+
static constexpr AccumulatorT value = 1;
181+
};
182+
183+
template <typename BinaryOperation>
184+
struct known_identity_impl<BinaryOperation, half,
185+
typename std::enable_if<IsOneIdentityOp<
186+
half, BinaryOperation>::value>::type> {
187+
static constexpr half value =
188+
#ifdef __SYCL_DEVICE_ONLY__
189+
1;
190+
#else
191+
cl::sycl::detail::host_half_impl::half(static_cast<uint16_t>(0x3C00));
192+
#endif
193+
};
194+
195+
/// Returns bit image consisting of all ones as identity for AND operations.
196+
template <typename BinaryOperation, typename AccumulatorT>
197+
struct known_identity_impl<BinaryOperation, AccumulatorT,
198+
typename std::enable_if<IsOnesIdentityOp<
199+
AccumulatorT, BinaryOperation>::value>::type> {
200+
static constexpr AccumulatorT value = ~static_cast<AccumulatorT>(0);
201+
};
202+
203+
/// Returns maximal possible value as identity for MIN operations.
204+
template <typename BinaryOperation, typename AccumulatorT>
205+
struct known_identity_impl<BinaryOperation, AccumulatorT,
206+
typename std::enable_if<IsMinimumIdentityOp<
207+
AccumulatorT, BinaryOperation>::value>::type> {
208+
static constexpr AccumulatorT value =
209+
std::numeric_limits<AccumulatorT>::has_infinity
210+
? std::numeric_limits<AccumulatorT>::infinity()
211+
: (std::numeric_limits<AccumulatorT>::max)();
212+
};
213+
214+
/// Returns minimal possible value as identity for MAX operations.
215+
template <typename BinaryOperation, typename AccumulatorT>
216+
struct known_identity_impl<BinaryOperation, AccumulatorT,
217+
typename std::enable_if<IsMaximumIdentityOp<
218+
AccumulatorT, BinaryOperation>::value>::type> {
219+
static constexpr AccumulatorT value =
220+
std::numeric_limits<AccumulatorT>::has_infinity
221+
? static_cast<AccumulatorT>(
222+
-std::numeric_limits<AccumulatorT>::infinity())
223+
: std::numeric_limits<AccumulatorT>::lowest();
224+
};
225+
147226
/// Class that is used to represent objects that are passed to user's lambda
148227
/// functions and representing users' reduction variable.
149228
/// The generic version of the class represents those reductions of those
@@ -193,43 +272,10 @@ class reducer<T, BinaryOperation,
193272
MValue = BOp(MValue, Partial);
194273
}
195274

196-
/// Returns zero as identity for ADD, OR, XOR operations.
197-
template <typename _T = T, class _BinaryOperation = BinaryOperation>
198-
static enable_if_t<IsZeroIdentityOp<_T, _BinaryOperation>::value, _T>
199-
getIdentity() {
200-
return 0;
201-
}
202-
203-
/// Returns one as identify for MULTIPLY operations.
204-
template <typename _T = T, class _BinaryOperation = BinaryOperation>
205-
static enable_if_t<IsOneIdentityOp<_T, _BinaryOperation>::value, _T>
206-
getIdentity() {
207-
return 1;
208-
}
209-
210-
/// Returns bit image consisting of all ones as identity for AND operations.
211275
template <typename _T = T, class _BinaryOperation = BinaryOperation>
212-
static enable_if_t<IsOnesIdentityOp<_T, _BinaryOperation>::value, _T>
276+
static enable_if_t<has_known_identity_impl<_BinaryOperation, _T>::value, _T>
213277
getIdentity() {
214-
return ~static_cast<_T>(0);
215-
}
216-
217-
/// Returns maximal possible value as identity for MIN operations.
218-
template <typename _T = T, class _BinaryOperation = BinaryOperation>
219-
static enable_if_t<IsMinimumIdentityOp<_T, _BinaryOperation>::value, _T>
220-
getIdentity() {
221-
return std::numeric_limits<_T>::has_infinity
222-
? std::numeric_limits<_T>::infinity()
223-
: (std::numeric_limits<_T>::max)();
224-
}
225-
226-
/// Returns minimal possible value as identity for MAX operations.
227-
template <typename _T = T, class _BinaryOperation = BinaryOperation>
228-
static enable_if_t<IsMaximumIdentityOp<_T, _BinaryOperation>::value, _T>
229-
getIdentity() {
230-
return std::numeric_limits<_T>::has_infinity
231-
? static_cast<_T>(-std::numeric_limits<_T>::infinity())
232-
: std::numeric_limits<_T>::lowest();
278+
return known_identity_impl<_BinaryOperation, _T>::value;
233279
}
234280

235281
template <typename _T = T>
@@ -1062,6 +1108,26 @@ reduction(T *VarPtr, BinaryOperation) {
10621108
access::mode::read_write>(VarPtr);
10631109
}
10641110

1111+
template <typename BinaryOperation, typename AccumulatorT>
1112+
struct has_known_identity : detail::has_known_identity_impl<
1113+
typename std::decay<BinaryOperation>::type,
1114+
typename std::decay<AccumulatorT>::type> {};
1115+
#if __cplusplus >= 201703L
1116+
template <typename BinaryOperation, typename AccumulatorT>
1117+
inline constexpr bool has_known_identity_v =
1118+
has_known_identity<BinaryOperation, AccumulatorT>::value;
1119+
#endif
1120+
1121+
template <typename BinaryOperation, typename AccumulatorT>
1122+
struct known_identity
1123+
: detail::known_identity_impl<typename std::decay<BinaryOperation>::type,
1124+
typename std::decay<AccumulatorT>::type> {};
1125+
#if __cplusplus >= 201703L
1126+
template <typename BinaryOperation, typename AccumulatorT>
1127+
inline constexpr AccumulatorT known_identity_v =
1128+
known_identity<BinaryOperation, AccumulatorT>::value;
1129+
#endif
1130+
10651131
} // namespace ONEAPI
10661132
} // namespace sycl
10671133
} // __SYCL_INLINE_NAMESPACE(cl)

sycl/include/CL/sycl/half_type.hpp

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@
2626
#else
2727
#define __SYCL_CONSTEXPR_ON_DEVICE
2828
#endif
29+
#if __cplusplus >= 201402L
30+
#define _CPP14_CONSTEXPR constexpr
31+
#else
32+
#define _CPP14_CONSTEXPR
33+
#endif
2934

3035
__SYCL_INLINE_NAMESPACE(cl) {
3136
namespace sycl {
@@ -35,8 +40,8 @@ namespace host_half_impl {
3540
class __SYCL_EXPORT half {
3641
public:
3742
half() = default;
38-
half(const half &) = default;
39-
half(half &&) = default;
43+
constexpr half(const half &) = default;
44+
constexpr half(half &&) = default;
4045

4146
half(const float &rhs);
4247

@@ -74,11 +79,20 @@ class __SYCL_EXPORT half {
7479
return ret;
7580
}
7681

82+
// Operator neg
83+
_CPP14_CONSTEXPR half &operator-() {
84+
Buf ^= 0x8000;
85+
return *this;
86+
}
87+
7788
// Operator float
7889
operator float() const;
7990

8091
template <typename Key> friend struct std::hash;
8192

93+
// Initialize underlying data
94+
constexpr explicit half(uint16_t x) : Buf(x) {}
95+
8296
private:
8397
uint16_t Buf;
8498
};
@@ -136,8 +150,8 @@ class half;
136150
class half {
137151
public:
138152
half() = default;
139-
half(const half &) = default;
140-
half(half &&) = default;
153+
constexpr half(const half &) = default;
154+
constexpr half(half &&) = default;
141155

142156
__SYCL_CONSTEXPR_ON_DEVICE half(const float &rhs) : Data(rhs) {}
143157

@@ -146,8 +160,8 @@ class half {
146160
#ifndef __SYCL_DEVICE_ONLY__
147161
// Since StorageT and BIsRepresentationT are different on host, these two
148162
// helpers are required for 'vec' class
149-
half(const detail::host_half_impl::half &rhs) : Data(rhs) {};
150-
operator detail::host_half_impl::half() const { return Data; }
163+
constexpr half(const detail::host_half_impl::half &rhs) : Data(rhs){};
164+
constexpr operator detail::host_half_impl::half() const { return Data; }
151165
#endif // __SYCL_DEVICE_ONLY__
152166

153167
// Operator +=, -=, *=, /=
@@ -193,7 +207,14 @@ class half {
193207
operator--();
194208
return ret;
195209
}
196-
210+
_CPP14_CONSTEXPR half &operator-() {
211+
Data = -Data;
212+
return *this;
213+
}
214+
_CPP14_CONSTEXPR half operator-() const {
215+
half r = *this;
216+
return -r;
217+
}
197218
// Operator float
198219
operator float() const { return static_cast<float>(Data); }
199220

@@ -280,8 +301,13 @@ template <> struct numeric_limits<cl::sycl::half> {
280301
return 0.5f;
281302
}
282303

283-
static __SYCL_CONSTEXPR_ON_DEVICE const cl::sycl::half infinity() noexcept {
304+
static constexpr const cl::sycl::half infinity() noexcept {
305+
#ifdef __SYCL_DEVICE_ONLY__
284306
return __builtin_huge_valf();
307+
#else
308+
return cl::sycl::detail::host_half_impl::half(
309+
static_cast<uint16_t>(0x7C00));
310+
#endif
285311
}
286312

287313
static __SYCL_CONSTEXPR_ON_DEVICE const cl::sycl::half quiet_NaN() noexcept {
@@ -313,3 +339,4 @@ inline std::istream &operator>>(std::istream &I, cl::sycl::half &rhs) {
313339
}
314340

315341
#undef __SYCL_CONSTEXPR_ON_DEVICE
342+
#undef _CPP14_CONSTEXPR

sycl/test/regression/constexpr-fp16-numeric-limits.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ int main() {
1010
constexpr cl::sycl::half L5 =
1111
std::numeric_limits<cl::sycl::half>::round_error();
1212
constexpr cl::sycl::half L6 = std::numeric_limits<cl::sycl::half>::infinity();
13+
constexpr cl::sycl::half L6n =
14+
-std::numeric_limits<cl::sycl::half>::infinity();
1315
constexpr cl::sycl::half L7 =
1416
std::numeric_limits<cl::sycl::half>::quiet_NaN();
1517
constexpr cl::sycl::half L8 =

0 commit comments

Comments
 (0)