Skip to content

Commit e6e45d0

Browse files
[SYCL][Docs] Allow copy-construction of device_global (#15075)
This commit makes it possible to copy-construct device_global variables if they do not have the device_image_scope property. The restriction on device_image_scope is due to static construction not being allowed in device code, which they would require, while other device_globals have USM storage which will be initialized by the host code, so the constructor on the device is a simple zero-initialization. --------- Signed-off-by: Larsen, Steffen <[email protected]> Co-authored-by: John Pennycook <[email protected]>
1 parent 5d5ec9e commit e6e45d0

File tree

4 files changed

+210
-6
lines changed

4 files changed

+210
-6
lines changed

sycl/doc/extensions/experimental/sycl_ext_oneapi_device_global.asciidoc

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ public:
232232
233233
// device_global initializes underlying T with the args argument
234234
#if __cpp_consteval
235+
// Available only if sizeof...(Args) > 1 or the one argument in args is not a
236+
// device_global.
235237
template <typename... Args>
236238
consteval explicit device_global(Args&&... args);
237239
#else
@@ -244,7 +246,14 @@ public:
244246
device_global() = default;
245247
#endif // __cpp_consteval
246248
247-
device_global(const device_global &) = delete;
249+
// Available if PropertyListT::has_property<device_image_scope_key>() is false.
250+
constexpr device_global(const device_global &other);
251+
252+
// Available if PropertyListT::has_property<device_image_scope_key>() is false
253+
// and OtherT is convertible to T.
254+
template <typename OtherT, typename OtherProps>
255+
constexpr device_global(const device_global<OtherT, OtherProps> &other);
256+
248257
device_global(const device_global &&) = delete;
249258
device_global &operator=(const device_global &) = delete;
250259
device_global &operator=(const device_global &&) = delete;
@@ -318,12 +327,42 @@ template <typename... Args>
318327
consteval explicit device_global(Args&&... args);
319328
----
320329
|
330+
Available only if sizeof...(Args) != 1 or the one argument in args is not a device_global.
331+
321332
Constructs a `device_global` object, and implicit storage for `T` in the global address space on each device that may access it.
322333

323334
The object of type `T` is initialized from the `args` parameter pack using list initialization as defined in the {cpp} specification.
324335

325336
`T` must be trivially destructible.
326337

338+
// --- ROW BREAK ---
339+
a|
340+
[source,c++]
341+
----
342+
constexpr device_global(const device_global &other);
343+
----
344+
|
345+
Available if `PropertyListT::has_property<device_image_scope_key>() == false`.
346+
347+
Constructs a `device_global` object, and implicit storage for `T` in the global address space on each device that may access it.
348+
349+
The storage on each device for `T` is initialized with a copy of the initial value of `other`. The behavior is undefined if `other` has been written to prior to a call to this constructor.
350+
351+
// --- ROW BREAK ---
352+
a|
353+
[source,c++]
354+
----
355+
template <typename OtherT, typename OtherProps>
356+
constexpr device_global(const device_global<OtherT, OtherProps> &other);
357+
----
358+
|
359+
Available if `PropertyListT::has_property<device_image_scope_key>() == false` and
360+
`std::is_convertible_v<OtherT, T> == true`;
361+
362+
Constructs a `device_global` object, and implicit storage for `T` in the global address space on each device that may access it.
363+
364+
The storage on each device for `T` is initialized with a copy of the initial value of `other`. The behavior is undefined if `other` has been written to prior to a call to this constructor.
365+
327366
// --- ROW BREAK ---
328367
a|
329368
[source,c++]

sycl/include/sycl/ext/oneapi/device_global/device_global.hpp

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ namespace sycl {
4040
inline namespace _V1 {
4141
namespace ext::oneapi::experimental {
4242

43+
template <typename T, typename PropertyListT> class device_global;
44+
4345
namespace detail {
4446
// Type-trait for checking if a type defines `operator->`.
4547
template <typename T, typename = void>
@@ -49,6 +51,20 @@ struct HasArrowOperator<T,
4951
std::void_t<decltype(std::declval<T>().operator->())>>
5052
: std::true_type {};
5153

54+
template <typename T, typename PropertyListT, typename>
55+
class device_global_base;
56+
57+
// Checks that T is a reference to either device_global or
58+
// device_global_base. This is used by the variadic ctor to allow copy ctors to
59+
// take preference.
60+
template <typename T> struct IsDeviceGlobalOrBaseRef : std::false_type {};
61+
template <typename T, typename PropertyListT>
62+
struct IsDeviceGlobalOrBaseRef<device_global_base<T, PropertyListT, void> &>
63+
: std::true_type {};
64+
template <typename T, typename PropertyListT>
65+
struct IsDeviceGlobalOrBaseRef<device_global<T, PropertyListT> &>
66+
: std::true_type {};
67+
5268
// Base class for device_global.
5369
template <typename T, typename PropertyListT, typename = void>
5470
class device_global_base {
@@ -63,14 +79,49 @@ class device_global_base {
6379
pointer_t get_ptr() noexcept { return usmptr; }
6480
pointer_t get_ptr() const noexcept { return usmptr; }
6581

82+
template <typename, typename, typename> friend class device_global_base;
83+
84+
#ifndef __SYCL_DEVICE_ONLY__
85+
template <typename OtherT, typename OtherProps>
86+
static constexpr const T &
87+
ExtractInitialVal(const device_global_base<OtherT, OtherProps> &Other) {
88+
if constexpr (OtherProps::template has_property<device_image_scope_key>())
89+
return Other.val;
90+
else
91+
return Other.init_val;
92+
}
93+
#endif // __SYCL_DEVICE_ONLY__
94+
6695
public:
6796
#if __cpp_consteval
68-
template <typename... Args>
97+
// The SFINAE is to allow the copy constructors to take priority.
98+
template <
99+
typename... Args,
100+
std::enable_if_t<
101+
sizeof...(Args) != 1 ||
102+
(!IsDeviceGlobalOrBaseRef<std::remove_cv_t<Args>>::value && ...),
103+
int> = 0>
69104
consteval explicit device_global_base(Args &&...args) : init_val{args...} {}
70105
#else
71106
device_global_base() = default;
72107
#endif // __cpp_consteval
73108

109+
#ifndef __SYCL_DEVICE_ONLY__
110+
template <typename OtherT, typename OtherProps,
111+
typename = std::enable_if_t<std::is_convertible_v<OtherT, T>>>
112+
constexpr device_global_base(
113+
const device_global_base<OtherT, OtherProps> &DGB)
114+
: init_val{ExtractInitialVal(DGB)} {}
115+
constexpr device_global_base(const device_global_base &DGB)
116+
: init_val{DGB.init_val} {}
117+
#else
118+
template <typename OtherT, typename OtherProps,
119+
typename = std::enable_if_t<std::is_convertible_v<OtherT, T>>>
120+
constexpr device_global_base(const device_global_base<OtherT, OtherProps> &) {
121+
}
122+
constexpr device_global_base(const device_global_base &) {}
123+
#endif // __SYCL_DEVICE_ONLY__
124+
74125
template <access::decorated IsDecorated>
75126
multi_ptr<T, access::address_space::global_space, IsDecorated>
76127
get_multi_ptr() noexcept {
@@ -100,14 +151,28 @@ class device_global_base<
100151
T *get_ptr() noexcept { return &val; }
101152
const T *get_ptr() const noexcept { return &val; }
102153

154+
template <typename, typename, typename> friend class device_global_base;
155+
103156
public:
104157
#if __cpp_consteval
105-
template <typename... Args>
158+
// The SFINAE is to allow the copy constructors to take priority.
159+
template <
160+
typename... Args,
161+
std::enable_if_t<
162+
sizeof...(Args) != 1 ||
163+
(!IsDeviceGlobalOrBaseRef<std::remove_cv_t<Args>>::value && ...),
164+
int> = 0>
106165
consteval explicit device_global_base(Args &&...args) : val{args...} {}
107166
#else
108167
device_global_base() = default;
109168
#endif // __cpp_consteval
110169

170+
template <typename OtherT, typename OtherProps,
171+
typename = std::enable_if_t<std::is_convertible_v<OtherT, T>>>
172+
constexpr device_global_base(const device_global_base<OtherT, OtherProps> &) =
173+
delete;
174+
constexpr device_global_base(const device_global_base &) = delete;
175+
111176
template <access::decorated IsDecorated>
112177
multi_ptr<T, access::address_space::global_space, IsDecorated>
113178
get_multi_ptr() noexcept {
@@ -124,6 +189,7 @@ class device_global_base<
124189
const T>(this->get_ptr());
125190
}
126191
};
192+
127193
} // namespace detail
128194

129195
template <typename T, typename PropertyListT = empty_properties_t>
@@ -151,6 +217,7 @@ class
151217
: public detail::device_global_base<T, detail::properties_t<Props...>> {
152218

153219
using property_list_t = detail::properties_t<Props...>;
220+
using base_t = detail::device_global_base<T, property_list_t>;
154221

155222
public:
156223
using element_type = std::remove_extent_t<T>;
@@ -167,10 +234,11 @@ class
167234
"Property list is invalid.");
168235

169236
// Inherit the base class' constructors
170-
using detail::device_global_base<
171-
T, detail::properties_t<Props...>>::device_global_base;
237+
using detail::device_global_base<T, property_list_t>::device_global_base;
238+
239+
constexpr device_global(const device_global &DG)
240+
: base_t(static_cast<const base_t &>(DG)) {}
172241

173-
device_global(const device_global &) = delete;
174242
device_global(const device_global &&) = delete;
175243
device_global &operator=(const device_global &) = delete;
176244
device_global &operator=(const device_global &&) = delete;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// RUN: %{build} -std=c++23 -o %t.out
2+
// RUN: %{run} %t.out
3+
//
4+
// UNSUPPORTED: opencl && gpu
5+
// UNSUPPORTED-TRACKER: GSD-4287
6+
//
7+
// Tests the copy ctor on device_global without device_image_scope.
8+
9+
#include <sycl/detail/core.hpp>
10+
11+
namespace oneapiext = sycl::ext::oneapi::experimental;
12+
13+
oneapiext::device_global<const int> DGInit1{3};
14+
oneapiext::device_global<const int> DGCopy1{DGInit1};
15+
16+
oneapiext::device_global<int> DGInit2{4};
17+
oneapiext::device_global<int> DGCopy2{DGInit2};
18+
19+
oneapiext::device_global<float> DGInit3{5.0f};
20+
oneapiext::device_global<int> DGCopy3{DGInit3};
21+
22+
oneapiext::device_global<const int, decltype(oneapiext::properties{
23+
oneapiext::device_image_scope})>
24+
DGInit4{6};
25+
oneapiext::device_global<const int> DGCopy4{DGInit4};
26+
27+
oneapiext::device_global<const int> DGInit5{7};
28+
oneapiext::device_global<const int, decltype(oneapiext::properties{
29+
oneapiext::host_access_read})>
30+
DGCopy5{DGInit5};
31+
32+
int main() {
33+
sycl::queue Q;
34+
35+
int ReadVals[10] = {0, 0};
36+
{
37+
sycl::buffer<int, 1> ReadValsBuff{ReadVals, 10};
38+
39+
Q.submit([&](sycl::handler &CGH) {
40+
sycl::accessor ReadValsAcc{ReadValsBuff, CGH, sycl::write_only};
41+
CGH.single_task([=]() {
42+
ReadValsAcc[0] = DGInit1.get();
43+
ReadValsAcc[1] = DGCopy1.get();
44+
ReadValsAcc[2] = DGInit2.get();
45+
ReadValsAcc[3] = DGCopy2.get();
46+
ReadValsAcc[4] = DGInit3.get();
47+
ReadValsAcc[5] = DGCopy3.get();
48+
ReadValsAcc[6] = DGInit4.get();
49+
ReadValsAcc[7] = DGCopy4.get();
50+
ReadValsAcc[8] = DGInit5.get();
51+
ReadValsAcc[9] = DGCopy5.get();
52+
});
53+
}).wait_and_throw();
54+
}
55+
56+
assert(ReadVals[0] == 3);
57+
assert(ReadVals[1] == 3);
58+
assert(ReadVals[2] == 4);
59+
assert(ReadVals[3] == 4);
60+
assert(ReadVals[4] == 5);
61+
assert(ReadVals[5] == 5);
62+
assert(ReadVals[6] == 6);
63+
assert(ReadVals[7] == 6);
64+
assert(ReadVals[8] == 7);
65+
assert(ReadVals[9] == 7);
66+
67+
return 0;
68+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %clangxx -std=c++23 -fsycl -fsycl-targets=%sycl_triple -fsyntax-only -Xclang -verify -Xclang -verify-ignore-unexpected=note %s
2+
//
3+
// Tests that the copy ctor on device_global with device_image_scope is
4+
// unavailable.
5+
6+
#include <sycl/sycl.hpp>
7+
8+
namespace oneapiext = sycl::ext::oneapi::experimental;
9+
10+
using device_image_properties =
11+
decltype(oneapiext::properties{oneapiext::device_image_scope});
12+
13+
// expected-error@sycl/ext/oneapi/device_global/device_global.hpp:* {{call to deleted constructor}}
14+
oneapiext::device_global<const int, device_image_properties> DGInit1{3};
15+
oneapiext::device_global<const int, device_image_properties> DGCopy1{DGInit1};
16+
17+
// expected-error@sycl/ext/oneapi/device_global/device_global.hpp:* {{call to deleted constructor}}
18+
oneapiext::device_global<int, device_image_properties> DGInit2{3};
19+
oneapiext::device_global<int, device_image_properties> DGCopy2{DGInit2};
20+
21+
// expected-error@+2 {{call to deleted constructor}}
22+
oneapiext::device_global<int, device_image_properties> DGInit3{3};
23+
oneapiext::device_global<float, device_image_properties> DGCopy3{DGInit3};
24+
25+
// expected-error@+2 {{call to deleted constructor}}
26+
oneapiext::device_global<const int> DGInit4{3};
27+
oneapiext::device_global<const int, device_image_properties> DGCopy4{DGInit4};
28+
29+
int main() { return 0; }

0 commit comments

Comments
 (0)