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

Clarify default constructed accessor and "require" #434

Merged

Conversation

gmlueck
Copy link
Contributor

@gmlueck gmlueck commented Jun 23, 2023

The previous wording caused some confusion about whether a default constructed accessor is a placeholder. In general, the accessor constructors that do not take a handler parameter construct placeholder accessors, which could give the impression that the default constructed accessor is a placeholder. Clarify this by specifically stating that the default constructed accessor is not a placeholder and that it may be passed as a kernel parameter without calling handler::require.

The default constructed accessor and the default constructed local_accessor are similar because they can both be passed as kernel parameters and neither allocates any memory for use in the kernel. Adjust the wording for these two constructors to be more similar.

Remove wording from handler::require saying that it is an error to call this function for a zero sized accessor. We allow non-placeholder accessor that are zero sized, so it makes no sense to disallow this for placeholder accessors.

Remove wording from the default accessor constructor saying that it is an error to pass such an accessor to handler::require. We allow other non-placeholder accessors to be passed to handler::require, so it makes sense to allow this for the default constructed accessor too.

Clarify the wording for accessor::is_placeholder. The previous wording "if the accessor was constructed as a placeholder" caused confusion. What if an accessor was constructed as a placeholder and then later assigned to a non-placeholder accessor (or swapped to a non-placeholder accessor)? Presumably, we want is_placeholder to return false in such a case. The new wording removes this confusing phrasing.

Closes #408.

The previous wording caused some confusion about whether a default
constructed `accessor` is a placeholder.  In general, the accessor
constructors that do not take a `handler` parameter construct
placeholder accessors, which could give the impression that the
default constructed accessor is a placeholder.  Clarify this by
specifically stating that the default constructed accessor is not a
placeholder and that it may be passed as a kernel parameter without
calling `handler::require`.

The default constructed `accessor` and the default constructed
`local_accessor` are similar because they can both be passed as
kernel parameters and neither allocates any memory for use in the
kernel.  Adjust the wording for these two constructors to be more
similar.

Remove wording from `handler::require` saying that it is an error to
call this function for a zero sized accessor.  We allow non-placeholder
accessor that are zero sized, so it makes no sense to disallow this for
placeholder accessors.

Remove wording from the default `accessor` constructor saying that it
is an error to pass such an accessor to `handler::require`.  We allow
other non-placeholder accessors to be passed to `handler::require`, so
it makes sense to allow this for the default constructed accessor too.

Clarify the wording for `accessor::is_placeholder`.  The previous
wording "if the accessor was constructed as a placeholder" caused
confusion.  What if an accessor was constructed as a placeholder and
then later assigned to a non-placeholder accessor (or swapped to a
non-placeholder accessor)?  Presumably, we want `is_placeholder` to
return `false` in such a case.  The new wording removes this confusing
phrasing.

Closes KhronosGroup#408.
@maarquitos14
Copy link
Contributor

Looks good to me. Only question is, requiring an empty accessor should create a dependency? Say we have an empty buffer, and we create a write accessor and a read accessor to it, should that create a write/read dependency or should we ignore it because it's empty?

@gmlueck
Copy link
Contributor Author

gmlueck commented Jun 26, 2023

Looks good to me. Only question is, requiring an empty accessor should create a dependency?

There are several ways to create an empty accessor, and I think the answer to your question depends on the way the accessor was created:

  • If the accessor was created from a ranged accessor with a zero-sized range, I think the spec is clear that there is a dependency on the entire buffer. Section 4.7.6.8 states that a ranged accessor creates a dependency on the entire buffer, and there is no exception for a zero-sized range. Therefore, this applies even to zero-size ranged accessors.

  • If the accessor was created from a zero-sized buffer, I think it still creates a dependency. Section 3.8.1 specifies how accessors create dependencies on buffers, and there is no exception here for zero-sized buffers. Therefore, I think there are dependencies even when the buffer has zero size.

  • If the accessor was created from the default constructor, there is no buffer at all, so it is impossible to create a dependency. This PR clarifies that default constructed accessors do not create requisites (i.e. dependencies). See the change in this PR to the section titled "SYCL functions for adding requirements".

Copy link
Member

@keryell keryell left a comment

Choose a reason for hiding this comment

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

Great subtle clarifications.

adoc/chapters/programming_interface.adoc Outdated Show resolved Hide resolved
adoc/chapters/programming_interface.adoc Show resolved Hide resolved
Avoid using the phrase "the underlying memory" for a default
constructed accessor because this implies that there is some
underlying memory.
Copy link
Member

@keryell keryell left a comment

Choose a reason for hiding this comment

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

Thanks, this is clearer.

@gmlueck
Copy link
Contributor Author

gmlueck commented Jul 13, 2023

Ping to the group .... looking for another reviewer on this.

steffenlarsen pushed a commit to intel/llvm that referenced this pull request Jul 14, 2023
…sed to a command (#10110)

Current mechanism for checking whether an exception should be thrown if
a placeholder accessor was passed to a command was not working properly
under certain conditions, either by not throwing when required, or
throwing when unrequired. Additionally, following clarifications in
KhronosGroup/SYCL-Docs#434, this PR makes
default-constructed accessors to stop being placeholders.

---------

Signed-off-by: Maronas, Marcos <marcos.maronas@intel.com>
Copy link
Collaborator

@AerialMantis AerialMantis left a comment

Choose a reason for hiding this comment

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

A few suggestions, but overall these changes look good to me.

One comment I have is I think we should also add wording to https://registry.khronos.org/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:data.access.and.storage to establish that zero-sized accessors are permitted, and perhaps add more general wording to the accessor section to describe the concept of a zero-sized accessor and the ways in which it can be created (i.e. zero-sized buffer, ranged accessor with size of zero or default constructor).

@@ -8195,7 +8193,9 @@ local_accessor()
* All size queries return [code]#0#.
* The return values of [code]#get_pointer()# and [code]#get_multi_ptr()# are
unspecified.
* Trying to access the underlying memory is undefined behavior.
* A default constructed local accessor can be passed to a
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* A default constructed local accessor can be passed to a
* A default constructed [code]#local_accessor# can be passed to a

adoc/chapters/programming_interface.adoc Outdated Show resolved Hide resolved

A default constructed accessor can be passed to a <<sycl-kernel-function>>
but it is not valid to register it with the command group handler.
* A default constructed accessor can be passed to a <<sycl-kernel-function>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
* A default constructed accessor can be passed to a <<sycl-kernel-function>>,
* A default constructed [code]#accessor# can be passed to a <<sycl-kernel-function>>,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you feel strongly about this, I don't mind adding the code-font. However, we do not seem to use code font for every occurrence of class names, especially the common ones like "queue", "buffer", and "accessor". For example, see the first sentence of this function description:

Constructs an empty accessor which fulfills the following post-conditions

(There is no code-font for "accessor" here.)

I've actually been trying to tone-down the amount of code font in cases like this because I think all the font changes are a little distracting. I don't really have a clear rule for when to use code font, but using it for every occurrence of a class name seems a bit too much.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't feel strongly about this, so I'm happy leaving this as-is, maybe it's worth having a separate discussion about general conventions for formatting?

Copy link
Member

Choose a reason for hiding this comment

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

In the past I think that I made some choice depending on whether it was either very important that it is about the sycl:: object or more vaguely the concept. Then we have the discussion about whether we should point to the glossary or not... :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the past I think that I made some choice depending on whether it was either very important that it is about the sycl:: object or more vaguely the concept.

Yes, this make sense.

Then we have the discussion about whether we should point to the glossary or not... :-)

Agreed that this is another point to consider. When writing new text, I've been trying to avoid excessive links to the glossary entries too. For example, the first occurrence of a glossary entry in a paragraph (or group of related paragraphs) can link to the glossary entry, but other occurrences of the glossary term in that same paragraph (or group of related paragraphs) does not need to be a link.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, I think this is generally a good convention to have. Maybe we should open a new issue for discussing this.

* Trying to access the underlying memory is undefined behavior.
* A default constructed local accessor can be passed to a
<<sycl-kernel-function>>, but attempting to access data elements from it
produces undefined behavior.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does somewhat create a foot gun, I think it's an acceptable side effect of allowing default constructed accessors to be passed to a kernel, and I don't think we can provide any stronger wording than saying it's UB without impacting implementations, however, we could potentially require that the value of the pointer for a zero-sized accessor is nullptr, to allow users to check for this case within kernel code (without having to query the size).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We discussed the question about nullptr already in #232, see this comment.

My opinion is pretty much the same as before. I think we should treat all zero-sized accessors the same in this regard (either say they all return nullptr or say they all return an unspecified value). It is not hard to guarantee the nullptr, but it does require extra logic in the implementation. For example, when constructing a ranged accessor, the implementation would need a special check to see if the range was zero. Guaranteeing a nullptr does not seem that useful to me. Applications still need to check that the index is within range of the accessor. If applications follow this rule, then they should never attempt to dereference the pointer for a zero-sized accessor. If applications do want to check for this special case, it seems easy enough to call accessor::empty.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I missed that discussion, I think that's a fair point, though I think there is still a use case for this, though admittedly pretty niche. If you had a kernel where an accessor is conditionally used, being left as default constructed if it's not, and when it is used each work-item only accesses its own index, and it's guaranteed to be the same size as the nd-range, then you may want to check if the accessor is zero-sized, without having to check the size (and potentially removing the implementation's ability to optimize away that kernel argument).

Though saying this I think while size() does require an argument to be passed to the kernel to provide this value, empty could be implemented based on whether the pointer is nullptr. So I think this is a problem that can be handled by the implementation, and so doesn't need to be in the specification.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rather than holding up this PR, lets create an issue to discuss whether a zero-sized accessor should be required to return nullptr from get_pointer. I think this is an orthogonal discussion from the clarification I'm making in this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree, I don't think we should hold up the PR for this, I'm comfortable that this is implementable with the current wording, so I think we can leave this as-is, also happy to discuss this further in a separate ticket.

adoc/chapters/programming_interface.adoc Outdated Show resolved Hide resolved
@gmlueck
Copy link
Contributor Author

gmlueck commented Jul 18, 2023

One comment I have is I think we should also add wording to https://registry.khronos.org/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:data.access.and.storage to establish that zero-sized accessors are permitted, and perhaps add more general wording to the accessor section to describe the concept of a zero-sized accessor and the ways in which it can be created (i.e. zero-sized buffer, ranged accessor with size of zero or default constructor).

How about expanding this existing paragraph in the introduction:

Zero-sized buffers are permitted. In this case, the buffer still handles ownership as normal, but does not need to store the zero-sized data itself.

The updated wording could be like this:

Zero-sized buffers and accessors are permitted, but attempting to access data within them produces undefined behavior, similar to dereferencing a null pointer in C++. Note that zero-sized accessors can be created in several ways: by creating an accessor from a zero-sized buffer, by creating an accessor with a zero-sized buffer sub-range, or by creating an accessor with its default constructor.

I intentionally dropped the last sentence here ("In this case, the buffer still handles ownership as normal, but does not need to store the zero-sized data itself.") because I don't think this specifies anything useful. What does it mean to say that the buffer "owns" zero-sized data? What do we mean by saying the ownership is "normal"? Is it illegal for an implementation to have a special case when the buffer has zero-size? In my opinion, this sentence does not add any clarity.

Use glossary term "sycl-kernel-function" and clarify that dereferencing
a zero-sized accessor is UB.
@AerialMantis
Copy link
Collaborator

One comment I have is I think we should also add wording to https://registry.khronos.org/SYCL/specs/sycl-2020/html/sycl-2020.html#sec:data.access.and.storage to establish that zero-sized accessors are permitted, and perhaps add more general wording to the accessor section to describe the concept of a zero-sized accessor and the ways in which it can be created (i.e. zero-sized buffer, ranged accessor with size of zero or default constructor).

How about expanding this existing paragraph in the introduction:

Zero-sized buffers are permitted. In this case, the buffer still handles ownership as normal, but does not need to store the zero-sized data itself.

The updated wording could be like this:

Zero-sized buffers and accessors are permitted, but attempting to access data within them produces undefined behavior, similar to dereferencing a null pointer in C++. Note that zero-sized accessors can be created in several ways: by creating an accessor from a zero-sized buffer, by creating an accessor with a zero-sized buffer sub-range, or by creating an accessor with its default constructor.

I intentionally dropped the last sentence here ("In this case, the buffer still handles ownership as normal, but does not need to store the zero-sized data itself.") because I don't think this specifies anything useful. What does it mean to say that the buffer "owns" zero-sized data? What do we mean by saying the ownership is "normal"? Is it illegal for an implementation to have a special case when the buffer has zero-size? In my opinion, this sentence does not add any clarity.

This wording sounds good to me, and I agree with the removal of the last sentence, if there's no data for the buffer to manage, then there's no ownership relationship to describe. Thanks.

Add a paragraph to the introduction of the section on buffers and
accessors stating that zero-sized accessors are legal.
@gmlueck
Copy link
Contributor Author

gmlueck commented Jul 20, 2023

This wording sounds good to me, and I agree with the removal of the last sentence, if there's no data for the buffer to manage, then there's no ownership relationship to describe. Thanks.

Done in d6654c0

@fraggamuffin
Copy link

LGTM

@keryell keryell merged commit 418c24a into KhronosGroup:SYCL-2020/master Jul 20, 2023
1 check passed
@gmlueck gmlueck deleted the gmlueck/require-accessor branch July 20, 2023 16:33
steffenlarsen added a commit to steffenlarsen/SYCL-CTS that referenced this pull request Aug 1, 2023
Following the removal of the require() exception condition in
KhronosGroup/SYCL-Docs#434 this commit removes
the corresponding test.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
steffenlarsen added a commit to steffenlarsen/SYCL-CTS that referenced this pull request Aug 2, 2023
Following the resolution of
KhronosGroup/SYCL-Docs#408 with
KhronosGroup/SYCL-Docs#434 DPC++ has been
changed to not throw for empty accessors. As such, the test cases using
check_zero_length_buffer_constructor can now be enabled.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
keryell added a commit that referenced this pull request Sep 10, 2024
Clarify default constructed accessor and "require".

The previous wording caused some confusion about whether a default constructed accessor is a placeholder. In general, the accessor constructors that do not take a handler parameter construct placeholder accessors, which could give the impression that the default constructed accessor is a placeholder. Clarify this by specifically stating that the default constructed accessor is not a placeholder and that it may be passed as a kernel parameter without calling handler::require.

The default constructed accessor and the default constructed local_accessor are similar because they can both be passed as kernel parameters and neither allocates any memory for use in the kernel. Adjust the wording for these two constructors to be more similar.

Remove wording from handler::require saying that it is an error to call this function for a zero sized accessor. We allow non-placeholder accessor that are zero sized, so it makes no sense to disallow this for placeholder accessors.

Remove wording from the default accessor constructor saying that it is an error to pass such an accessor to handler::require. We allow other non-placeholder accessors to be passed to handler::require, so it makes sense to allow this for the default constructed accessor too.

Clarify the wording for accessor::is_placeholder. The previous wording "if the accessor was constructed as a placeholder" caused confusion. What if an accessor was constructed as a placeholder and then later assigned to a non-placeholder accessor (or swapped to a non-placeholder accessor)? Presumably, we want is_placeholder to return false in such a case. The new wording removes this confusing phrasing.

Closes #408.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should accessors created from a command group handler throw if passed empty buffers?
5 participants