-
Notifications
You must be signed in to change notification settings - Fork 754
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
[RFC][POC] Suggest re-design of compile-time properties extension #15752
Conversation
* merge ctor * SFINAE on ctors/CTAD rules * more tests
Used to fail with gcc
29e753a
to
9785c26
Compare
int x = 42; | ||
properties pl{ct_prop<42>{}, rt_prop{x}}; | ||
constexpr auto p = pl.get_property<struct ct_prop_key>(); | ||
static_assert(std::is_same_v<decltype(p), const ct_prop<42>>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if const ct_prop<42>
instead of prop<42>
is an issue, and if it is then I don't know how to address it.
9785c26
to
47595b0
Compare
47595b0
to
b449892
Compare
2a11218
to
c74fe5d
Compare
foo(+prop<42>); | ||
// More than one property in a property list looks very natural. | ||
// Alternatively, that can be `operator|` but it has no unary version. | ||
foo(prop<42> + other_prop); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want to provide a simplified user interface like this, but it's unclear what's the best way to implement this. This file sketches few possible approaches starting from here.
sycl/include/sycl/kernel_bundle.hpp
Outdated
template <typename property_list_ty> | ||
inline constexpr bool are_properties_valid_for_create_bundle_from_source = | ||
new_properties::all_properties_in_v<property_list_ty, include_files>; | ||
|
||
template <typename property_list_ty> | ||
inline constexpr bool are_properties_valid_for_build_source_bundle = | ||
new_properties::all_properties_in_v<property_list_ty, build_options, | ||
save_log, registered_kernel_names>; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is subjective, but I think this is better than having each property specialize is_property_key_of
as all the supported properties of something are listed in one place.
foo(properties{prop<42>{}}); | ||
foo(properties{prop<42>{}, other_prop{}}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the simplicity of this. I think the old approach required us to define too many things with the same/similar names, and so if we can avoid having to define the shortcuts that's probably better for everybody.
Another benefit of exposing that these property values are objects is that developers will naturally have a better understanding of the difference between properties with compile-time and run-time values:
prop<42>{}
is clearly an instance of a class storing 42 as a compile-time valueprop(42)
is clearly an instance of a class storing 42 as a run-time value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't agree. I think it's still clear that prop<42>
is a compile-time property (without the {}
), and I like that it's two characters shorter.
Also, I think the inline constexpr values are a verbosity saver for compile-time properties that have an enumeration. Consider the host_acess
property from sycl_ext_oneapi_device_global:
properties props{host_access_read}; // with inline constexpr variable
properties props{host_access<host_access_enum::read>{}}; // without
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't agree. I think it's still clear that prop<42> is a compile-time property (without the {}), and I like that it's two characters shorter.
It's shorter, but it's inconsistent. In the situation where prop
supports both compile-time and run-time values, the compile-time value would be used as prop<42>
and the run-time value would be used as prop_property(42)
. Pairing prop<42>{}
and prop(42)
is more consistent, and we don't require developers to learn any additional naming conventions.
Also, I think the inline constexpr values are a verbosity saver for compile-time properties that have an enumeration. Consider the host_acess property from [sycl_ext_oneapi_device_global]> (https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_device_global.asciidoc)
I'm not opposed to creating shorthand aliases for compile-time properties with an enumeration. But we can have a shorthand and still expose everything as a type -- we'd just have to use a type alias instead of a variable:
using host_access_read = host_access<host_access_enum::read>;
...
properties props{host_access_read{}}; // with shorthand
properties props{host_access<host_access_enum::read>{}}; // without shorthand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's shorter, but it's inconsistent. In the situation where
prop
supports both compile-time and run-time values, the compile-time value would be used asprop<42>
and the run-time value would be used asprop_property(42)
. Pairingprop<42>{}
andprop(42)
is more consistent, and we don't require developers to learn any additional naming conventions.
We had this discussion before. There are no properties today that mix compile-time and runtime values, and we don't have any immediate plans to add any like this. I do not think it makes sense to optimize for such a weird case, which will probably never happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had this discussion before.
I have a vague recollection of this, but I thought it was with an older design of properties, where the shorthands were more essential. Andrei's proposal here is much simpler, to the point that I don't think we need the aliases anymore, and that's why I think we should discuss it again.
Consider the current definition of sub_group_size
:
struct sub_group_size_key {
template <uint32_t Size>
using value_t = property_value<sub_group_size_key, std::integral_constant<uint32_t, Size>>;
};
template <uint32_t Size>
inline constexpr sub_group_size_key::value_t<Size> sub_group_size;
Typing sub_group_size<32>
is clearly much better than typing sub_group_size_key::value_t<32>{}
, and defining an alias hides all the implementation complexity from the user.
I don't think typing sub_group_size<32>
instead of sub_group_size<32>{}
is enough of a gain to justify introducing an otherwise unnecessary sub_group_size_property
class.
There are no properties today that mix compile-time and runtime values, and we don't have any immediate plans to add any like this.
I hadn't realized at the time, but @rolandschulz and I were actually discussing a case like this late last week.
If we add a property to control the size of the work-group scratchpad memory (like the one proposed in #15061), then having a single work_group_scratchpad_size
property that can be used as either work_group_scratchpad_size<1024>{}
or work_group_scratchpad_size(N)
would be nicer than splitting it into two properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd envision something like this instead: https://godbolt.org/z/b97a39zrv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to avoid stealing a value from the representable range of value. But yes, that works too.
You can avoid the <>
by employing a deduction guide. https://godbolt.org/z/zK53Kvvnv.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://godbolt.org/z/PYs946Ko4 avoids stealing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that's clever, but it's still not clear to me that there's a real use case here. Why would we want a compile-time-constant version of work_group_scratchpad_size
? Is there some meaningful optimization that we expect the compiler to do in this case?
In any case, this would be a very disruptive change in the common use case for properties
. There's a lot of code out there using the inline constexpr variables. We can't ask all that code to change now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't ask all that code to change now
I don't agree. The properties are experimental and I think changing them by deprecating variables in some 2025.x minor release and removing in 2026.0 is acceptable. Also, we can consider this in scope of eventually making it a KHR extension (or next SYCL revision).
sycl/test/extensions/properties/new_properties_open_question.cpp
Outdated
Show resolved
Hide resolved
void bar() { | ||
// "Duck-typing" here. | ||
foo(prop<42>); | ||
|
||
// Now nothing prevents us from using `operator|` here that someone might | ||
// found more natural. Not doing for simplicity for now. | ||
foo(prop<42> + other_prop); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way that we could achieve this syntax without the duck-typing would be to define two overloads of every function that is supposed to take properties:
template <typename Property> auto foo(Property prop);
template <typename PropertyList> auto foo(PropList props = {});
...but there would be drawbacks to that, like the fact that foo<prop<42>>
would be a different function to foo<properties<prop<42>>>
. That said, I think duck-typing and @rolandschulz's suggestion that we handle this via adjusted constraints might both have this same issue.
Would this be good enough? People designing property interfaces would have to be aware of this potential issue, but we might just be able to disable the single-property overload on a case-by-case basis. I think it would only matter if somebody wanted to use a static
variable inside of a function accepting a property list, and I can't immediately think of a use-case for that. And there's a straightforward workaround (as @rolandschulz pointed out offline): if you want to use this pattern, foo(property)
should just call foo(properties(property))
.
Providing the extra overload seems nicer to me than supporting unary +
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Providing the extra overload seems nicer to me than supporting unary
+
.
I agree. Would callers be able to pass an initializer list to specify a list of properties like:
foo({prop1, prop2});
I think that would be nicer than:
foo(properties{prop1, prop2});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would callers be able to pass an initializer list to specify a list of properties like:
I might be wrong, but I think we'll always hit problems with this sort of thing because the property list type can't be deduced.
We can make foo((prop1, prop2))
work by overloading the ,(property)
operator, but I don't know if that's a good idea in general. I've never seen ,
overloaded in the wild.
* Unary plus * "merge" ctor - binary operator+ is used instead * include of the new header in `properties.hpp` * simplify get_property_impl
Latest revision drops This comes with a limitation that we won't be able to support properties that have mixed type/non-type template parameters until something like https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2989r2.pdf (universal template parameters) appears in C++, but I'm not aware of such properties (we do have type-templated properties for virtual functions though). Note that some extensions implement llvm/sycl/include/sycl/ext/oneapi/device_global/device_global.hpp Lines 238 to 245 in eed2d03
Update: https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_prefetch.asciidoc is a "mixed" non-type/type property, but it can easily be changed - the type is used as a simple tag. |
Looks great to me.👍
How do you recommend that we fix this case? If I understand the issue correctly, we could either: 1) replace the |
..make previous property_key_tag the key itself (adjusted to the lack of universal template).
I think the latter. I'd personally prefer |
static_assert(foo<ty>::bar(detail::key<prop>())); | ||
static_assert(foo<ty>::bar(detail::key<prop>(), detail::key<prop2>())); | ||
static_assert(foo<empty_properties_t>::bar(detail::key<prop>())); | ||
static_assert(!foo<ty>::bar(detail::key<prop2>())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's continue the discussion from #15899 (comment) here. @gmlueck , @Pennycook
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gmlueck 's argument is that we want to be able to extend the set of allowed properties for a given API from multiple extensions. As such, it's preferred to have a trait that each property can specialize, instead of a single type list (as in @Pennycook's and my suggestions).
Remaining open questions (to the best of my understanding):
An alternative might be to name the types without
|
I think this is my position, too. But to make it explicit, am I right in thinking that this would mean that your example above becomes the below? template <...> struct foo {};
struct bar { bar(...); };
// consistent usage: user must always construct a property (same as existing sycl::property_list)
auto pl = properties{ foo<..>{}, bar(...) };
// consistent naming: properties only have one name, used for both declarations and queries
pl.has_property<foo>() && pl.has_property<bar>(); I'd still like us to try and come up with some shorthand syntax, even if it's not one of the approaches that we've discussed above. For comparison, consider a reduction: // SYCL 2020
sycl::reduction(..., sycl::property::reduction::initialize_to_identity{}); // 51 characters
// Without any shorthands, top-level namespace
sycl::reduction(..., sycl::properties{sycl::initialize_to_identity{}}); // 48 characters
// Without any shorthands, new property namespace (my current expectation)
sycl::reduction(..., sycl::properties{sycl::property::initialize_to_identity{}}); // 58 characters
// With a shorthand, or even just an overload for one property
sycl::reduction(..., sycl::property::initialize_to_identity{}); // 40 characters |
Yes. |
…hortcut To show how real-world usage would look like.
This is based on @rolandschulz 's #13776 and #13669, with lots of help from both @rolandschulz and @tahonermann in offline chats.
This is a proof of concept on how the extension can be changed. The purpose of this PR is to start discussion on
I plan on drafting the changes to the extension later on once the implementation prototype is closer to finalization.