Skip to content

[SYCL] Add basic tests for virtual functions #14209

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

113 changes: 113 additions & 0 deletions sycl/test-e2e/VirtualFunctions/2/1/1/missing-overrides.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// UNSUPPORTED: cuda, hip, acc
// FIXME: replace unsupported with an aspect check once we have it
//
// RUN: %{build} -o %t.out -Xclang -fsycl-allow-virtual-functions %helper-includes
// RUN: %{run} %t.out

#include <sycl/detail/core.hpp>

#include "helpers.hpp"

#include <iostream>

namespace oneapi = sycl::ext::oneapi::experimental;

class Base {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void increment(int *) { /* do nothhing */
}

SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void multiply(int *) { /* do nothhing */
}

SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void substract(int *) { /* do nothhing */
}
};

class IncrementBy1 : public Base {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 1; }
};

class IncrementBy1AndSubstractBy2 : public IncrementBy1 {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void substract(int *Data) override { *Data -= 2; }
};

class MultiplyBy2 : public Base {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void multiply(int *Data) override { *Data *= 2; }
};

class MultiplyBy2AndIncrementBy8 : public MultiplyBy2 {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 8; }
};

class SubstractBy4 : public Base {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void substract(int *Data) override { *Data -= 4; }
};

class SubstractBy4AndMultiplyBy4 : public SubstractBy4 {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void multiply(int *Data) override { *Data *= 4; }
};

void applyOp(int *DataPtr, Base *ObjPtr) {
ObjPtr->increment(DataPtr);
ObjPtr->substract(DataPtr);
ObjPtr->multiply(DataPtr);
}

int main() try {
using storage_t = obj_storage_t<IncrementBy1, IncrementBy1AndSubstractBy2,
MultiplyBy2, MultiplyBy2AndIncrementBy8,
SubstractBy4, SubstractBy4AndMultiplyBy4>;
storage_t HostStorage;
sycl::buffer<storage_t> DeviceStorage(sycl::range{1});

auto asyncHandler = [](sycl::exception_list list) {
for (auto &e : list)
std::rethrow_exception(e);
};

sycl::queue q(asyncHandler);

constexpr oneapi::properties props{oneapi::calls_indirectly<>};
for (unsigned TestCase = 0; TestCase < 6; ++TestCase) {
int HostData = 42;
int Data = HostData;
sycl::buffer<int> DataStorage(&Data, sycl::range{1});

q.submit([&](sycl::handler &CGH) {
sycl::accessor StorageAcc(DeviceStorage, CGH, sycl::write_only);
sycl::accessor DataAcc(DataStorage, CGH, sycl::write_only);
CGH.single_task(props, [=]() {
auto *Ptr = StorageAcc[0].construct</* ret type = */ Base>(TestCase);
applyOp(DataAcc.get_multi_ptr<sycl::access::decorated::no>().get(),
Ptr);
});
});

Base *Ptr = HostStorage.construct</* ret type = */ Base>(TestCase);
applyOp(&HostData, Ptr);

sycl::host_accessor HostAcc(DataStorage);
assert(HostAcc[0] == HostData);
}

return 0;
} catch (sycl::exception &e) {
std::cout << "Unexpected exception was thrown: " << e.what() << std::endl;
return 1;
}
93 changes: 93 additions & 0 deletions sycl/test-e2e/VirtualFunctions/2/1/1/more-complex-hierarchy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// UNSUPPORTED: cuda, hip, acc
// FIXME: replace unsupported with an aspect check once we have it
//
// RUN: %{build} -o %t.out -Xclang -fsycl-allow-virtual-functions %helper-includes
// RUN: %{run} %t.out

#include <sycl/detail/core.hpp>

#include "helpers.hpp"

#include <iostream>

namespace oneapi = sycl::ext::oneapi::experimental;

class AbstractOp {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void applyOp(int *) = 0;
};

class IncrementOp : public AbstractOp {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void applyOp(int *Data) final override { increment(Data); }

SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void increment(int *) = 0;
};

class IncrementBy1 : public IncrementOp {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 1; }
};

class IncrementBy2 : public IncrementOp {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 2; }
};

class IncrementBy4 : public IncrementOp {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 4; }
};

class IncrementBy8 : public IncrementOp {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 8; }
};

void applyOp(int *Data, AbstractOp *Obj) { Obj->applyOp(Data); }

int main() try {
using storage_t =
obj_storage_t<IncrementBy1, IncrementBy2, IncrementBy4, IncrementBy8>;

storage_t HostStorage;
sycl::buffer<storage_t> DeviceStorage(sycl::range{1});

auto asyncHandler = [](sycl::exception_list list) {
for (auto &e : list)
std::rethrow_exception(e);
};

sycl::queue q(asyncHandler);

constexpr oneapi::properties props{oneapi::calls_indirectly<>};
for (unsigned TestCase = 0; TestCase < 4; ++TestCase) {
int HostData = 42;
int Data = HostData;
sycl::buffer<int> DataStorage(&Data, sycl::range{1});

q.submit([&](sycl::handler &CGH) {
sycl::accessor StorageAcc(DeviceStorage, CGH, sycl::write_only);
sycl::accessor DataAcc(DataStorage, CGH, sycl::write_only);
CGH.single_task(props, [=]() {
auto *Ptr =
StorageAcc[0].construct</* ret type = */ AbstractOp>(TestCase);
applyOp(DataAcc.get_multi_ptr<sycl::access::decorated::no>().get(),
Ptr);
});
});

auto *Ptr = HostStorage.construct</* ret type = */ AbstractOp>(TestCase);
Ptr->applyOp(&HostData);

sycl::host_accessor HostAcc(DataStorage);
assert(HostAcc[0] == HostData);
}

return 0;
} catch (sycl::exception &e) {
std::cout << "Unexpected exception was thrown: " << e.what() << std::endl;
return 1;
}
78 changes: 78 additions & 0 deletions sycl/test-e2e/VirtualFunctions/2/1/1/simple-hierarchy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// UNSUPPORTED: cuda, hip, acc
// FIXME: replace unsupported with an aspect check once we have it
//
// RUN: %{build} -o %t.out -Xclang -fsycl-allow-virtual-functions %helper-includes
// RUN: %{run} %t.out

#include <sycl/detail/core.hpp>

#include "helpers.hpp"

#include <iostream>

namespace oneapi = sycl::ext::oneapi::experimental;

class BaseIncrement {
public:
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
virtual void increment(int *Data) { *Data += 1; }
};

class IncrementBy2 : public BaseIncrement {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 2; }
};

class IncrementBy4 : public BaseIncrement {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 4; }
};

class IncrementBy8 : public BaseIncrement {
SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(oneapi::indirectly_callable<>)
void increment(int *Data) override { *Data += 8; }
};

int main() try {
using storage_t =
obj_storage_t<BaseIncrement, IncrementBy2, IncrementBy4, IncrementBy8>;

storage_t HostStorage;
sycl::buffer<storage_t> DeviceStorage(sycl::range{1});

auto asyncHandler = [](sycl::exception_list list) {
for (auto &e : list)
std::rethrow_exception(e);
};

sycl::queue q(asyncHandler);

constexpr oneapi::properties props{oneapi::calls_indirectly<>};
for (unsigned TestCase = 0; TestCase < 4; ++TestCase) {
int HostData = 42;
int Data = HostData;
sycl::buffer<int> DataStorage(&Data, sycl::range{1});

q.submit([&](sycl::handler &CGH) {
sycl::accessor StorageAcc(DeviceStorage, CGH, sycl::write_only);
sycl::accessor DataAcc(DataStorage, CGH, sycl::write_only);
CGH.single_task(props, [=]() {
auto *Ptr =
StorageAcc[0].construct</* ret type = */ BaseIncrement>(TestCase);
Ptr->increment(
DataAcc.get_multi_ptr<sycl::access::decorated::no>().get());
});
});

auto *Ptr = HostStorage.construct</* ret type = */ BaseIncrement>(TestCase);
Ptr->increment(&HostData);

sycl::host_accessor HostAcc(DataStorage);
assert(HostAcc[0] == HostData);
}

return 0;
} catch (sycl::exception &e) {
std::cout << "Unexpected exception was thrown: " << e.what() << std::endl;
return 1;
}
7 changes: 7 additions & 0 deletions sycl/test-e2e/VirtualFunctions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# E2E tests for `sycl_ext_oneapi_virtual_functions` extension

Note about naming convention and files organization for this folder: the tests,
files and directories are named and organized in a way that resembles their
description in the corresponding test plan document: link to be inserted here
later, but for now look into
[intel/llvm#10540](https://github.com/intel/llvm/pull/10540) PR.
53 changes: 53 additions & 0 deletions sycl/test-e2e/VirtualFunctions/helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <algorithm>
#include <type_traits>

// TODO: strictly speaking, selecting a max alignment here may not be always
// valid, but for test cases that we have now we expect alignment of all types
// to be the same.
// std::aligned_storage uses double under the hood which prevents us from
// using it on some HW. Therefore we use a custom implementation.
template <typename... T> struct aligned_storage {
static constexpr size_t Len = std::max({sizeof(T)...});
static constexpr size_t Align = std::max({alignof(T)...});

struct type {
alignas(Align) unsigned char data[Len];
};
};

// Helper data structure that automatically creates a right (in terms of size
// and alignment) storage to accomodate a value of any of types T...
template <typename... T> struct obj_storage_t {
static_assert(std::max({alignof(T)...}) == std::min({alignof(T)...}),
"Unsupported alignment of input types");
using type = typename aligned_storage<T...>::type;
static constexpr size_t size = std::max({sizeof(T)...});

type storage;

template <typename RetT> RetT *construct(const unsigned int TypeIndex) {
if (TypeIndex >= sizeof...(T)) {
#ifndef __SYCL_DEVICE_ONLY__
assert(false && "Type index is invalid");
#endif
return nullptr;
}

return constructHelper<RetT, T...>(TypeIndex, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think something like

static constexpr auto get_vec_idx(int idx) {
int counter = 0;
int result = -1;
((result = counter++ == idx ? Indexes : result), ...);
return result;
}
can be used to eliminate the helper.

Copy link
Contributor Author

@AlexeySachkov AlexeySachkov Sep 16, 2024

Choose a reason for hiding this comment

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

TypeIndex is a runtime value here, the function is not constexpr.
Otherwise, I think I could have used tuple_element as built-in helper, essentially

UPD: looking at it more, TypeIndex being RT value shouldn't matter here. Checks are happening at runtime, only expansion happens at compile-time and list of types is known. I will take a deeper look to see if I can simplify this

}

private:
template <typename RetT> RetT *constructHelper(const int, const int) {
// Won't be ever called, but required to compile
return nullptr;
}

template <typename RetT, typename Type, typename... Rest>
RetT *constructHelper(const int TargetIndex, const int CurIndex) {
if (TargetIndex != CurIndex)
return constructHelper<RetT, Rest...>(TargetIndex, CurIndex + 1);

RetT *Ptr = new (reinterpret_cast<Type *>(&storage)) Type;
return Ptr;
}
};
6 changes: 6 additions & 0 deletions sycl/test-e2e/VirtualFunctions/lit.local.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import os

# Tests are sharing some common header, but we don't won't to use relative
# paths like "../../../helper.hpp" in them, so let's just register a
# substitution to add directory with helper headers into include search path
config.substitutions.append(("%helper-includes", "-I {}".format(os.path.dirname(os.path.abspath(__file__)))))
Loading