Skip to content

Commit

Permalink
Casting accessors
Browse files Browse the repository at this point in the history
1. Implicit conversion to `const dataT`
2. Type traits for `const` conversions
  * For resolving access modes
3. Free functions for casting accessors
  * `static_pointer_cast`, `dynamic_pointer_cast`,
    `const_pointer_cast`, `reinterpret_pointer_cast`
4. Converting to a type of a different size
    requires `get_count`, `get_range`, and `get_offset`
    to return different values than the original accessor
5. Examples for all casts
  • Loading branch information
ProGTX committed Sep 22, 2019
1 parent b15fa36 commit a39ac72
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ Each proposal in the table below will be tagged with one of the following states
| CP017 | [Host Access](host_access/index.md) | SYCL 1.2.1 vendor extension | 17 September 2018 | 13 December 2018 | _Available since CE 1.0.3_ |
| CP018 | [Built-in kernels](builtin_kernels/index.md) | SYCL 1.2.1 vendor extension | 12 October 2018 | 12 October 2018 | _Available since CE 1.0.3_ |
| CP019 | [On-chip Memory Allocation](onchip-memory/index.md) | SYCL 1.2.1 vendor extension | 03 December 2018 | 03 December 2018 | _Available since CE 1.0.3_ |
| CP021 | [Casting accessors](accessor-cast/index.md) | SYCL 1.2.1 vendor extension | 21 September 2019 | 21 September 2019 | _Work in Progress_ |
20 changes: 20 additions & 0 deletions accessor-cast/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Casting accessors

| Proposal ID | CP021 |
|-------------|--------|
| Name | Casting accessors |
| Date of Creation | 21 September 2019 |
| Target | SYCL 1.2.1 vendor extension |
| Current Status | _Work in Progress_ |
| Reply-to | Peter Žužek <[email protected]> |
| Original author | Peter Žužek <[email protected]> |
| Contributors | |

## Overview

This paper proposes the addition of pointer casting functions to SYCL
in order to simplify casting of the `accessor` class.

## Versions

[Version 1](sycl-2.2/index.md)
318 changes: 318 additions & 0 deletions accessor-cast/sycl-2.2/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
# Casting accessors

## Motivation

The current SYCL 1.2.1 specification allows casting buffers
using the `reinterpret` member function.
This essentially provides a different view of the same data,
however it cannot be performed inside a kernel.

One option would be to cast the `multi_ptr` obtained from an accessor.
However, that is a separate proposal,
plus it would add another layer of first obtaining the pointer
and then using that pointer instead of the accessor.

## Summary

This proposal adds an implicit conversion operator
to a const-qualified accessor type
as a member function of the `accessor` class,
and also adds several free functions to the `cl::sycl` namespace
that follow the naming and semantics of the `std::shared_ptr` pointer cast functions
defined by the C++17 standard: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast.
In order to help with the `const` conversions
we also propose adding a few type traits.

## Const casting type traits

In order to help with casting the const qualifier,
we propose adding a few type traits:
1. `add_const_access` -
resolves any access mode to `access::mode::read`,
except for `access::mode::atomic`,
which resolves to the same mode.
2. `remove_const_access` -
resolves `access::mode::read` to `access::mode::read_write`,
and keeps all other modes the same.
3. `access_mode_from_const` -
takes an access mode and two types
and then decides, based on the types,
whether to use `add_const_access`, `remove_const_access`, or none,
in order to resolve the access mode.

```cpp
namespace cl {
namespace sycl {

/// add_const_access
template <access::mode mode>
struct add_const_access {
static constexpr auto value = access::mode::read;
};
template <>
struct add_const_access<access::mode::atomic> {
static constexpr auto value = access::mode::atomic;
};

/// remove_const_access
template <access::mode mode>
struct remove_const_access {
static constexpr auto value = mode;
};
template <>
struct remove_const_access<access::mode::read> {
static constexpr auto value = access::mode::read_write;
};

/// access_mode_from_const
template <access::mode mode, typename oldT, typename newT>
struct access_mode_from_const {
static constexpr auto value = mode;
};
template <access::mode mode, typename oldT, typename newT>
struct access_mode_from_const<
mode, const oldT, const newT>
: access_mode_from_const<
mode, oldT, newT> {};
template <access::mode mode, typename oldT, typename newT>
struct access_mode_from_const<
mode, const oldT, newT>
: access_mode_from_const<
remove_const_access<mode>::value, oldT, newT> {};
template <access::mode mode, typename oldT, typename newT>
struct access_mode_from_const<
mode, oldT, const newT>
: access_mode_from_const<
add_const_access<mode>::value, oldT, newT> {};

} // namespace sycl
} // namespace cl
```
## Conversion operator
The new interface of the `accessor` class would look like this:
```cpp
namespace cl {
namespace sycl {
template <typename dataT,
int dimensions,
access::mode accessMode,
access::target accessTarget,
access::placeholder isPlaceholder>
class accessor {
public:
/// All existing members here
...
// Implicit conversion to `const dataT`
operator accessor<const dataT,
dimensions,
add_const_access<dataT>::value,
accessTarget,
isPlaceholder>() const;
};
} // namespace sycl
} // namespace cl
```

| Member function | Description |
|-----------------|-------------|
| *`operator accessor<const dataT, dimensions, add_const_access<accessMode>::value, accessTarget, isPlaceholder>() const`* | Returns a new accessor of type `const dataT`. The access mode of the new accessor is a read-only mode. |

## Conversion functions

In addition to the conversion operator,
we propose adding the following free functions to the `cl::sycl` namespace:

```cpp
namespace cl {
namespace sycl {

// Performs a static_cast of the contained pointer
template <typename dataU,
typename dataT,
int dimensions,
access::mode accessMode,
access::target accessTarget,
access::placeholder isPlaceholder>
accessor<dataU,
dimensions,
access_mode_from_const<accessMode, dataT, dataU>::value,
accessTarget,
isPlaceholder>
static_pointer_cast(
const accessor<
dataT, dimensions, accessMode, accessTarget, isPlaceholder>&
acc);

// Performs a dynamic_cast of the contained pointer
template <typename dataU,
typename dataT,
int dimensions,
access::mode accessMode,
access::target accessTarget,
access::placeholder isPlaceholder>
accessor<dataU,
dimensions,
accessMode,
accessTarget,
isPlaceholder>
dynamic_pointer_cast(
const accessor<
dataT, dimensions, accessMode, accessTarget, isPlaceholder>&
acc);

// Performs a const_cast of the contained pointer
template <typename dataU,
typename dataT,
int dimensions,
access::mode accessMode,
access::target accessTarget,
access::placeholder isPlaceholder>
accessor<dataU,
dimensions,
access_mode_from_const<accessMode, dataT, dataU>::value,
accessTarget,
isPlaceholder>
const_pointer_cast(
const accessor<
dataT, dimensions, accessMode, accessTarget, isPlaceholder>&
acc);

// Performs a reinterpret_cast of the contained pointer
template <typename dataU,
typename dataT,
int dimensions,
access::mode accessMode,
access::target accessTarget,
access::placeholder isPlaceholder>
accessor<dataU,
dimensions,
accessMode,
accessTarget,
isPlaceholder>
reinterpret_pointer_cast(
const accessor<
dataT, dimensions, accessMode, accessTarget, isPlaceholder>&
acc);

} // namespace sycl
} // namespace cl
```
The following function declarations are simplified
in order to reduce table verbosity.
For full declarations see the code above.
| Function | Description |
|-----------------|-------------|
| *`template <typename dataU, typename dataT, ...> accessor<dataU, ...> static_pointer_cast(const accessor<dataT, ...>& acc)`* | Performs a `static_cast` of the underlying pointer `dataT*` contained within `acc` to `dataU*` and returns a new `accessor` instance containing the cast pointer. When casting from a non-`const` to a `const` type, `add_const_access` is used to resolve the access mode. All other template parameters stay the same. This conversion is only valid if the `static_cast` from `dataT*` to `dataU*` is valid. |
| *`template <typename dataU, typename dataT, ...> accessor<dataU, ...> dynamic_pointer_cast(const accessor<dataT, ...>& acc)`* | Performs a `dynamic_cast` of the underlying pointer `dataT*` contained within `acc` to `dataU*` and returns a new `accessor` instance containing the cast pointer. All other template parameters stay the same. This conversion is only valid if the `dynamic_cast` from `dataT*` to `dataU*` is valid. If `sizeof(dataT) != sizeof(dataU)`, member functions `get_count()`, `get_range()`, and `get_offset()` of the returned accessor return values appropriate to the new type size. |
| *`template <typename dataU, typename dataT, ...> accessor<dataU, ...> const_pointer_cast(const accessor<dataT, ...>& acc)`* | Performs a `const_cast` of the underlying pointer `dataT*` contained within `acc` to `dataU*` and returns a new `accessor` instance containing the cast pointer. `access_mode_from_const` is used to resolve the access mode. All other template parameters stay the same. This conversion is only valid if the `const_cast` from `dataT*` to `dataU*` is valid. |
| *`template <typename dataU, typename dataT, ...> accessor<dataU, ...> reinterpret_pointer_cast(const accessor<dataT, ...>& acc)`* | Performs a `reinterpret_cast` of the underlying pointer `dataT*` contained within `acc` to `dataU*` and returns a new `accessor` instance containing the cast pointer. All other template parameters stay the same. This conversion is only valid if the `reinterpret_cast` from `dataT*` to `dataU*` is valid. If `sizeof(dataT) != sizeof(dataU)`, member functions `get_count()`, `get_range()`, and `get_offset()` of the returned accessor return values appropriate to the new type size. |
## Examples
All examples use
`dimensions == 1`,
`accessTarget == access::target::global_buffer`,
and `isPlaceholder == access::placeholder::false_t`,
but they should generally apply to all accessor types.
### Simple casts
```cpp
using namespace cl::sycl;
static constexpr auto accessTarget = access::target::global_buffer;
// Obtain some accessors
accessor<int, 1, access::mode::read, accessTarget>
accIntR =
buf.get_access<access::mode::read, accessTarget>(cgh);
accessor<int, 1, access::mode::write, accessTarget>
accIntW =
buf.get_access<access::mode::write, accessTarget>(cgh);
accessor<int, 1, access::mode::read_write, accessTarget>
accIntRW =
buf.get_access<access::mode::read_write, accessTarget>(cgh);
accessor<const int, 1, access::mode::read, accessTarget>
accIntCR =
buf.get_access<access::mode::read, accessTarget>(cgh);
// Conversion operator
auto rAccIntCR = static_cast<
accessor<const int, 1, access::mode::read, accessTarget>>(
accIntR);
auto rAccIntCW = static_cast<
accessor<const int, 1, access::mode::read, accessTarget>>(
accIntW);
auto rAccIntCRW = static_cast<
accessor<const int, 1, access::mode::read, accessTarget>>(
accIntRW);
auto rAccIntCCR = static_cast<
accessor<const int, 1, access::mode::read, accessTarget>>(
accIntCR);
// static_pointer_cast
accessor<const int, 1, access::mode::read, accessTarget>
rAccIntCR2 = static_pointer_cast<const int>(accIntR);
accessor<const int, 1, access::mode::read, accessTarget>
rAccIntCW2 = static_pointer_cast<const int>(accIntW);
accessor<const int, 1, access::mode::read, accessTarget>
rAccIntCRW2 = static_pointer_cast<const int>(accIntRW);
accessor<const int, 1, access::mode::read, accessTarget>
rAccIntCCR2 = static_pointer_cast<const int>(accIntCR);
// const_pointer_cast
accessor<int, 1, access::mode::read_write, accessTarget>
rAccIntR = const_pointer_cast<int>(accIntR);
accessor<int, 1, access::mode::write, accessTarget>
rAccIntW = const_pointer_cast<int>(accIntW);
accessor<int, 1, access::mode::read_write, accessTarget>
rAccIntRW = const_pointer_cast<int>(accIntRW);
accessor<int, 1, access::mode::read_write, accessTarget>
rAccIntCR3 = const_pointer_cast<int>(accIntCR);
// reinterpret_pointer_cast
accessor<float, 1, access::mode::read, accessTarget>
rAccFloatR = reinterpret_pointer_cast<float>(accIntR);
accessor<float, 1, access::mode::write, accessTarget>
rAccFloatW = reinterpret_pointer_cast<float>(accIntW);
accessor<float, 1, access::mode::read_write, accessTarget>
rAccFloatRW = reinterpret_pointer_cast<float>(accIntRW);
accessor<const float, 1, access::mode::read, accessTarget>
rAccFloatCR3 = reinterpret_pointer_cast<const float>(accIntCR);
```

### `dynamic_pointer_cast`

All of these examples also use the same access mode for all casts.

```cpp
using namespace cl::sycl;
static constexpr auto accessMode = access::mode::read_write;
static constexpr auto accessTarget = access::target::global_buffer;

struct Base {
virtual void foo() const {}
virtual ~Base(){}
};
struct Derived : public Base {
void foo() const override {}
};

accessor<Base, 1, accessMode, accessTarget>
accBase =
buf.get_access<accessMode, accessTarget>(cgh);

accessor<int, 1, accessMode, accessTarget>
accDerived = dynamic_pointer_cast<Derived>(accBase);
accessor<int, 1, accessMode, accessTarget>
accBase2 = dynamic_pointer_cast<Base>(accDerived);
```

0 comments on commit a39ac72

Please sign in to comment.