SYCL buffers have the fun effect where when they are destroyed they may write back into the host memory from which they were formed. This is specified in 3.9.8.1 of the sycl 2020 standard:
Buffer destruction: The destructors for sycl::buffer, sycl::unsampled_image and sycl::sampled_image objects wait for all submitted work on those objects to complete and to copy the data back to host memory before returning. These destructors only wait if the object was constructed with attached host memory and if data needs to be copied back to the host.
sycl::buffer, has many constructors:
buffer(const sycl::range<dimensions> &bufferRange,
const sycl::property_list &propList = {});
...
buffer(T *hostData, const sycl::range<dimensions> &bufferRange,
AllocatorT allocator, const sycl::property_list &propList = {});
buffer(const T *hostData, const sycl::range<dimensions> &bufferRange,
const sycl::property_list &propList = {});
buffer(const T *hostData, const sycl::range<dimensions> &bufferRange,
AllocatorT allocator, const sycl::property_list &propList = {});
buffer(const shared_ptr_class<T> &hostData,
const sycl::range<dimensions> &bufferRange, AllocatorT allocator,
const sycl::property_list &propList = {});
...
template <class InputIterator>
buffer<T, 1>(InputIterator first, InputIterator last, AllocatorT allocator,
const sycl::property_list &propList = {});
template <class InputIterator>
buffer<T, 1>(InputIterator first, InputIterator last,
const sycl::property_list &propList = {});
buffer(cl_mem clMemObject, const sycl::context &syclContext,
event availableEvent = {});
But it does not specify directly, which ones do the copy on destruction method. For example, the iterator constructor, could be used with a range:
std::vector<int> some_nums;
// .. Fill the vector
auto values = some_nums | ranges::filter([](int v) { return v % 2};
sycl::buffer<int, 1> buf{std::begin(values), std::end(values)};
This could be used to fill the buffer with all odd values. But if on buffer destruction the sycl subsystem attempts to write back to the range, this would be disastrous.
How do we know which constructors cause this write to host on destruction?
Buffers are containers for data that can be read/written by both kernel and host. The destructor for a buffer can optionally write the data back to host memory, either by pointer or iterator. We can control the write-back of data using set_final_data() and set_write_back().
All the below buffer constructors can be used to write back to the host on destruction:
buffer(T hostData, const sycl::range<dimensions> &bufferRange,
const sycl::property_list &propList = {});
buffer(T *hostData, const sycl::range<dimensions> &bufferRange,
AllocatorT allocator, const sycl::property_list &propList = {});
buffer(const T *hostData, const sycl::range<dimensions> &bufferRange,
const sycl::property_list &propList = {});
buffer(const T *hostData, const sycl::range<dimensions> &bufferRange,
AllocatorT allocator, const sycl::property_list &propList = {});
buffer(const shared_ptr_class<T> &hostData,
const sycl::range<dimensions> &bufferRange, AllocatorT allocator,
const sycl::property_list &propList = {});
buffer(const shared_ptr_class<T> &hostData,
const sycl::range<dimensions> &bufferRange,
const sycl::property_list &propList = {});
buffer(buffer<T, dimensions, AllocatorT> b, const id<dimensions>
&baseIndex,
const sycl::range<dimensions> &subRange);
*Available only when:
dimensions == 1
template <class InputIterator>
buffer<T, 1>(InputIterator first, InputIterator last, AllocatorT allocator,
const sycl::property_list &propList = {});
template <class InputIterator>
buffer<T, 1>(InputIterator first, InputIterator last,
const sycl::property_list &propList = {});
buffer(cl_mem clMemObject, const sycl::context &syclContext,
event availableEvent = {});