Skip to content
This repository was archived by the owner on Mar 28, 2023. It is now read-only.

Commit 07c210f

Browse files
authored
[SYCL][ESIMD] Add tests on simd vector move constructor (#679)
Context variations are moved out as a test cases, with any action run directly over the simd move constructor result, with no further instance copy or move. XFAIL remains so if there is PR with move constructors added the CI will fail effectively specifying the need to change the tests The feature flag was hard-coded into the source to disable test freeze for any use case, with or without parsing the RUN directives Signed-off-by: Kochetkov, Yuriy <[email protected]>
1 parent 34c16eb commit 07c210f

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//==------- ctor_move.cpp - DPC++ ESIMD on-device test --------------------==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// REQUIRES: gpu, level_zero
9+
// XREQUIRES: gpu
10+
// TODO Remove the level_zero restriction once the test is supported on other
11+
// platforms
12+
// UNSUPPORTED: cuda, hip
13+
// RUN: %clangxx -fsycl %s -fsycl-device-code-split=per_kernel -o %t.out
14+
// RUN: %GPU_RUN_PLACEHOLDER %t.out
15+
// XFAIL: *
16+
// TODO Remove XFAIL once the simd vector provides move constructor
17+
//
18+
// Test for esimd move constructor
19+
// The test creates source simd instance with reference data and invokes move
20+
// constructor in different C++ contexts to create a new simd instance from the
21+
// source simd instance. It is expected for a new simd instance to store
22+
// bitwise same data as the one passed as the source simd constructor.
23+
24+
// The following issues for simd<T,32> observed:
25+
// - freeze with T in {char, unsigned char, singned char};
26+
// - runtime failure with T in {short, unsigned short}.
27+
// TODO Remove once the freeze is fixed
28+
#define SKIP_VECTOR_LEN_32
29+
30+
// The test proxy is used to verify the move constructor was called actually.
31+
#define __ESIMD_ENABLE_TEST_PROXY
32+
33+
#include "common.hpp"
34+
#include <algorithm>
35+
#include <cassert>
36+
37+
using namespace sycl::ext::intel::experimental::esimd;
38+
using namespace esimd_test::api::functional;
39+
40+
// Uses the initializer C++ context to call simd move constructor
41+
struct initializer {
42+
static std::string get_description() { return "initializer"; }
43+
44+
template <typename SimdT, typename ActionT>
45+
static void run(SimdT &&source, const ActionT &action) {
46+
static_assert(
47+
type_traits::is_nonconst_rvalue_reference_v<decltype(source)>);
48+
49+
const auto instance = SimdT(std::move(source));
50+
action(instance);
51+
}
52+
};
53+
54+
// Uses the variable declaration C++ context to call simd move constructor
55+
struct var_decl {
56+
static std::string get_description() { return "variable declaration"; }
57+
58+
template <typename SimdT, typename ActionT>
59+
static void run(SimdT &&source, const ActionT &action) {
60+
static_assert(
61+
type_traits::is_nonconst_rvalue_reference_v<decltype(source)>);
62+
63+
const auto instance(std::move(source));
64+
action(instance);
65+
}
66+
};
67+
68+
// Uses the rvalue in expression C++ context to call simd move constructor
69+
struct rval_in_expr {
70+
static std::string get_description() { return "rvalue in expression"; }
71+
72+
template <typename SimdT, typename ActionT>
73+
static void run(SimdT &&source, const ActionT &action) {
74+
static_assert(
75+
type_traits::is_nonconst_rvalue_reference_v<decltype(source)>);
76+
77+
SimdT instance;
78+
instance = SimdT(std::move(source));
79+
action(instance);
80+
}
81+
};
82+
83+
// Uses the function argument C++ context to call simd move constructor
84+
class const_ref {
85+
public:
86+
static std::string get_description() { return "const reference"; }
87+
88+
template <typename SimdT, typename ActionT>
89+
static void run(SimdT &&source, const ActionT &action) {
90+
static_assert(
91+
type_traits::is_nonconst_rvalue_reference_v<decltype(source)>);
92+
93+
action(SimdT(std::move(source)));
94+
}
95+
};
96+
97+
// The core test functionality.
98+
// Runs a TestCaseT, specific for each C++ context, for a simd<DataT,NumElems>
99+
// instance
100+
template <typename DataT, int NumElems, typename TestCaseT> class run_test {
101+
using KernelName = ctors::Kernel<DataT, NumElems, TestCaseT>;
102+
103+
public:
104+
bool operator()(sycl::queue &queue, const std::string &data_type) {
105+
bool passed = true;
106+
bool was_moved = false;
107+
108+
const shared_allocator<DataT> data_allocator(queue);
109+
const shared_allocator<int> flags_allocator(queue);
110+
const auto reference = generate_ref_data<DataT, NumElems>();
111+
112+
shared_vector<DataT> input(reference.cbegin(), reference.cend(),
113+
data_allocator);
114+
shared_vector<DataT> result(reference.size(), data_allocator);
115+
116+
// We need a special handling for case of simd<T,1>, as we need to check
117+
// more than a single data value; therefore we need to loop over the
118+
// reference data to run test
119+
if constexpr (NumElems == 1) {
120+
const auto n_checks = input.size();
121+
const sycl::range<1> range(n_checks);
122+
123+
// We need a separate flag per each check to have a parallel_for possible,
124+
// because any concurrent access to the same memory location is UB, even
125+
// in case we are updating variable to the same value in multiple threads
126+
shared_vector<int> flags_storage(n_checks, flags_allocator);
127+
128+
// Run check for each of the reference elements using a single work-item
129+
// per single element
130+
queue.submit([&](sycl::handler &cgh) {
131+
const DataT *const ptr_in = input.data();
132+
const auto ptr_out = result.data();
133+
const auto ptr_flags = flags_storage.data();
134+
135+
cgh.parallel_for<KernelName>(
136+
range, [=](sycl::id<1> id) SYCL_ESIMD_KERNEL {
137+
const auto work_item_index = id[0];
138+
// Access a separate memory areas from each of the work items
139+
const DataT *const in = ptr_in + work_item_index;
140+
const auto out = ptr_out + work_item_index;
141+
const auto was_moved_flag = ptr_flags + work_item_index;
142+
*was_moved_flag = run_check(in, out);
143+
});
144+
});
145+
queue.wait_and_throw();
146+
147+
// Oversafe: verify the proper signature was called for every check
148+
was_moved = std::all_of(flags_storage.cbegin(), flags_storage.cend(),
149+
[](int flag) { return flag; });
150+
} else {
151+
assert((input.size() == NumElems) &&
152+
"Unexpected size of the input vector");
153+
154+
shared_vector<int> flags_storage(1, flags_allocator);
155+
156+
queue.submit([&](sycl::handler &cgh) {
157+
const DataT *const in = input.data();
158+
const auto out = result.data();
159+
const auto was_moved_flag = flags_storage.data();
160+
161+
cgh.single_task<KernelName>(
162+
[=]() SYCL_ESIMD_KERNEL { *was_moved_flag = run_check(in, out); });
163+
});
164+
queue.wait_and_throw();
165+
166+
was_moved = flags_storage[0];
167+
}
168+
169+
if (!was_moved) {
170+
passed = false;
171+
172+
// TODO: Make ITestDescription architecture more flexible
173+
std::string log_msg = "Failed for simd<";
174+
log_msg += data_type + ", " + std::to_string(NumElems) + ">";
175+
log_msg += ", with context: " + TestCaseT::get_description();
176+
log_msg += ". A copy constructor instead of a move constructor was used.";
177+
178+
log::note(log_msg);
179+
} else {
180+
for (size_t i = 0; i < reference.size(); ++i) {
181+
const auto &retrieved = result[i];
182+
const auto &expected = reference[i];
183+
184+
if (!are_bitwise_equal(retrieved, expected)) {
185+
passed = false;
186+
187+
log::fail(ctors::TestDescription<DataT, NumElems, TestCaseT>(
188+
i, retrieved, expected, data_type));
189+
}
190+
}
191+
}
192+
193+
return passed;
194+
}
195+
196+
private:
197+
// The core check logic.
198+
// Uses USM pointers for input data and to store the data from the new simd
199+
// instance, so that we could check it later
200+
// Returns the flag that should be true only if the move constructor was
201+
// actually called, to differentiate with the copy constructor calls
202+
static bool run_check(const DataT *const in, DataT *const out) {
203+
bool was_moved = false;
204+
205+
// Prepare the source simd to move
206+
simd<DataT, NumElems> source;
207+
source.copy_from(in);
208+
209+
// Action to run over the simd move constructor result
210+
const auto action = [&](const simd<DataT, NumElems> &instance) {
211+
was_moved = instance.get_test_proxy().was_move_destination();
212+
instance.copy_to(out);
213+
};
214+
// Call the move constructor in the specific context and run action
215+
// directly over the simd move constructor result
216+
TestCaseT::template run(std::move(source), action);
217+
218+
return was_moved;
219+
}
220+
};
221+
222+
int main(int, char **) {
223+
bool passed = true;
224+
const auto types = get_tested_types<tested_types::all>();
225+
#ifdef SKIP_VECTOR_LEN_32
226+
const auto dims = values_pack<1, 8, 16>();
227+
#else
228+
const auto dims = get_all_dimensions();
229+
#endif
230+
231+
sycl::queue queue(esimd_test::ESIMDSelector{},
232+
esimd_test::createExceptionHandler());
233+
234+
// Run for all combinations possible
235+
passed &= for_all_types_and_dims<run_test, initializer>(types, dims, queue);
236+
passed &= for_all_types_and_dims<run_test, var_decl>(types, dims, queue);
237+
passed &= for_all_types_and_dims<run_test, rval_in_expr>(types, dims, queue);
238+
passed &= for_all_types_and_dims<run_test, const_ref>(types, dims, queue);
239+
240+
std::cout << (passed ? "=== Test passed\n" : "=== Test FAILED\n");
241+
return passed ? 0 : 1;
242+
}

0 commit comments

Comments
 (0)