Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move description of shared_ptr buffer destructor #298

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 22 additions & 63 deletions adoc/chapters/programming_interface.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5029,20 +5029,30 @@ copy of data back to host.
When the buffer is destroyed, the destructor will block until all work
in queues on the buffer have completed.
--
. A buffer can be constructed using a [code]#shared_ptr# to host
data. This pointer is shared between the SYCL application and the
runtime. In order to allow synchronization between the application and
the runtime a [code]#mutex# is used which will be locked by the
runtime whenever the data is in use, and unlocked when it is no longer
needed.
. A buffer can be constructed using a [code]#std::shared_ptr# to host
data. This is similar to the case when the buffer is constructed using a
raw [code]#hostData# pointer, except that the SYCL runtime holds a
reference count to the [code]#shared_ptr# for the lifetime of the buffer.
Thus, the application can release its [code]#shared_ptr# even before the
buffer is destroyed. The buffer will use this host memory for its full
lifetime, but the contents of this host memory are unspecified for the
lifetime of the buffer. If the host memory is modified on the host or if
it is used to construct another buffer or image during the lifetime of this
buffer, then the results are undefined. The initial contents of the buffer
will be the contents of the host memory at the time of construction.
+
--
The [code]#shared_ptr# reference counting is used in order to prevent
destroying the buffer host data prematurely. If the [code]#shared_ptr#
is deleted from the user application before buffer destruction, the buffer
can continue securely because the pointer hasn't been destroyed yet. It will
not copy data back to the host before destruction, however, as the
application side has already deleted its copy.
When the buffer is destroyed, the destructor is guaranteed to block if the
application still holds a reference count to the [code]#shared_ptr# at the
point when the destructor runs. In this case, the destructor will block until
all work in queues on the buffer have completed. The buffer destructor may
also copy-back data to host memory. This happens only if the application still
holds a reference count to the [code]#shared_ptr# and only if it is otherwise
necessary to copy this data back.

If the underlying type of the [code]#shared_ptr# is [code]#const#, then the
buffer is read-only; only read accessors are allowed on the buffer and no
copy-back to host memory is performed.

Note that since there is an implicit conversion of a
[code]#std::unique_ptr# to a [code]#std::shared_ptr#, a
Expand Down Expand Up @@ -6057,57 +6067,6 @@ buffer construction.
----


==== Shared SYCL ownership of the host memory

When an instance of [code]#std::shared_ptr# is passed to the buffer
constructor, then the buffer object and the developer's application share
the memory region. If the shared pointer is still used on the application's
side then the data will be copied back from the buffer or image and will be
available to the application after the buffer or image is destroyed.

If the [code]#shared_ptr# is not empty, the contents of the referenced
memory are used to initialize the buffer. If the [code]#shared_ptr# is
empty, then the buffer is created with uninitialized memory.

When the buffer is destroyed and the data have potentially been updated, if
the number of copies of the shared pointer outside the runtime is 0, there
is no user-side shared pointer to read the data. Therefore the data is not
copied out, and the buffer destructor does not need to wait for the data
processes to be finished, as the outcome is not needed on the application's
side.

This behavior can be overridden using the [code]#set_final_data()#
member function of the buffer class, which will by any means force the buffer
destructor to wait until the data is copied to wherever the
[code]#set_final_data()# member function has put the data (or not wait nor copy
if set final data is [code]#nullptr)#.

[source,,linenums]
----
{
std::shared_ptr<int> ptr { data };
{
buffer<int, 1> b { ptr, range<2>{ 10, 10 } };
// update the data
[...]
} // Data is copied back because there is an user side shared_ptr
}
----

[source,,linenums]
----
{
std::shared_ptr<int> ptr { data };
{
buffer<int, 1> b { ptr, range<2>{ 10, 10 } };
// update the data
[...]
ptr.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line require a comment, for casual readers not using routinely std::shared_ptr.

} // Data is not copied back, there is no user side shared_ptr.
}
----


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep these examples?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have checked again : these were the only 2 code examples showing sycl::buffer and std::shared_ptr. Why removing them? Are they wrong now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the examples are good to keep. I was hoping to move all of the normative description of the buffer APIs to section 4.7.2 Buffers, and in particular the normative description of the blocking behavior and copy-back behavior to section 4.7.2.3 Buffer synchronization rules.

How do you feel about the following ... I'll still augment section 4.7.2.3 to contain a complete description of the blocking behavior and copy-back behavior, so all of that information is together in one place. We can still keep section 4.7.4, though, as a set of examples. In this case, I'd probably retitle the section to something like "Buffer examples", and the introduction of that section will read something like:

This section provides some examples showing typical use cases for buffer. These examples are intended to clarify the definition of the buffer interfaces, but the content of this section is nonnormative.

Over time, we can add more examples to this section to clarify other behavior about buffers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I restored the examples, and converted section 4.7.4 to an examples section as I describe in the comment above. See commit 15e8eab.

Note that this PR is incomplete at this point because I need answers to the questions in (https://gitlab.khronos.org/sycl/Specification/-/issues/478)

[[subsec:mutex]]
=== Synchronization primitives

Expand Down