Skip to content

[SYCL][Docs] Add sycl_ext_oneapi_weak_object extension and implementation #7508

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

Merged
merged 17 commits into from
Dec 5, 2022

Conversation

steffenlarsen
Copy link
Contributor

This commit add the sycl_ext_oneapi_weak_object extension together with its implementation. This extension intendeds to allow users to weak variants of SYCL objects with common reference semantics, similar to how std::weak_ptr works with std::shared_ptr. Among other benefits, this allows user code to store these SYCL objects without worrying about the storage keeping the objects alive for longer than needed.

Additionally the extension adds the notion of owner ordering of SYCL objects. This can be used as less-than comparison between SYCL objects and their corresponding weak variants.

…tion

This commit add the sycl_ext_oneapi_weak_object extension together with
its implementation. This extension intendeds to allow users to weak
variants of SYCL objects with common reference semantics, similar to how
std::weak_ptr works with std::shared_ptr. Among other benefits, this
allows user code to store these SYCL objects without worrying about the
storage keeping the objects alive for longer than needed.

Additionally the extension adds the notion of owner ordering of SYCL
objects. This can be used as less-than comparison between SYCL objects
and their corresponding weak variants.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
@steffenlarsen steffenlarsen requested a review from a team November 23, 2022 13:55
@steffenlarsen steffenlarsen requested review from a team as code owners November 23, 2022 13:55
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
@aelovikov-intel
Copy link
Contributor

I've briefly looked through some files where I'd expect to find the implementation details documentation (like weak_object[_base}.hpp, etc.) but haven't found anything looking close enough. Can you please add some high-level description of how that is supposed to work (i.e., where the counters are located, what happens when new objects are created, etc.)?

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
@steffenlarsen
Copy link
Contributor Author

I've briefly looked through some files where I'd expect to find the implementation details documentation (like weak_object[_base}.hpp, etc.) but haven't found anything looking close enough. Can you please add some high-level description of how that is supposed to work (i.e., where the counters are located, what happens when new objects are created, etc.)?

I have added some notes. Since we use std::shared_ptr with implementation classes to do common reference semantics, the implementation of weak_object is mostly just a wrapper around a std::weak_ptr created from these. There are some deviations from the behavior of std::weak_ptr as we do not have the notion of empty SYCL objects, so lock will throw instead.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Copy link
Contributor

@gmlueck gmlueck left a comment

Choose a reason for hiding this comment

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

Spec LGTM.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Comment on lines +344 to +349
sycl::accessor<int, 1, sycl::access::mode::read> DAcc1D1{Buf1D1, CGH};
sycl::accessor<int, 1, sycl::access::mode::read> DAcc1D2{Buf1D2, CGH};
sycl::accessor<int, 2, sycl::access::mode::read> DAcc2D1{Buf2D1, CGH};
sycl::accessor<int, 2, sycl::access::mode::read> DAcc2D2{Buf2D2, CGH};
sycl::accessor<int, 3, sycl::access::mode::read> DAcc3D1{Buf3D1, CGH};
sycl::accessor<int, 3, sycl::access::mode::read> DAcc3D2{Buf3D2, CGH};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
sycl::accessor<int, 1, sycl::access::mode::read> DAcc1D1{Buf1D1, CGH};
sycl::accessor<int, 1, sycl::access::mode::read> DAcc1D2{Buf1D2, CGH};
sycl::accessor<int, 2, sycl::access::mode::read> DAcc2D1{Buf2D1, CGH};
sycl::accessor<int, 2, sycl::access::mode::read> DAcc2D2{Buf2D2, CGH};
sycl::accessor<int, 3, sycl::access::mode::read> DAcc3D1{Buf3D1, CGH};
sycl::accessor<int, 3, sycl::access::mode::read> DAcc3D2{Buf3D2, CGH};
sycl::accessor DAcc1D1{Buf1D1, CGH};
sycl::accessor DAcc1D2{Buf1D2, CGH};
sycl::accessor DAcc2D1{Buf2D1, CGH};
sycl::accessor DAcc2D2{Buf2D2, CGH};
sycl::accessor DAcc3D1{Buf3D1, CGH};
sycl::accessor DAcc3D2{Buf3D2, CGH};

Maybe with an extra sycl::read_only (is that the right spelling?) param.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like unittests don't currently like the deduction guides, seemingly due to a problem with IsValidTag and IsSameAsBuffer. I cannot reproduce with an actual SYCL program, so I suspect it may have something to do with how the unittests are built and/or potentially a bug in the host compiler. I have a fix but I think it would be better to do in isolation as a follow-up patch.

Comment on lines +286 to +289
sycl::accessor<int, 1, sycl::access::mode::read_write,
sycl::access::target::device,
sycl::access::placeholder::true_t>
PAcc1D1{Buf1D1};
Copy link
Contributor

Choose a reason for hiding this comment

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

AFAIK sycl::access::placeholder::true_t is deprecated, we can use normal CTAD like

sycl::accessor PAcc1D1{Buf1D1};

to create a placeholder accessor.

Copy link
Contributor Author

@steffenlarsen steffenlarsen Dec 2, 2022

Choose a reason for hiding this comment

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

This has the same problem as #7508 (comment). I will address it separately.

Comment on lines +53 to +88
template <>
struct owner_less<context> : public detail::owner_less_base<context> {};
template <>
struct owner_less<device> : public detail::owner_less_base<device> {};
template <> struct owner_less<event> : public detail::owner_less_base<event> {};
template <>
struct owner_less<kernel> : public detail::owner_less_base<kernel> {};
template <>
struct owner_less<kernel_id> : public detail::owner_less_base<kernel_id> {};
template <>
struct owner_less<platform> : public detail::owner_less_base<platform> {};
template <> struct owner_less<queue> : public detail::owner_less_base<queue> {};

template <bundle_state State>
struct owner_less<device_image<State>>
: public detail::owner_less_base<device_image<State>> {};

template <bundle_state State>
struct owner_less<kernel_bundle<State>>
: public detail::owner_less_base<kernel_bundle<State>> {};

template <typename DataT, int Dimensions, typename AllocatorT>
struct owner_less<buffer<DataT, Dimensions, AllocatorT>>
: public detail::owner_less_base<buffer<DataT, Dimensions, AllocatorT>> {};

template <typename DataT, int Dimensions, access_mode AccessMode,
target AccessTarget, access::placeholder isPlaceholder>
struct owner_less<
accessor<DataT, Dimensions, AccessMode, AccessTarget, isPlaceholder>>
: public detail::owner_less_base<accessor<DataT, Dimensions, AccessMode,
AccessTarget, isPlaceholder>> {};

template <typename DataT, int Dimensions, access_mode AccessMode>
struct owner_less<host_accessor<DataT, Dimensions, AccessMode>>
: public detail::owner_less_base<
host_accessor<DataT, Dimensions, AccessMode>> {};
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, feel free to ignore.

I'd rather see something like

namespace detail {
template <typename T>
struct owner_less_unsupport {
static_assert(always_false_v<T>);
}
}

template <typename T>
struct owner_less : public std::conditional<....,
                                            detail::owner_less_base<T>,
                                            detail::owner_less_unsupported<T>> {};

using some utilities like

template <typename T> struct is_accessor : std::false_type {};
template <typename DataT, int Dimensions, access::mode AccessMode,
access::target AccessTarget, access::placeholder IsPlaceholder,
typename PropertyListT>
struct is_accessor<accessor<DataT, Dimensions, AccessMode, AccessTarget,
IsPlaceholder, PropertyListT>> : std::true_type {};
template <typename T> struct is_host_accessor : std::false_type {};
template <typename DataT, int Dimensions, access::mode AccessMode>
struct is_host_accessor<host_accessor<DataT, Dimensions, AccessMode>>
for the condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So instead of having a handful specializations we would have a generic class with checks for all the different classes we should support? Since we would still need to add new is_X traits for new classes and add them here, I am not sure I see the benefit to that approach.

Copy link
Contributor

@aelovikov-intel aelovikov-intel Dec 2, 2022

Choose a reason for hiding this comment

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

I have a feeling that I see the same pattern in multiple places. As such, new is_X cost would be spread over multiple places (maybe not now, but soon enough). For example, is_*access* already has other usage - the link above.

a generic class with checks for all the different classes we should support

That somehow sounds much heavier than it would be like.

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 suppose for the ones with multiple template arguments this approach would make it cleaner, but I am reluctant to add these helpers as part of this PR as it is already fairly big. I will try and do some refactoring after this is merged.

@@ -0,0 +1,66 @@
//==------- weak_object_base.hpp --- SYCL weak objects base class ----------==//
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I'd prefer not having this separate file, but up to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is needed because we specialize weak_object for buffer and that would either require us to create a source file or create a circular dependency, so this seemed like the simpler path.

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
Copy link
Contributor

@aelovikov-intel aelovikov-intel left a comment

Choose a reason for hiding this comment

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

All major concerns have been addressed! Remaining nits are up to the author's judgement.

Copy link
Contributor

@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.

Interesting concept.
Perhaps weak_ref is more precise?
In the C++ standard there is also reference_wrapper but it seems less suitable for weak_wrapper for SYCL.

@steffenlarsen
Copy link
Contributor Author

Interesting concept. Perhaps weak_ref is more precise? In the C++ standard there is also reference_wrapper but it seems less suitable for weak_wrapper for SYCL.

I might worry that the name ref could make it look like it should act like a reference and make the underlying object directly accessible. weak_object fits the bill because it is a "weak" copy of the SYCL object, which do to the common reference semantics makes it a reference to the same object. That said, it's not a strong preference for one or the other. @gmlueck - Do you have a preference?

Signed-off-by: Larsen, Steffen <steffen.larsen@intel.com>
@gmlueck
Copy link
Contributor

gmlueck commented Dec 2, 2022

Do you have a preference?

I do not have a strong preference. I think I have a weak preference for weak_object, but it is a very weak preference.

@keryell
Copy link
Contributor

keryell commented Dec 2, 2022

It works like a weak_ptr but you do not use a * with it. So this is why this makes me think to a reference. :-)
But whatever is fine, since it is an extension.
@gmlueck Do you propose weak_preference? ;-)

@steffenlarsen
Copy link
Contributor Author

In that case I will make the executive decision and stick with weak_object.

HIP failures are known (#7602 (comment)).

@steffenlarsen steffenlarsen merged commit d948427 into intel:sycl Dec 5, 2022
@steffenlarsen
Copy link
Contributor Author

I am investigating the Windows post-commit failure.

@steffenlarsen
Copy link
Contributor Author

Windows post-commit fix: #7642

whitneywhtsang added a commit to whitneywhtsang/llvm that referenced this pull request Dec 5, 2022
whitneywhtsang added a commit to whitneywhtsang/llvm that referenced this pull request Dec 6, 2022
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.

4 participants