:sectnums:

= `sycl_ext_oneapi_properties`

:dpcpp: pass:[DPC++]

== Introduction

The `sycl::property_list` found in SYCL 2020 is used to store properties used in the construction of runtime classes. It does so in a fully dynamic manner, such that it is not possible to obtain any useful information about the types of properties passed nor their values at compile time.

Compile-time-constant properties are an important building block for classes and functions that have a need to propagate compile-time information for semantic and optimization purposes, while runtime properties continue to serve an important role in dynamic parameter specification.

This extension introduces `sycl::ext::oneapi::experimental::properties`, which is a replacement for `sycl::property_list` that supports the storage and manipulation of compile-time-constant properties in addition to runtime properties.

== Contributors
Joe Garvey, Intel +
Roland Schulz, Intel +
Ilya Burylov, Intel +
Michael Kinsner, Intel +
John Pennycook, Intel +
Jessica Davies, Intel +
Greg Lueck, Intel +
Jason Sewall, Intel

== Notice
Copyright (c) 2021 Intel Corporation.  All rights reserved.

== Status

This is an experimental extension specification, intended to provide early
access to features and gather community feedback.  Interfaces defined in this
specification are implemented in {dpcpp}, but they are not finalized and may
change incompatibly in future versions of {dpcpp} without prior notice.
*Shipping software products should not rely on APIs defined in this
specification.*

== Version

Last Modified Date: 2021-12-15 +
Revision: 2

== Dependencies

This extension is written against the SYCL 2020 Specification (revision 3).

== Overview

The purpose of this document is to clearly describe and specify a new property list (`properties`) and related concepts, types, and mechanisms, and to give examples and context for their usage.

=== Motivation

`sycl::property_list` enables users to pass additional parameters when constructing objects of the runtime classes, but the design of `sycl::property_list` is such that it is not possible to reliably determine the contents of these passed parameters at compile time.

There are numerous circumstances where parameters are only useful when their presence and contents are known at compile-time; this extension addresses this by introducing a `properties` class that is able to represent compile-time-constant properties in addition to runtime properties.

The handling of runtime properties in `properties` departs from that of `sycl::property_list` by having the types of any runtime properties present be known at compile-time. The distinction between runtime properties and compile-time-constant properties is that runtime properties store values that are set at runtime.

=== Examples

[NOTE]
====
Due to type deduction, users should usually not need to specify the template parameters of `properties` directly. In cases the type is needed, `decltype` should be used.
====

A property list that has the compile-time constant property `foo` can be created as follows:

```c++
properties P1{foo};
```

where `foo` is a global variable representing a property value of the compile-time constant property.

The type of a `properties` object is determined by the set of runtime properties and compile-
time-constant property values that it contains.  In the following examples, `bar`
and `baz` are compile-time-constant properties, while `foo` is a
runtime property.

```c++
properties P1{bar};
properties P2{bar, foo{argv[0]}};
properties P3{bar, baz};
properties P4{baz, bar};
static_assert(!std::is_same_v<decltype(P1), decltype(P2)>); // P1 and P2 contain different properties
static_assert(!std::is_same_v<decltype(P1), decltype(P3)>); // P1 and P3 contain different properties
static_assert(std::is_same_v<decltype(P3), decltype(P4)>); // The order properties are specified doesn't change the type
```

=== Goals

The goals of this extension are:

. Enable the storage, manipulation, and propagation of both compile-time-constant properties and runtime properties via `properties`
. Handle runtime properties such that the existence and types of runtime properties in a property list are known at compile time (while keeping their values dynamic).

The intention is to provide a robust mechanism with which to pass compile-time-constant properties and runtime properties to classes and functions.

=== Glossary

property:: A property is represented by a key and value. Properties can be used to provide extra values to classes or functions.

property value:: An object of the property value class. A property value has zero or more property parameters.
For runtime properties the value type is the same as the key type.
For compile time properties the value type is given by the `value_t` type alias of the key type.

property key:: A class representing the property key. It is used to query properties.

property parameter:: A parameter of a property that can be set and queried. May be dynamic (runtime) or compile-time-only, depending on the property. For compile-time-constant properties, property parameters may be types or non-types. For runtime properties, property parameters must be non-types.

runtime property:: A property that has property parameters determined at runtime and stored as members of the property.

compile-time-constant property:: A property that has no parameters that are
determined at runtime.  This includes properties that have no parameters and
properties where all parameters are determined at compile time.  If such a
property has parameters, their values are stored as template arguments of the
property value class.



== Proposal

=== Feature test macro

This extension provides a feature-test macro as described in the core SYCL
specification section 6.3.3 "Feature test macros".  An
implementation supporting this extension must predefine the macro
`SYCL_EXT_ONEAPI_PROPERTIES` to one of the values defined in the table below.
Applications can test for the existence of this macro to determine if the
implementation supports this feature, or applications can test the macro's
value to determine which of the extension's APIs the implementation supports.

[%header,cols="1,5"]
|===
|Value |Description
|1     |Initial extension version.  Base features are supported.
|===

=== Representation of Properties

Properties have a value and key type,
and by convention, these classes are declared in the root of the
`sycl::ext::oneapi::experimental` namespace. For a runtime property the key and value types are the same and the name of the property value
class has no suffix. A runtime property value typically has a constructor
which takes the value(s) of the properties and member function(s) which return those values. 

```c++
namespace sycl::ext::oneapi::experimental {

// This is a runtime property value with one integer parameter.
// The name of the property value class is the the name of the property without any suffix.
struct foo {
  foo(int);
  int value;
};
// A runtime property key is an alias to the value type.
using foo_key = foo;

} // namespace experimental::oneapi::ext::sycl
```

For compile-time constant parameters the value type is a template specialization of `property_value`. 
The property key class contains a `value_t` alias which is templated on the property parameters. The `property_value` class holds the
values of the compile-time parameters as template arguments. The parameters to a compile-
time-constant property can be either types or non-type values.
The implementation provides a variable with the property value type. The variable has the name of the property without a suffix.

```c++
namespace sycl::ext::oneapi::experimental {

template<typename...> struct property_value;

// This property has no parameters.
struct bar_key {
  using value_t = property_value<bar_key>;
};
// bar is an object of the property value type of bar.
inline constexpr bar_key::value_t bar;

// This property has one integer non-type parameter.
struct baz_key {
  template<int K>
  using value_t = property_value<baz_key, std::integral_constant<int, K> >;
  // Note: integral_constant is used for the example. An implementation can use a different mapping.
};
// baz is an object of a property value type of baz.
template<int K>
inline constexpr baz_key::value_t<K> baz;

// This property has an arbitrary number of type parameters.
struct boo_key {
  template<typename...Ts>
  using value_t = property_value<boo_key, Ts...>;
};
// boo is an object of a property value type of boo.
template<typename... Ts>
inline constexpr boo_key::value_t<Ts...> boo;

} // namespace experimental::oneapi::ext::sycl
```

=== Property traits

Properties may add a specialization of `is_property_key_of` that inherits from
`std::true_type` for each SYCL runtime class that the property can be applied
to. All have a base case which inherits from `std::false_type`.

```c++
namespace sycl::ext::oneapi::experimental {
//Base case
template<typename, typename> struct is_property_key_of : std::false_type {};

// These properties can be applied to any SYCL object.
template<typename SyclObjectT>
struct is_property_key_of<foo_key, SyclObjectT> : std::true_type {};
template<typename SyclObjectT>
struct is_property_key_of<bar_key, SyclObjectT> : std::true_type {};


// is_property_value is provided by the properties infrastructure in implementation-defined way
template<typename V> struct is_property_value;
// is_property_value_of is based on is_property_key_of, implementation is provided by the properties infrastructure
template<typename V, typename O> struct is_property_value_of;

} // namespace experimental::oneapi::ext::sycl
```

=== Property value class

The `property_value` class has implementation-defined template parameters.  In
the common case when the property has a single parameter, it provides a member
variable named `value` and a type alias named `value_t` to retrieve the value
and type of the parameter. When a property has more than one parameter, the
`property_value` class provides more semantically meaningful ways to retrieve
the values and types of the parameters.

```c++
namespace sycl::ext::oneapi::experimental {

template<typename Property, typename First, typename...Others>
struct property_value {
  // Alias of the property key type
  using key_t = Property;

  // Each property with multi-parameter property_value must define template 
  // specializations for accessing the parameters.

  // Available only when the property value has a single parameter and `value_t` of the property takes a non-type parameter
  static constexpr auto value = First::value;

  // Available only when the property value has a single parameter
  using value_t = First;
};

} // namespace experimental::oneapi::ext::sycl
```

The members of `property_value` are described in the table below.

--
[options="header"]
|====
| Member | Description
a|
```c++
static constexpr auto value = First::value;
```
| The value of the parameter. Available only when there is exactly one non-type
parameter.
a|
```c++
using value_t = First;
```
| Available only when there is exactly one parameter.  When the parameter's value is a type, `value_t`
is that type.  When the parameter's value is a non-type, `value_t` is an implementation-defined type
with a member constant `value` equal to the value.
a|
```c++
using key_t = property;
```
| The property key type.
|===
--

=== Property operators

The implementation provides equality and inequality operators for
properties.

```c++
namespace sycl::ext::oneapi::experimental {

// Available only if Prop is a compile-time constant property
template <typename Prop, typename...A, typename...B>
constexpr bool operator==(property_value<Prop, A...> V1, property_value<Prop, B...> V2);

// Available only if Prop is a compile-time constant property
template <typename Prop, typename...A, typename...B>
constexpr bool operator!=(property_value<Prop, A...> V1, property_value<Prop, B...> V2);

// Available only if Prop is a runtime property
template <typename Prop>
bool operator==(Prop P1, Prop P2);

// Available only if Prop is a runtime property
template <typename Prop>
bool operator!=(Prop P1, Prop P2);

} // namespace experimental::oneapi::ext::sycl
```

--
[options="header"]
|====
| Function | Description
a|
```c++
template <typename Prop, typename...A, typename...B>
constexpr bool operator==(property_value<Prop, A...> V1, property_value<Prop, B...> V2);
```
| Returns true if `V1` and `V2` have the same type. Returns false otherwise.
a|
```c++
template <typename Prop, typename...A, typename...B>
constexpr bool operator!=(property_value<Prop, A...> V1, property_value<Prop, B...> V2);
```
| Returns false if `V1` and `V2` have the same type, true otherwise.
a|
```c++
template <typename Prop>
bool operator==(Prop P1, Prop P2);
```
| Returns true if all parameters (=member variables) of `P1` and `P2` are the same. Returns false otherwise.
a|
```c++
template <typename Prop>
bool operator!=(Prop P1, Prop P2);
```
| Returns false if all parameters of `P1` and `P2` are the same. Returns true otherwise.
|===
--


=== Property list `properties`

This extension adds a new template class, `sycl::ext::oneapi::experimental::properties`, which is a property list that can contain compile-time constant properties as well as runtime properties.

`properties` is a class template, and the properties stored by it influence its type. Two `properties` objects have the same type if and only if they were constructed with the same set of compile-time constant property values and the same set of runtime properties.

[NOTE]
====
The runtime properties contained in the property list affect the type of `sycl::ext::oneapi::experimental::properties`, but their property values do not.
====

It is possible at compile-time to determine whether a `properties` object contains a particular (runtime or compile-time constant) property. See the `static constexpr` function `has_property` of the `properties` class.

It is possible at compile-time to determine the property value of a compile-time constant property contained in a `properties` object. See the `static constexpr` function `get_property` of the `properties` class.

The `get_property` member function of `properties` returns a property value object.
For runtime properties, the `get_property` member function of `properties` returns a _copy_ of the property object passed to the `properties` constructor.

In the same way that two different runtime properties of the same type cannot be applied to the same object, two compile-time constant property values of the same property class `T` cannot belong to the same `properties`, whether the property value is the same or different.

[NOTE]
====
That last sentence is not explicitly stated in the core SYCL spec, but it is assumed by the properties interface.
====

The `empty_properties_t` type alias is a shorthand for the type of an empty property list.

The new `properties` class template is as follows:

```c++
namespace sycl::ext::oneapi::experimental {

template<typename PropertyValuesT>
class properties {
  // static_assert: all types in PropertyValuesT need to be properties and need to be unique and sorted.
 public:
  // props can contain objects of compile-time constant and runtime property values in any order.
  // Available only if all types in PropertyValueTs are property values. 
  // Only valid if all types in PropertyValueTs are in PropertyValuesT,
  // and all types in PropertyValuesT  which are not default constructible are in PropertyValueTs.
  template<typename... PropertyValueTs>
  constexpr properties(PropertyValueTs... props);

  template<typename PropertyKeyT>
  static constexpr bool has_property();

  // Available only when PropertyT is the property class of a runtime property
  template<typename PropertyT>
  constexpr PropertyT get_property() const;

  // Available only when PropertyKeyT is the property class of a compile-time constant property
  template<typename PropertyKeyT>
  static constexpr auto get_property();
};

using empty_properties_t = decltype(properties{});

} // namespace experimental::oneapi::ext::sycl
```

[NOTE]
====
Implementations will need a deduction guide to satisfy the requirement that `properties` objects created from the same set of compile-time constant property values and runtime properties must have an identical type.
The requirement that `properties` objects have the same type if they contain the same set of compile-time constant property values and runtime properties also implies that implementations must define an ordering over all properties. This extension provides no mechanism for users to define their own properties.
====

The following table describes the constructors of the `properties` class:

--
[options="header"]
|====
| Constructor | Description
a|
```c++
template<typename ... PropertyValueTs>
constexpr properties(PropertyValueTs... props);
``` | Available only when each argument in props is an object of a property value.
Construct a property list with zero or more property values.  This constructor can accept both runtime and compile-time constant property values. Each property in the property list (as determined by PropertyValuesT) that is not default constructable must have an object provided in props.
|===
--

The following table describes the member functions of the `properties` class:

--
[options="header"]
|====
| Member function | Description
a|
```c++
template<typename PropertyKeyT>
static constexpr bool has_property();
``` | Returns true if the property list contains the property with property key class `PropertyKeyT`.  Returns false if it does not. Available only when `PropertyKeyT` is a property key class.
a|
```c++
template<typename PropertyT>
PropertyT get_property() const;
``` | Returns a copy of the property value contained in the property list.
Available only if `PropertyT` is a runtime property and the property list
contains a `PropertyT` property.
a|
```c++
template<typename PropertyKeyT>
static constexpr auto get_property();
``` | Returns a copy of the property value contained in the property list.
Available only if `PropertyKeyT` is the property key class of a compile-time
constant property and the property list contains a property with the
`PropertyKeyT` key.
|===
--

The following trait is added to recognize a `properties`.

```c++
namespace sycl::ext::oneapi::experimental {

// New trait to recognize a properties
template<typename propertyListT>
struct is_property_list;

template<typename propertyListT>
inline constexpr bool is_property_list_v = is_property_list<properties>::value;

} // namespace experimental::oneapi::ext::sycl
```

The following table describes the new `is_property_list` trait:

--
[width="100%",options="header"]
|====
| Traits | Description
a|
----
template<typename propertyListT>
struct is_property_list;
----
| An explicit specialization of `is_property_list` that inherits from `std::true_type` must be provided for each specialization of `properties`. All other specializations of `is_property_list` must inherit from `std::false_type`.
a|
----
template<typename propertyListT>
inline constexpr bool is_property_list_v;
----
| Variable containing value of `is_property_list<propertyListT>::value`.
|===
--


=== Type of `properties`

The details of the `properties` template argument(s) are unspecified. In particular the sorting order of properties is unspecified.
The type of the property list can be written with `decltype`.

The following example shows how `decltype` is used to create a property list type containing the compile-time constant properties `bar` and `baz`:

```c++
using P1 = decltype(properties(baz<1>, bar));
using P2 = decltype(properties(bar, baz<1>));
static_assert(std::is_same<P1, P2>::value); // Succeeds, since the order of properties does not matter
static_assert(P1::get_property<baz_key>().value == 1);
```

=== Querying Properties in Device Code

All values of compile-time constant properties are device copyable.

Some runtime properties may not be device copyable.
A `properties` object is device copyable if and only if it contains no runtime properties that are not device copyable. A device copyable `properties` object can be passed as a kernel parameter (as defined in the SYCL specification section 4.12.4).

In the following examples, `foo` is a compile-time constant property and is therefore device copyable. The property `bar` is a runtime property that is also device copyable.

```c++
static_assert(sycl::is_device_copyable_v<decltype(foo<1>)>);
static_assert(sycl::is_device_copyable_v<bar>);

properties P1{foo<1>, bar{}};

// All properties in P1 are device copyable, so P1 is device copyable
static_assert(sycl::is_device_copyable_v<decltype(P1)>);

h.single_task([=] {
  auto a = P1.has_property<foo_key>(); // OK
  auto b = P1.get_property<foo_key>(); // OK
  auto c = P1.has_property<bar_key>(); // OK
  auto d = P1.get_property<bar_key>(); // OK
});
```

A `properties` object that contains a runtime property that is not device copyable can not be passed as a kernel parameter.  However, a kernel can still call the `static constexpr` member functions by using `decltype`, as shown in the example below.
This allows a kernel to query for the existence of any property in a `properties` object, and it allows a kernel to query the value of a compile-time constant property, but it does not allow the kernel to query the value of a (device copyable or not device copyable) runtime property.
The following `decltype` syntax must be used to avoid capturing the not device copyable `properties` object.

In the following example, `foo` is a compile-time constant property and property `bar_vec` is a runtime property that is not device copyable.

```c++
// P2 contains the runtime property bar_vec, which is not device copyable
// P2 can not be a kernel parameter. P2 is not device copyable.
// decltype must be used even for compile-time constant properties
static_assert(!sycl::is_device_copyable_v<bar_vec>);
std::vector<int> v(atoi(argv[1]), 42);
properties P2{foo<1>, bar_vec{v}};
static_assert(!sycl::is_device_copyable_v<decltype(P2)>);
h.single_task([=] {
  auto a = decltype(P2)::has_property<foo_key>(); // OK, since decltype is used
  auto b = P2.has_property<foo_key>(); // incorrect, since decltype is missing

  auto c = decltype(P2)::has_property<bar_vec_key>(); // OK, since decltype is used
  auto d = decltype(P2)::get_property<bar_vec_key>(); // incorrect, since bar_vec is a runtime property

  auto e = decltype(P2)::get_property<foo_key>(); // OK, since foo is a compile-time constant property
  auto f = P2.get_property<foo_key>(); // incorrect, since decltype is missing
});
```

== Extended Examples

The arguments passed to the constructor of a runtime property specify the value of the property's parameters at runtime. Similarly, compile-time constant properties may have parameters that affect their semantics. For example, the property `foo` takes a single integer parameter:

```c++
properties P5{foo<1>};
properties P6{foo<2>};
properties P7{foo<1>, bar};
static_assert(P6.has_property<foo_key>()); // No need to specify the value of the property's parameter
static_assert(!std::is_same_v<decltype(P5), decltype(P6)>); // The parameter values of foo are different
auto f1 = P5.get_property<foo_key>(); // f1 is a copy of global variable foo<1 >
auto f2 = P6.get_property<foo_key>(); // f2 is a copy of global variable foo<2 >
static_assert(f1 != f2); // Not equal since the property values are different, i.e., 1 vs. 2
auto f3 = P7.get_property<foo_key>();
static_assert(f3 == f1); // Equal because the property values are the same, i.e., equal to 1
```

The parameters of a property may also be types. For example, the property foo_types takes an arbitrary number of parameters, each of which is a type. In this example, `foo_types` exposes its parameters (which are types) as `first_t`, `second_t`, `third_t`.

```c++
properties P8{foo_types<float, int, bool>};
using f = decltype(P8.get_property<foo_types_key>());
using t1 = f::first_t;
using t2 = f::second_t;
using t3 = f::third_t;
static_assert(std::is_same_v<t1, float);
static_assert(std::is_same_v<t2, int>);
static_assert(std::is_same_v<t3, bool>);
```

[NOTE]
====
Properties should provide semantically meaningful functions to access the parameters' values.
====

For functions that take a property list parameter, it is possible to restrict
the parameter to require a specific property.  If the property takes a
compile-time constant parameter, it is further possible to restrict the
function to take a property list that has a property with a specific parameter.
The following example demonstrates this, where `foo` is runtime property and
`bar` is a compile-time constant property.

```c++
template<typename PropertiesT>
std::enable_if_t<is_property_list_v<PropertiesT>> my_func1(propertyListT p);

template<typename PropertiesT>
std::enable_if_t<is_property_list_v<PropertiesT> && propertyListT::template has_property<foo_key>()> my_func2(PropertiesT p);

template<typename PropertiesT>
std::enable_if_t<is_property_list_v<PropertiesT> && (propertyListT::template get_property<bar_key>().value == 2)> my_func3(PropertiesT p);
...
my_func1(properties{foo}); // Legal.  my_func1 accepts any properties
my_func2(properties{foo}); // Legal.  my_func2 requires foo
my_func2(properties{bar}); // Illegal.  my_func2 requires foo
my_func2(properties{foo, bar<1>}); // Legal.  Other properties can also be specified.
my_func3(properties{bar<2>); // Legal. my_func3 requires bar with value 2
my_func3(properties{bar<1>); // Illegal. my_func3 requires bar with value 2
```

In the examples above, also note the distinction between the property key class `foo_key` and the _property value variable_ `foo`, both associated with property `foo`. Note how the property value variable `foo` is used to create the property list, while property key class `foo_key` is used to query the property list.

== End Matter

This section provides more detailed information for implementers. It is non-normative, and may be removed in future revisions of the extension.

=== Interface Guidelines for `properties` consumers

Adding a new compile-time constant property requires implementers to introduce the following:

* A new class representing the property key
* Specialization of `sycl::is_property_key_of` for the new property class
* A global variable that represents an object of the property value

=== Example of a Compile-time Constant Property

This is an example showing the definition of a compile-time constant property `foo` that takes a single integer parameter. The property key class associated with the property is `foo_key`.

```c++
namespace sycl::ext::oneapi::experimental {

// foo is the property key class
struct foo_key {
  template<int K>
  using value_t = property_value<foo, std::integral_constant<int, K>>;
};

// foo is a variable of the property value class that can be used to construct a
// property list with this property
template<int K>
inline constexpr foo::value_t<K> foo;

// foo can be applied to any object
template<typename SyclObjectT>
struct is_property_key_of<foo_key, SyclObjectT> : std::true_type {};

} // namespace experimental::oneapi::ext::sycl
```


== Revision History

[cols="5,15,15,70"]
[grid="rows"]
[options="header"]
|========================================
|Rev|Date|Author|Changes
|2|2021-12-15|Roland Schulz|Rename of value/key
|1|2021-07-13|Jason Sewall|Initial internal draft
|========================================
