1 //===--- acxxel_test.cpp - Tests for the Acxxel API -----------------------===//
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 
9 #include "acxxel.h"
10 #include "config.h"
11 #include "gtest/gtest.h"
12 
13 #include <chrono>
14 #include <condition_variable>
15 #include <mutex>
16 #include <thread>
17 
18 namespace {
19 
arraySize(T (&)[N])20 template <typename T, size_t N> constexpr size_t arraySize(T (&)[N]) {
21   return N;
22 }
23 
24 using PlatformGetter = acxxel::Expected<acxxel::Platform *> (*)();
25 class AcxxelTest : public ::testing::TestWithParam<PlatformGetter> {};
26 
TEST_P(AcxxelTest,GetDeviceCount)27 TEST_P(AcxxelTest, GetDeviceCount) {
28   acxxel::Platform *Platform = GetParam()().takeValue();
29   int DeviceCount = Platform->getDeviceCount().getValue();
30   EXPECT_GE(DeviceCount, 0);
31 }
32 
33 // Tests all the methods of a DeviceMemorySpan that was created from the asSpan
34 // method of a DeviceMemory object.
35 //
36 // The length is the number of elements in the span. The ElementByteSize is the
37 // number of bytes per element in the span.
38 //
39 // It is assumed that the input span has 10 or more elements.
40 template <typename SpanType>
testFullDeviceMemorySpan(SpanType && Span,ptrdiff_t Length,ptrdiff_t ElementByteSize)41 void testFullDeviceMemorySpan(SpanType &&Span, ptrdiff_t Length,
42                               ptrdiff_t ElementByteSize) {
43   EXPECT_GE(Length, 10);
44   EXPECT_GT(ElementByteSize, 0);
45 
46   // Full span
47   EXPECT_EQ(Length, Span.length());
48   EXPECT_EQ(Length, Span.size());
49   EXPECT_EQ(Length * ElementByteSize, Span.byte_size());
50   EXPECT_EQ(0, Span.offset());
51   EXPECT_EQ(0, Span.byte_offset());
52   EXPECT_FALSE(Span.empty());
53 
54   // Sub-span with first method.
55   auto First2 = Span.first(2);
56   EXPECT_EQ(2, First2.length());
57   EXPECT_EQ(2, First2.size());
58   EXPECT_EQ(2 * ElementByteSize, First2.byte_size());
59   EXPECT_EQ(0, First2.offset());
60   EXPECT_EQ(0, First2.byte_offset());
61   EXPECT_FALSE(First2.empty());
62 
63   auto First0 = Span.first(0);
64   EXPECT_EQ(0, First0.length());
65   EXPECT_EQ(0, First0.size());
66   EXPECT_EQ(0, First0.byte_size());
67   EXPECT_EQ(0, First0.offset());
68   EXPECT_EQ(0, First0.byte_offset());
69   EXPECT_TRUE(First0.empty());
70 
71   // Sub-span with last method.
72   auto Last2 = Span.last(2);
73   EXPECT_EQ(2, Last2.length());
74   EXPECT_EQ(2, Last2.size());
75   EXPECT_EQ(2 * ElementByteSize, Last2.byte_size());
76   EXPECT_EQ(Length - 2, Last2.offset());
77   EXPECT_EQ((Length - 2) * ElementByteSize, Last2.byte_offset());
78   EXPECT_FALSE(Last2.empty());
79 
80   auto Last0 = Span.last(0);
81   EXPECT_EQ(0, Last0.length());
82   EXPECT_EQ(0, Last0.size());
83   EXPECT_EQ(0, Last0.byte_size());
84   EXPECT_EQ(Length, Last0.offset());
85   EXPECT_EQ(Length * ElementByteSize, Last0.byte_offset());
86   EXPECT_TRUE(Last0.empty());
87 
88   // Sub-span with subspan method.
89   auto Middle2 = Span.subspan(4, 2);
90   EXPECT_EQ(2, Middle2.length());
91   EXPECT_EQ(2, Middle2.size());
92   EXPECT_EQ(2 * ElementByteSize, Middle2.byte_size());
93   EXPECT_EQ(4, Middle2.offset());
94   EXPECT_EQ(4 * ElementByteSize, Middle2.byte_offset());
95   EXPECT_FALSE(Middle2.empty());
96 
97   auto Middle0 = Span.subspan(4, 0);
98   EXPECT_EQ(0, Middle0.length());
99   EXPECT_EQ(0, Middle0.size());
100   EXPECT_EQ(0, Middle0.byte_size());
101   EXPECT_EQ(4, Middle0.offset());
102   EXPECT_EQ(4 * ElementByteSize, Middle0.byte_offset());
103   EXPECT_TRUE(Middle0.empty());
104 
105   auto Subspan2AtStart = Span.subspan(0, 2);
106   EXPECT_EQ(2, Subspan2AtStart.length());
107   EXPECT_EQ(2, Subspan2AtStart.size());
108   EXPECT_EQ(2 * ElementByteSize, Subspan2AtStart.byte_size());
109   EXPECT_EQ(0, Subspan2AtStart.offset());
110   EXPECT_EQ(0, Subspan2AtStart.byte_offset());
111   EXPECT_FALSE(Subspan2AtStart.empty());
112 
113   auto Subspan2AtEnd = Span.subspan(Length - 2, 2);
114   EXPECT_EQ(2, Subspan2AtEnd.length());
115   EXPECT_EQ(2, Subspan2AtEnd.size());
116   EXPECT_EQ(2 * ElementByteSize, Subspan2AtEnd.byte_size());
117   EXPECT_EQ(Length - 2, Subspan2AtEnd.offset());
118   EXPECT_EQ((Length - 2) * ElementByteSize, Subspan2AtEnd.byte_offset());
119   EXPECT_FALSE(Subspan2AtEnd.empty());
120 
121   auto Subspan0AtStart = Span.subspan(0, 0);
122   EXPECT_EQ(0, Subspan0AtStart.length());
123   EXPECT_EQ(0, Subspan0AtStart.size());
124   EXPECT_EQ(0, Subspan0AtStart.byte_size());
125   EXPECT_EQ(0, Subspan0AtStart.offset());
126   EXPECT_EQ(0, Subspan0AtStart.byte_offset());
127   EXPECT_TRUE(Subspan0AtStart.empty());
128 
129   auto Subspan0AtEnd = Span.subspan(Length, 0);
130   EXPECT_EQ(0, Subspan0AtEnd.length());
131   EXPECT_EQ(0, Subspan0AtEnd.size());
132   EXPECT_EQ(0, Subspan0AtEnd.byte_size());
133   EXPECT_EQ(Length, Subspan0AtEnd.offset());
134   EXPECT_EQ(Length * ElementByteSize, Subspan0AtEnd.byte_offset());
135   EXPECT_TRUE(Subspan0AtEnd.empty());
136 }
137 
TEST_P(AcxxelTest,DeviceMemory)138 TEST_P(AcxxelTest, DeviceMemory) {
139   acxxel::Platform *Platform = GetParam()().takeValue();
140   acxxel::Expected<acxxel::DeviceMemory<int>> MaybeMemory =
141       Platform->mallocD<int>(10);
142   EXPECT_FALSE(MaybeMemory.isError());
143 
144   // ref
145   acxxel::DeviceMemory<int> &MemoryRef = MaybeMemory.getValue();
146   EXPECT_EQ(10, MemoryRef.length());
147   EXPECT_EQ(10, MemoryRef.size());
148   EXPECT_EQ(10 * sizeof(int), static_cast<size_t>(MemoryRef.byte_size()));
149   EXPECT_FALSE(MemoryRef.empty());
150 
151   // mutable span
152   acxxel::DeviceMemorySpan<int> MutableSpan = MemoryRef.asSpan();
153   testFullDeviceMemorySpan(MutableSpan, 10, sizeof(int));
154 
155   // const ref
156   const acxxel::DeviceMemory<int> &ConstMemoryRef = MaybeMemory.getValue();
157   EXPECT_EQ(10, ConstMemoryRef.length());
158   EXPECT_EQ(10, ConstMemoryRef.size());
159   EXPECT_EQ(10 * sizeof(int), static_cast<size_t>(ConstMemoryRef.byte_size()));
160   EXPECT_FALSE(ConstMemoryRef.empty());
161 
162   // immutable span
163   acxxel::DeviceMemorySpan<const int> ImmutableSpan = ConstMemoryRef.asSpan();
164   testFullDeviceMemorySpan(ImmutableSpan, 10, sizeof(int));
165 }
166 
TEST_P(AcxxelTest,CopyHostAndDevice)167 TEST_P(AcxxelTest, CopyHostAndDevice) {
168   acxxel::Platform *Platform = GetParam()().takeValue();
169   acxxel::Stream Stream = Platform->createStream().takeValue();
170   int A[] = {0, 1, 2};
171   std::array<int, arraySize(A)> B;
172   acxxel::DeviceMemory<int> X =
173       Platform->mallocD<int>(arraySize(A)).takeValue();
174   Stream.syncCopyHToD(A, X);
175   Stream.syncCopyDToH(X, B);
176   for (size_t I = 0; I < arraySize(A); ++I)
177     EXPECT_EQ(A[I], B[I]);
178   EXPECT_FALSE(Stream.takeStatus().isError());
179 }
180 
TEST_P(AcxxelTest,CopyDToD)181 TEST_P(AcxxelTest, CopyDToD) {
182   acxxel::Platform *Platform = GetParam()().takeValue();
183   acxxel::Stream Stream = Platform->createStream().takeValue();
184   int A[] = {0, 1, 2};
185   std::array<int, arraySize(A)> B;
186   acxxel::DeviceMemory<int> X =
187       Platform->mallocD<int>(arraySize(A)).takeValue();
188   acxxel::DeviceMemory<int> Y =
189       Platform->mallocD<int>(arraySize(A)).takeValue();
190   Stream.syncCopyHToD(A, X);
191   Stream.syncCopyDToD(X, Y);
192   Stream.syncCopyDToH(Y, B);
193   for (size_t I = 0; I < arraySize(A); ++I)
194     EXPECT_EQ(A[I], B[I]);
195   EXPECT_FALSE(Stream.takeStatus().isError());
196 }
197 
TEST_P(AcxxelTest,AsyncCopyHostAndDevice)198 TEST_P(AcxxelTest, AsyncCopyHostAndDevice) {
199   acxxel::Platform *Platform = GetParam()().takeValue();
200   int A[] = {0, 1, 2};
201   std::array<int, arraySize(A)> B;
202   acxxel::DeviceMemory<int> X =
203       Platform->mallocD<int>(arraySize(A)).takeValue();
204   acxxel::Stream Stream = Platform->createStream().takeValue();
205   acxxel::AsyncHostMemory<int> AsyncA =
206       Platform->registerHostMem(A).takeValue();
207   acxxel::AsyncHostMemory<int> AsyncB =
208       Platform->registerHostMem(B).takeValue();
209   EXPECT_FALSE(Stream.asyncCopyHToD(AsyncA, X).takeStatus().isError());
210   EXPECT_FALSE(Stream.asyncCopyDToH(X, AsyncB).takeStatus().isError());
211   EXPECT_FALSE(Stream.sync().isError());
212   for (size_t I = 0; I < arraySize(A); ++I)
213     EXPECT_EQ(A[I], B[I]);
214 }
215 
TEST_P(AcxxelTest,AsyncMemsetD)216 TEST_P(AcxxelTest, AsyncMemsetD) {
217   acxxel::Platform *Platform = GetParam()().takeValue();
218   constexpr size_t ArrayLength = 10;
219   std::array<uint32_t, ArrayLength> Host;
220   acxxel::DeviceMemory<uint32_t> X =
221       Platform->mallocD<uint32_t>(ArrayLength).takeValue();
222   acxxel::Stream Stream = Platform->createStream().takeValue();
223   acxxel::AsyncHostMemory<uint32_t> AsyncHost =
224       Platform->registerHostMem(Host).takeValue();
225   EXPECT_FALSE(Stream.asyncMemsetD(X, 0x12).takeStatus().isError());
226   EXPECT_FALSE(Stream.asyncCopyDToH(X, AsyncHost).takeStatus().isError());
227   EXPECT_FALSE(Stream.sync().isError());
228   for (size_t I = 0; I < ArrayLength; ++I)
229     EXPECT_EQ(0x12121212u, Host[I]);
230 }
231 
TEST_P(AcxxelTest,RegisterHostMem)232 TEST_P(AcxxelTest, RegisterHostMem) {
233   acxxel::Platform *Platform = GetParam()().takeValue();
234   auto Data = std::unique_ptr<int[]>(new int[3]);
235   acxxel::Expected<acxxel::AsyncHostMemory<const int>> MaybeAsyncHostMemory =
236       Platform->registerHostMem<int>({Data.get(), 3});
237   EXPECT_FALSE(MaybeAsyncHostMemory.isError())
238       << MaybeAsyncHostMemory.getError().getMessage();
239   acxxel::AsyncHostMemory<const int> AsyncHostMemory =
240       MaybeAsyncHostMemory.takeValue();
241   EXPECT_EQ(Data.get(), AsyncHostMemory.data());
242   EXPECT_EQ(3, AsyncHostMemory.size());
243 }
244 
245 struct RefCounter {
246   static int Count;
247 
RefCounter__anonb4bca4590111::RefCounter248   RefCounter() { ++Count; }
~RefCounter__anonb4bca4590111::RefCounter249   ~RefCounter() { --Count; }
250   RefCounter(const RefCounter &) = delete;
251   RefCounter &operator=(const RefCounter &) = delete;
252 };
253 
254 int RefCounter::Count;
255 
TEST_P(AcxxelTest,OwnedAsyncHost)256 TEST_P(AcxxelTest, OwnedAsyncHost) {
257   acxxel::Platform *Platform = GetParam()().takeValue();
258   RefCounter::Count = 0;
259   {
260     acxxel::OwnedAsyncHostMemory<RefCounter> A =
261         Platform->newAsyncHostMem<RefCounter>(3).takeValue();
262     EXPECT_EQ(3, RefCounter::Count);
263   }
264   EXPECT_EQ(0, RefCounter::Count);
265 }
266 
TEST_P(AcxxelTest,OwnedAsyncCopyHostAndDevice)267 TEST_P(AcxxelTest, OwnedAsyncCopyHostAndDevice) {
268   acxxel::Platform *Platform = GetParam()().takeValue();
269   size_t Length = 3;
270   acxxel::OwnedAsyncHostMemory<int> A =
271       Platform->newAsyncHostMem<int>(Length).takeValue();
272   for (size_t I = 0; I < Length; ++I)
273     A[I] = I;
274   acxxel::OwnedAsyncHostMemory<int> B =
275       Platform->newAsyncHostMem<int>(Length).takeValue();
276   acxxel::DeviceMemory<int> X = Platform->mallocD<int>(Length).takeValue();
277   acxxel::Stream Stream = Platform->createStream().takeValue();
278   EXPECT_FALSE(Stream.asyncCopyHToD(A, X).takeStatus().isError());
279   EXPECT_FALSE(Stream.asyncCopyDToH(X, B).takeStatus().isError());
280   EXPECT_FALSE(Stream.sync().isError());
281   for (size_t I = 0; I < Length; ++I)
282     EXPECT_EQ(A[I], B[I]);
283 }
284 
TEST_P(AcxxelTest,AsyncCopyDToD)285 TEST_P(AcxxelTest, AsyncCopyDToD) {
286   acxxel::Platform *Platform = GetParam()().takeValue();
287   int A[] = {0, 1, 2};
288   std::array<int, arraySize(A)> B;
289   acxxel::DeviceMemory<int> X =
290       Platform->mallocD<int>(arraySize(A)).takeValue();
291   acxxel::DeviceMemory<int> Y =
292       Platform->mallocD<int>(arraySize(A)).takeValue();
293   acxxel::Stream Stream = Platform->createStream().takeValue();
294   acxxel::AsyncHostMemory<int> AsyncA =
295       Platform->registerHostMem(A).takeValue();
296   acxxel::AsyncHostMemory<int> AsyncB =
297       Platform->registerHostMem(B).takeValue();
298   EXPECT_FALSE(Stream.asyncCopyHToD(AsyncA, X).takeStatus().isError());
299   EXPECT_FALSE(Stream.asyncCopyDToD(X, Y).takeStatus().isError());
300   EXPECT_FALSE(Stream.asyncCopyDToH(Y, AsyncB).takeStatus().isError());
301   EXPECT_FALSE(Stream.sync().isError());
302   for (size_t I = 0; I < arraySize(A); ++I)
303     EXPECT_EQ(A[I], B[I]);
304 }
305 
TEST_P(AcxxelTest,Stream)306 TEST_P(AcxxelTest, Stream) {
307   acxxel::Platform *Platform = GetParam()().takeValue();
308   acxxel::Stream Stream = Platform->createStream().takeValue();
309   EXPECT_FALSE(Stream.sync().isError());
310 }
311 
TEST_P(AcxxelTest,Event)312 TEST_P(AcxxelTest, Event) {
313   acxxel::Platform *Platform = GetParam()().takeValue();
314   acxxel::Event Event = Platform->createEvent().takeValue();
315   EXPECT_TRUE(Event.isDone());
316   EXPECT_FALSE(Event.sync().isError());
317 }
318 
TEST_P(AcxxelTest,RecordEventsInAStream)319 TEST_P(AcxxelTest, RecordEventsInAStream) {
320   acxxel::Platform *Platform = GetParam()().takeValue();
321   acxxel::Stream Stream = Platform->createStream().takeValue();
322   acxxel::Event Start = Platform->createEvent().takeValue();
323   acxxel::Event End = Platform->createEvent().takeValue();
324   EXPECT_FALSE(Stream.enqueueEvent(Start).takeStatus().isError());
325   EXPECT_FALSE(Start.sync().isError());
326   std::this_thread::sleep_for(std::chrono::milliseconds(10));
327   EXPECT_FALSE(Stream.enqueueEvent(End).takeStatus().isError());
328   EXPECT_FALSE(End.sync().isError());
329   EXPECT_GT(End.getSecondsSince(Start).takeValue(), 0);
330 }
331 
TEST_P(AcxxelTest,StreamCallback)332 TEST_P(AcxxelTest, StreamCallback) {
333   acxxel::Platform *Platform = GetParam()().takeValue();
334   int Value = 0;
335   acxxel::Stream Stream = Platform->createStream().takeValue();
336   EXPECT_FALSE(
337       Stream
338           .addCallback([&Value](acxxel::Stream &, const acxxel::Status &) {
339             Value = 42;
340           })
341           .takeStatus()
342           .isError());
343   EXPECT_FALSE(Stream.sync().isError());
344   EXPECT_EQ(42, Value);
345 }
346 
TEST_P(AcxxelTest,WaitForEventsInAStream)347 TEST_P(AcxxelTest, WaitForEventsInAStream) {
348   acxxel::Platform *Platform = GetParam()().takeValue();
349   acxxel::Stream Stream0 = Platform->createStream().takeValue();
350   acxxel::Stream Stream1 = Platform->createStream().takeValue();
351   acxxel::Event Event0 = Platform->createEvent().takeValue();
352   acxxel::Event Event1 = Platform->createEvent().takeValue();
353 
354   // Thread loops on Stream0 until someone sets the GoFlag, then set the
355   // MarkerFlag.
356 
357   std::mutex Mutex;
358   std::condition_variable ConditionVar;
359   bool GoFlag = false;
360   bool MarkerFlag = false;
361 
362   EXPECT_FALSE(Stream0
363                    .addCallback([&Mutex, &ConditionVar, &GoFlag, &MarkerFlag](
364                        acxxel::Stream &, const acxxel::Status &) {
365                      std::unique_lock<std::mutex> Lock(Mutex);
366                      ConditionVar.wait(Lock,
367                                        [&GoFlag] { return GoFlag == true; });
368                      MarkerFlag = true;
369                    })
370                    .takeStatus()
371                    .isError());
372 
373   // Event0 can only occur after GoFlag and MarkerFlag are set.
374   EXPECT_FALSE(Stream0.enqueueEvent(Event0).takeStatus().isError());
375 
376   // Use waitOnEvent to make a callback on Stream1 wait for an event on Stream0.
377   EXPECT_FALSE(Stream1.waitOnEvent(Event0).isError());
378   EXPECT_FALSE(Stream1.enqueueEvent(Event1).takeStatus().isError());
379   EXPECT_FALSE(Stream1
380                    .addCallback([&Mutex, &MarkerFlag](acxxel::Stream &,
381                                                       const acxxel::Status &) {
382                      std::unique_lock<std::mutex> Lock(Mutex);
383                      // This makes sure that this callback runs after the
384                      // callback on Stream0.
385                      EXPECT_TRUE(MarkerFlag);
386                    })
387                    .takeStatus()
388                    .isError());
389 
390   // Allow the callback on Stream0 to set MarkerFlag and finish.
391   {
392     std::unique_lock<std::mutex> Lock(Mutex);
393     GoFlag = true;
394   }
395   ConditionVar.notify_one();
396 
397   // Make sure the events have finished and that Event1 did not happen before
398   // Event0.
399   EXPECT_FALSE(Event0.sync().isError());
400   EXPECT_FALSE(Event1.sync().isError());
401   EXPECT_FALSE(Stream1.sync().isError());
402 }
403 
404 #if defined(ACXXEL_ENABLE_CUDA) || defined(ACXXEL_ENABLE_OPENCL)
405 INSTANTIATE_TEST_CASE_P(BothPlatformTest, AcxxelTest,
406                         ::testing::Values(
407 #ifdef ACXXEL_ENABLE_CUDA
408                             acxxel::getCUDAPlatform
409 #ifdef ACXXEL_ENABLE_OPENCL
410                             ,
411 #endif
412 #endif
413 #ifdef ACXXEL_ENABLE_OPENCL
414                             acxxel::getOpenCLPlatform
415 #endif
416                             ));
417 #endif
418 
419 } // namespace
420