//===--- acxxel_test.cpp - Tests for the Acxxel API -----------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "acxxel.h" #include "config.h" #include "gtest/gtest.h" #include #include #include #include namespace { template constexpr size_t arraySize(T (&)[N]) { return N; } using PlatformGetter = acxxel::Expected (*)(); class AcxxelTest : public ::testing::TestWithParam {}; TEST_P(AcxxelTest, GetDeviceCount) { acxxel::Platform *Platform = GetParam()().takeValue(); int DeviceCount = Platform->getDeviceCount().getValue(); EXPECT_GE(DeviceCount, 0); } // Tests all the methods of a DeviceMemorySpan that was created from the asSpan // method of a DeviceMemory object. // // The length is the number of elements in the span. The ElementByteSize is the // number of bytes per element in the span. // // It is assumed that the input span has 10 or more elements. template void testFullDeviceMemorySpan(SpanType &&Span, ptrdiff_t Length, ptrdiff_t ElementByteSize) { EXPECT_GE(Length, 10); EXPECT_GT(ElementByteSize, 0); // Full span EXPECT_EQ(Length, Span.length()); EXPECT_EQ(Length, Span.size()); EXPECT_EQ(Length * ElementByteSize, Span.byte_size()); EXPECT_EQ(0, Span.offset()); EXPECT_EQ(0, Span.byte_offset()); EXPECT_FALSE(Span.empty()); // Sub-span with first method. auto First2 = Span.first(2); EXPECT_EQ(2, First2.length()); EXPECT_EQ(2, First2.size()); EXPECT_EQ(2 * ElementByteSize, First2.byte_size()); EXPECT_EQ(0, First2.offset()); EXPECT_EQ(0, First2.byte_offset()); EXPECT_FALSE(First2.empty()); auto First0 = Span.first(0); EXPECT_EQ(0, First0.length()); EXPECT_EQ(0, First0.size()); EXPECT_EQ(0, First0.byte_size()); EXPECT_EQ(0, First0.offset()); EXPECT_EQ(0, First0.byte_offset()); EXPECT_TRUE(First0.empty()); // Sub-span with last method. auto Last2 = Span.last(2); EXPECT_EQ(2, Last2.length()); EXPECT_EQ(2, Last2.size()); EXPECT_EQ(2 * ElementByteSize, Last2.byte_size()); EXPECT_EQ(Length - 2, Last2.offset()); EXPECT_EQ((Length - 2) * ElementByteSize, Last2.byte_offset()); EXPECT_FALSE(Last2.empty()); auto Last0 = Span.last(0); EXPECT_EQ(0, Last0.length()); EXPECT_EQ(0, Last0.size()); EXPECT_EQ(0, Last0.byte_size()); EXPECT_EQ(Length, Last0.offset()); EXPECT_EQ(Length * ElementByteSize, Last0.byte_offset()); EXPECT_TRUE(Last0.empty()); // Sub-span with subspan method. auto Middle2 = Span.subspan(4, 2); EXPECT_EQ(2, Middle2.length()); EXPECT_EQ(2, Middle2.size()); EXPECT_EQ(2 * ElementByteSize, Middle2.byte_size()); EXPECT_EQ(4, Middle2.offset()); EXPECT_EQ(4 * ElementByteSize, Middle2.byte_offset()); EXPECT_FALSE(Middle2.empty()); auto Middle0 = Span.subspan(4, 0); EXPECT_EQ(0, Middle0.length()); EXPECT_EQ(0, Middle0.size()); EXPECT_EQ(0, Middle0.byte_size()); EXPECT_EQ(4, Middle0.offset()); EXPECT_EQ(4 * ElementByteSize, Middle0.byte_offset()); EXPECT_TRUE(Middle0.empty()); auto Subspan2AtStart = Span.subspan(0, 2); EXPECT_EQ(2, Subspan2AtStart.length()); EXPECT_EQ(2, Subspan2AtStart.size()); EXPECT_EQ(2 * ElementByteSize, Subspan2AtStart.byte_size()); EXPECT_EQ(0, Subspan2AtStart.offset()); EXPECT_EQ(0, Subspan2AtStart.byte_offset()); EXPECT_FALSE(Subspan2AtStart.empty()); auto Subspan2AtEnd = Span.subspan(Length - 2, 2); EXPECT_EQ(2, Subspan2AtEnd.length()); EXPECT_EQ(2, Subspan2AtEnd.size()); EXPECT_EQ(2 * ElementByteSize, Subspan2AtEnd.byte_size()); EXPECT_EQ(Length - 2, Subspan2AtEnd.offset()); EXPECT_EQ((Length - 2) * ElementByteSize, Subspan2AtEnd.byte_offset()); EXPECT_FALSE(Subspan2AtEnd.empty()); auto Subspan0AtStart = Span.subspan(0, 0); EXPECT_EQ(0, Subspan0AtStart.length()); EXPECT_EQ(0, Subspan0AtStart.size()); EXPECT_EQ(0, Subspan0AtStart.byte_size()); EXPECT_EQ(0, Subspan0AtStart.offset()); EXPECT_EQ(0, Subspan0AtStart.byte_offset()); EXPECT_TRUE(Subspan0AtStart.empty()); auto Subspan0AtEnd = Span.subspan(Length, 0); EXPECT_EQ(0, Subspan0AtEnd.length()); EXPECT_EQ(0, Subspan0AtEnd.size()); EXPECT_EQ(0, Subspan0AtEnd.byte_size()); EXPECT_EQ(Length, Subspan0AtEnd.offset()); EXPECT_EQ(Length * ElementByteSize, Subspan0AtEnd.byte_offset()); EXPECT_TRUE(Subspan0AtEnd.empty()); } TEST_P(AcxxelTest, DeviceMemory) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Expected> MaybeMemory = Platform->mallocD(10); EXPECT_FALSE(MaybeMemory.isError()); // ref acxxel::DeviceMemory &MemoryRef = MaybeMemory.getValue(); EXPECT_EQ(10, MemoryRef.length()); EXPECT_EQ(10, MemoryRef.size()); EXPECT_EQ(10 * sizeof(int), static_cast(MemoryRef.byte_size())); EXPECT_FALSE(MemoryRef.empty()); // mutable span acxxel::DeviceMemorySpan MutableSpan = MemoryRef.asSpan(); testFullDeviceMemorySpan(MutableSpan, 10, sizeof(int)); // const ref const acxxel::DeviceMemory &ConstMemoryRef = MaybeMemory.getValue(); EXPECT_EQ(10, ConstMemoryRef.length()); EXPECT_EQ(10, ConstMemoryRef.size()); EXPECT_EQ(10 * sizeof(int), static_cast(ConstMemoryRef.byte_size())); EXPECT_FALSE(ConstMemoryRef.empty()); // immutable span acxxel::DeviceMemorySpan ImmutableSpan = ConstMemoryRef.asSpan(); testFullDeviceMemorySpan(ImmutableSpan, 10, sizeof(int)); } TEST_P(AcxxelTest, CopyHostAndDevice) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); int A[] = {0, 1, 2}; std::array B; acxxel::DeviceMemory X = Platform->mallocD(arraySize(A)).takeValue(); Stream.syncCopyHToD(A, X); Stream.syncCopyDToH(X, B); for (size_t I = 0; I < arraySize(A); ++I) EXPECT_EQ(A[I], B[I]); EXPECT_FALSE(Stream.takeStatus().isError()); } TEST_P(AcxxelTest, CopyDToD) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); int A[] = {0, 1, 2}; std::array B; acxxel::DeviceMemory X = Platform->mallocD(arraySize(A)).takeValue(); acxxel::DeviceMemory Y = Platform->mallocD(arraySize(A)).takeValue(); Stream.syncCopyHToD(A, X); Stream.syncCopyDToD(X, Y); Stream.syncCopyDToH(Y, B); for (size_t I = 0; I < arraySize(A); ++I) EXPECT_EQ(A[I], B[I]); EXPECT_FALSE(Stream.takeStatus().isError()); } TEST_P(AcxxelTest, AsyncCopyHostAndDevice) { acxxel::Platform *Platform = GetParam()().takeValue(); int A[] = {0, 1, 2}; std::array B; acxxel::DeviceMemory X = Platform->mallocD(arraySize(A)).takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); acxxel::AsyncHostMemory AsyncA = Platform->registerHostMem(A).takeValue(); acxxel::AsyncHostMemory AsyncB = Platform->registerHostMem(B).takeValue(); EXPECT_FALSE(Stream.asyncCopyHToD(AsyncA, X).takeStatus().isError()); EXPECT_FALSE(Stream.asyncCopyDToH(X, AsyncB).takeStatus().isError()); EXPECT_FALSE(Stream.sync().isError()); for (size_t I = 0; I < arraySize(A); ++I) EXPECT_EQ(A[I], B[I]); } TEST_P(AcxxelTest, AsyncMemsetD) { acxxel::Platform *Platform = GetParam()().takeValue(); constexpr size_t ArrayLength = 10; std::array Host; acxxel::DeviceMemory X = Platform->mallocD(ArrayLength).takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); acxxel::AsyncHostMemory AsyncHost = Platform->registerHostMem(Host).takeValue(); EXPECT_FALSE(Stream.asyncMemsetD(X, 0x12).takeStatus().isError()); EXPECT_FALSE(Stream.asyncCopyDToH(X, AsyncHost).takeStatus().isError()); EXPECT_FALSE(Stream.sync().isError()); for (size_t I = 0; I < ArrayLength; ++I) EXPECT_EQ(0x12121212u, Host[I]); } TEST_P(AcxxelTest, RegisterHostMem) { acxxel::Platform *Platform = GetParam()().takeValue(); auto Data = std::unique_ptr(new int[3]); acxxel::Expected> MaybeAsyncHostMemory = Platform->registerHostMem({Data.get(), 3}); EXPECT_FALSE(MaybeAsyncHostMemory.isError()) << MaybeAsyncHostMemory.getError().getMessage(); acxxel::AsyncHostMemory AsyncHostMemory = MaybeAsyncHostMemory.takeValue(); EXPECT_EQ(Data.get(), AsyncHostMemory.data()); EXPECT_EQ(3, AsyncHostMemory.size()); } struct RefCounter { static int Count; RefCounter() { ++Count; } ~RefCounter() { --Count; } RefCounter(const RefCounter &) = delete; RefCounter &operator=(const RefCounter &) = delete; }; int RefCounter::Count; TEST_P(AcxxelTest, OwnedAsyncHost) { acxxel::Platform *Platform = GetParam()().takeValue(); RefCounter::Count = 0; { acxxel::OwnedAsyncHostMemory A = Platform->newAsyncHostMem(3).takeValue(); EXPECT_EQ(3, RefCounter::Count); } EXPECT_EQ(0, RefCounter::Count); } TEST_P(AcxxelTest, OwnedAsyncCopyHostAndDevice) { acxxel::Platform *Platform = GetParam()().takeValue(); size_t Length = 3; acxxel::OwnedAsyncHostMemory A = Platform->newAsyncHostMem(Length).takeValue(); for (size_t I = 0; I < Length; ++I) A[I] = I; acxxel::OwnedAsyncHostMemory B = Platform->newAsyncHostMem(Length).takeValue(); acxxel::DeviceMemory X = Platform->mallocD(Length).takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); EXPECT_FALSE(Stream.asyncCopyHToD(A, X).takeStatus().isError()); EXPECT_FALSE(Stream.asyncCopyDToH(X, B).takeStatus().isError()); EXPECT_FALSE(Stream.sync().isError()); for (size_t I = 0; I < Length; ++I) EXPECT_EQ(A[I], B[I]); } TEST_P(AcxxelTest, AsyncCopyDToD) { acxxel::Platform *Platform = GetParam()().takeValue(); int A[] = {0, 1, 2}; std::array B; acxxel::DeviceMemory X = Platform->mallocD(arraySize(A)).takeValue(); acxxel::DeviceMemory Y = Platform->mallocD(arraySize(A)).takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); acxxel::AsyncHostMemory AsyncA = Platform->registerHostMem(A).takeValue(); acxxel::AsyncHostMemory AsyncB = Platform->registerHostMem(B).takeValue(); EXPECT_FALSE(Stream.asyncCopyHToD(AsyncA, X).takeStatus().isError()); EXPECT_FALSE(Stream.asyncCopyDToD(X, Y).takeStatus().isError()); EXPECT_FALSE(Stream.asyncCopyDToH(Y, AsyncB).takeStatus().isError()); EXPECT_FALSE(Stream.sync().isError()); for (size_t I = 0; I < arraySize(A); ++I) EXPECT_EQ(A[I], B[I]); } TEST_P(AcxxelTest, Stream) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); EXPECT_FALSE(Stream.sync().isError()); } TEST_P(AcxxelTest, Event) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Event Event = Platform->createEvent().takeValue(); EXPECT_TRUE(Event.isDone()); EXPECT_FALSE(Event.sync().isError()); } TEST_P(AcxxelTest, RecordEventsInAStream) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Stream Stream = Platform->createStream().takeValue(); acxxel::Event Start = Platform->createEvent().takeValue(); acxxel::Event End = Platform->createEvent().takeValue(); EXPECT_FALSE(Stream.enqueueEvent(Start).takeStatus().isError()); EXPECT_FALSE(Start.sync().isError()); std::this_thread::sleep_for(std::chrono::milliseconds(10)); EXPECT_FALSE(Stream.enqueueEvent(End).takeStatus().isError()); EXPECT_FALSE(End.sync().isError()); EXPECT_GT(End.getSecondsSince(Start).takeValue(), 0); } TEST_P(AcxxelTest, StreamCallback) { acxxel::Platform *Platform = GetParam()().takeValue(); int Value = 0; acxxel::Stream Stream = Platform->createStream().takeValue(); EXPECT_FALSE( Stream .addCallback([&Value](acxxel::Stream &, const acxxel::Status &) { Value = 42; }) .takeStatus() .isError()); EXPECT_FALSE(Stream.sync().isError()); EXPECT_EQ(42, Value); } TEST_P(AcxxelTest, WaitForEventsInAStream) { acxxel::Platform *Platform = GetParam()().takeValue(); acxxel::Stream Stream0 = Platform->createStream().takeValue(); acxxel::Stream Stream1 = Platform->createStream().takeValue(); acxxel::Event Event0 = Platform->createEvent().takeValue(); acxxel::Event Event1 = Platform->createEvent().takeValue(); // Thread loops on Stream0 until someone sets the GoFlag, then set the // MarkerFlag. std::mutex Mutex; std::condition_variable ConditionVar; bool GoFlag = false; bool MarkerFlag = false; EXPECT_FALSE(Stream0 .addCallback([&Mutex, &ConditionVar, &GoFlag, &MarkerFlag]( acxxel::Stream &, const acxxel::Status &) { std::unique_lock Lock(Mutex); ConditionVar.wait(Lock, [&GoFlag] { return GoFlag == true; }); MarkerFlag = true; }) .takeStatus() .isError()); // Event0 can only occur after GoFlag and MarkerFlag are set. EXPECT_FALSE(Stream0.enqueueEvent(Event0).takeStatus().isError()); // Use waitOnEvent to make a callback on Stream1 wait for an event on Stream0. EXPECT_FALSE(Stream1.waitOnEvent(Event0).isError()); EXPECT_FALSE(Stream1.enqueueEvent(Event1).takeStatus().isError()); EXPECT_FALSE(Stream1 .addCallback([&Mutex, &MarkerFlag](acxxel::Stream &, const acxxel::Status &) { std::unique_lock Lock(Mutex); // This makes sure that this callback runs after the // callback on Stream0. EXPECT_TRUE(MarkerFlag); }) .takeStatus() .isError()); // Allow the callback on Stream0 to set MarkerFlag and finish. { std::unique_lock Lock(Mutex); GoFlag = true; } ConditionVar.notify_one(); // Make sure the events have finished and that Event1 did not happen before // Event0. EXPECT_FALSE(Event0.sync().isError()); EXPECT_FALSE(Event1.sync().isError()); EXPECT_FALSE(Stream1.sync().isError()); } #if defined(ACXXEL_ENABLE_CUDA) || defined(ACXXEL_ENABLE_OPENCL) INSTANTIATE_TEST_CASE_P(BothPlatformTest, AcxxelTest, ::testing::Values( #ifdef ACXXEL_ENABLE_CUDA acxxel::getCUDAPlatform #ifdef ACXXEL_ENABLE_OPENCL , #endif #endif #ifdef ACXXEL_ENABLE_OPENCL acxxel::getOpenCLPlatform #endif )); #endif } // namespace