to reflect the new license. We understand that people may be surprised that we're moving the header entirely to discuss the new license. We checked this carefully with the Foundation's lawyer and we believe this is the correct approach. Essentially, all code in the project is now made available by the LLVM project under our new license, so you will see that the license headers include that license only. Some of our contributors have contributed code under our old license, and accordingly, we have retained a copy of our old license notice in the top-level files in each project and repository. llvm-svn: 351636
420 lines
15 KiB
C++
420 lines
15 KiB
C++
//===--- 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 <chrono>
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
template <typename T, size_t N> constexpr size_t arraySize(T (&)[N]) {
|
|
return N;
|
|
}
|
|
|
|
using PlatformGetter = acxxel::Expected<acxxel::Platform *> (*)();
|
|
class AcxxelTest : public ::testing::TestWithParam<PlatformGetter> {};
|
|
|
|
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 <typename SpanType>
|
|
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<acxxel::DeviceMemory<int>> MaybeMemory =
|
|
Platform->mallocD<int>(10);
|
|
EXPECT_FALSE(MaybeMemory.isError());
|
|
|
|
// ref
|
|
acxxel::DeviceMemory<int> &MemoryRef = MaybeMemory.getValue();
|
|
EXPECT_EQ(10, MemoryRef.length());
|
|
EXPECT_EQ(10, MemoryRef.size());
|
|
EXPECT_EQ(10 * sizeof(int), static_cast<size_t>(MemoryRef.byte_size()));
|
|
EXPECT_FALSE(MemoryRef.empty());
|
|
|
|
// mutable span
|
|
acxxel::DeviceMemorySpan<int> MutableSpan = MemoryRef.asSpan();
|
|
testFullDeviceMemorySpan(MutableSpan, 10, sizeof(int));
|
|
|
|
// const ref
|
|
const acxxel::DeviceMemory<int> &ConstMemoryRef = MaybeMemory.getValue();
|
|
EXPECT_EQ(10, ConstMemoryRef.length());
|
|
EXPECT_EQ(10, ConstMemoryRef.size());
|
|
EXPECT_EQ(10 * sizeof(int), static_cast<size_t>(ConstMemoryRef.byte_size()));
|
|
EXPECT_FALSE(ConstMemoryRef.empty());
|
|
|
|
// immutable span
|
|
acxxel::DeviceMemorySpan<const int> 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<int, arraySize(A)> B;
|
|
acxxel::DeviceMemory<int> X =
|
|
Platform->mallocD<int>(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<int, arraySize(A)> B;
|
|
acxxel::DeviceMemory<int> X =
|
|
Platform->mallocD<int>(arraySize(A)).takeValue();
|
|
acxxel::DeviceMemory<int> Y =
|
|
Platform->mallocD<int>(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<int, arraySize(A)> B;
|
|
acxxel::DeviceMemory<int> X =
|
|
Platform->mallocD<int>(arraySize(A)).takeValue();
|
|
acxxel::Stream Stream = Platform->createStream().takeValue();
|
|
acxxel::AsyncHostMemory<int> AsyncA =
|
|
Platform->registerHostMem(A).takeValue();
|
|
acxxel::AsyncHostMemory<int> 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<uint32_t, ArrayLength> Host;
|
|
acxxel::DeviceMemory<uint32_t> X =
|
|
Platform->mallocD<uint32_t>(ArrayLength).takeValue();
|
|
acxxel::Stream Stream = Platform->createStream().takeValue();
|
|
acxxel::AsyncHostMemory<uint32_t> 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<int[]>(new int[3]);
|
|
acxxel::Expected<acxxel::AsyncHostMemory<const int>> MaybeAsyncHostMemory =
|
|
Platform->registerHostMem<int>({Data.get(), 3});
|
|
EXPECT_FALSE(MaybeAsyncHostMemory.isError())
|
|
<< MaybeAsyncHostMemory.getError().getMessage();
|
|
acxxel::AsyncHostMemory<const int> 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<RefCounter> A =
|
|
Platform->newAsyncHostMem<RefCounter>(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<int> A =
|
|
Platform->newAsyncHostMem<int>(Length).takeValue();
|
|
for (size_t I = 0; I < Length; ++I)
|
|
A[I] = I;
|
|
acxxel::OwnedAsyncHostMemory<int> B =
|
|
Platform->newAsyncHostMem<int>(Length).takeValue();
|
|
acxxel::DeviceMemory<int> X = Platform->mallocD<int>(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<int, arraySize(A)> B;
|
|
acxxel::DeviceMemory<int> X =
|
|
Platform->mallocD<int>(arraySize(A)).takeValue();
|
|
acxxel::DeviceMemory<int> Y =
|
|
Platform->mallocD<int>(arraySize(A)).takeValue();
|
|
acxxel::Stream Stream = Platform->createStream().takeValue();
|
|
acxxel::AsyncHostMemory<int> AsyncA =
|
|
Platform->registerHostMem(A).takeValue();
|
|
acxxel::AsyncHostMemory<int> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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
|