diff --git a/offload/unittests/CMakeLists.txt b/offload/unittests/CMakeLists.txt index 388d15f834b1d..6d165ffd4c53a 100644 --- a/offload/unittests/CMakeLists.txt +++ b/offload/unittests/CMakeLists.txt @@ -41,7 +41,7 @@ function(add_offload_test_device_code test_filename test_name) COMMAND ${CMAKE_C_COMPILER} --target=nvptx64-nvidia-cuda -march=${nvptx_arch} -nogpulib --cuda-path=${CUDA_ROOT} -flto ${ARGN} - -c ${SRC_PATH} -o ${output_file} + ${SRC_PATH} -o ${output_file} DEPENDS ${SRC_PATH} ) add_custom_target(${test_name}.nvptx64 DEPENDS ${output_file}) @@ -64,7 +64,7 @@ function(add_offload_test_device_code test_filename test_name) OUTPUT ${output_file} COMMAND ${CMAKE_C_COMPILER} --target=amdgcn-amd-amdhsa -mcpu=${amdgpu_arch} - -nogpulib -flto ${ARGN} -c ${SRC_PATH} -o ${output_file} + -nogpulib -flto ${ARGN} ${SRC_PATH} -o ${output_file} DEPENDS ${SRC_PATH} ) add_custom_target(${test_name}.amdgpu DEPENDS ${output_file}) @@ -106,16 +106,15 @@ function(add_conformance_test test_name) endif() add_executable(${target_name} ${files}) - add_dependencies(${target_name} ${PLUGINS_TEST_COMMON} ${test_name}.bin) - target_compile_definitions(${target_name} PRIVATE DEVICE_CODE_PATH="${CONFORMANCE_TEST_DEVICE_CODE_PATH}") + add_dependencies(${target_name} conformance_device_binaries) + target_compile_definitions(${target_name} + PRIVATE DEVICE_BINARY_DIR="${OFFLOAD_CONFORMANCE_DEVICE_BINARY_DIR}") target_link_libraries(${target_name} PRIVATE ${PLUGINS_TEST_COMMON} libc) - target_include_directories(${target_name} PRIVATE ${PLUGINS_TEST_INCLUDE}) set_target_properties(${target_name} PROPERTIES EXCLUDE_FROM_ALL TRUE) add_custom_target(offload.conformance.${test_name} COMMAND $ - DEPENDS ${target_name} - COMMENT "Running conformance test ${test_name}") + DEPENDS ${target_name}) add_dependencies(offload.conformance offload.conformance.${test_name}) endfunction() diff --git a/offload/unittests/Conformance/CMakeLists.txt b/offload/unittests/Conformance/CMakeLists.txt index bc3141757372a..ce0421553de05 100644 --- a/offload/unittests/Conformance/CMakeLists.txt +++ b/offload/unittests/Conformance/CMakeLists.txt @@ -1,8 +1,7 @@ add_custom_target(offload.conformance) -set(PLUGINS_TEST_COMMON LLVMOffload LLVMSupport) -set(PLUGINS_TEST_INCLUDE ${LIBOMPTARGET_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/common) +set(PLUGINS_TEST_COMMON MathTest) add_subdirectory(device_code) - -add_conformance_test(sin sin.cpp) +add_subdirectory(lib) +add_subdirectory(tests) diff --git a/offload/unittests/Conformance/device_code/CMakeLists.txt b/offload/unittests/Conformance/device_code/CMakeLists.txt index 223f04ccfb698..18f54b8dc5252 100644 --- a/offload/unittests/Conformance/device_code/CMakeLists.txt +++ b/offload/unittests/Conformance/device_code/CMakeLists.txt @@ -1,4 +1,4 @@ -# FIXME: Currently missing dependencies to build GPU portion automatically. -add_offload_test_device_code(sin.c sin) +add_offload_test_device_code(LLVMLibm.c llvm-libm -stdlib -fno-builtin) -set(OFFLOAD_TEST_DEVICE_CODE_PATH ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) +add_custom_target(conformance_device_binaries DEPENDS llvm-libm.bin) +set(OFFLOAD_CONFORMANCE_DEVICE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) diff --git a/offload/unittests/Conformance/device_code/LLVMLibm.c b/offload/unittests/Conformance/device_code/LLVMLibm.c new file mode 100644 index 0000000000000..fe5196a539455 --- /dev/null +++ b/offload/unittests/Conformance/device_code/LLVMLibm.c @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the implementation of the device kernels that wrap the +/// math functions from the llvm-libm provider. +/// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include + +typedef _Float16 float16; + +__gpu_kernel void hypotf16Kernel(const float16 *X, float16 *Y, float16 *Out, + size_t NumElements) { + uint32_t Index = + __gpu_num_threads_x() * __gpu_block_id_x() + __gpu_thread_id_x(); + + if (Index < NumElements) + Out[Index] = hypotf16(X[Index], Y[Index]); +} + +__gpu_kernel void logfKernel(const float *X, float *Out, size_t NumElements) { + uint32_t Index = + __gpu_num_threads_x() * __gpu_block_id_x() + __gpu_thread_id_x(); + + if (Index < NumElements) + Out[Index] = logf(X[Index]); +} diff --git a/offload/unittests/Conformance/device_code/sin.c b/offload/unittests/Conformance/device_code/sin.c deleted file mode 100644 index e969e60f352a2..0000000000000 --- a/offload/unittests/Conformance/device_code/sin.c +++ /dev/null @@ -1,4 +0,0 @@ -#include -#include - -__gpu_kernel void kernel(double *out) { *out = sin(*out); } diff --git a/offload/unittests/Conformance/include/mathtest/CommandLine.hpp b/offload/unittests/Conformance/include/mathtest/CommandLine.hpp new file mode 100644 index 0000000000000..b28594aafbcf2 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/CommandLine.hpp @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of custom command-line argument parsers +/// using llvm::cl. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_COMMANDLINE_HPP +#define MATHTEST_COMMANDLINE_HPP + +#include "mathtest/TestConfig.hpp" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" + +#include + +namespace llvm { +namespace cl { + +struct TestConfigsArg { + enum class Mode { Default, All, Explicit } Mode = Mode::Default; + llvm::SmallVector Explicit; +}; + +template <> class parser : public basic_parser { +public: + parser(Option &O) : basic_parser(O) {} + + static bool isAllowed(const mathtest::TestConfig &Config) { + static const llvm::SmallVector &AllTestConfigs = + mathtest::getAllTestConfigs(); + + return llvm::is_contained(AllTestConfigs, Config); + } + + bool parse(Option &O, StringRef ArgName, StringRef ArgValue, + TestConfigsArg &Val) { + ArgValue = ArgValue.trim(); + if (ArgValue.empty()) + return O.error( + "Expected '" + getValueName() + + "', but got an empty string. Omit the flag to use defaults"); + + if (ArgValue.equals_insensitive("all")) { + Val.Mode = TestConfigsArg::Mode::All; + return false; + } + + llvm::SmallVector Pairs; + ArgValue.split(Pairs, ',', /*MaxSplit=*/-1, /*KeepEmpty=*/false); + + Val.Mode = TestConfigsArg::Mode::Explicit; + Val.Explicit.clear(); + + for (StringRef Pair : Pairs) { + llvm::SmallVector Parts; + Pair.split(Parts, ':'); + + if (Parts.size() != 2) + return O.error("Expected ':', got '" + Pair + "'"); + + StringRef Provider = Parts[0].trim(); + StringRef Platform = Parts[1].trim(); + + if (Provider.empty() || Platform.empty()) + return O.error("Provider and platform must not be empty in '" + Pair + + "'"); + + mathtest::TestConfig Config = {Provider.str(), Platform.str()}; + if (!isAllowed(Config)) + return O.error("Invalid pair '" + Pair + "'"); + + Val.Explicit.push_back(Config); + } + + return false; + } + + StringRef getValueName() const override { + return "all|provider:platform[,provider:platform...]"; + } + + void printOptionDiff(const Option &O, const TestConfigsArg &V, OptVal Default, + size_t GlobalWidth) const { + printOptionNoValue(O, GlobalWidth); + } +}; +} // namespace cl +} // namespace llvm + +#endif // MATHTEST_COMMANDLINE_HPP diff --git a/offload/unittests/Conformance/include/mathtest/CommandLineExtras.hpp b/offload/unittests/Conformance/include/mathtest/CommandLineExtras.hpp new file mode 100644 index 0000000000000..e80fdbf179d6c --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/CommandLineExtras.hpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declaration of the command-line options and the main +/// interface for selecting test configurations. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_COMMANDLINEEXTRAS_HPP +#define MATHTEST_COMMANDLINEEXTRAS_HPP + +#include "mathtest/CommandLine.hpp" +#include "mathtest/TestConfig.hpp" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/CommandLine.h" + +namespace mathtest { +namespace cl { + +extern llvm::cl::opt IsVerbose; + +namespace detail { + +extern llvm::cl::opt TestConfigsOpt; +} // namespace detail + +const llvm::SmallVector &getTestConfigs(); +} // namespace cl +} // namespace mathtest + +#endif // MATHTEST_COMMANDLINEEXTRAS_HPP diff --git a/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp b/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp new file mode 100644 index 0000000000000..5c31fc3da53cd --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/DeviceContext.hpp @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the DeviceContext class, which serves +/// as the high-level interface to a particular device (GPU). +/// +/// This class provides methods for allocating buffers, loading binaries, and +/// getting and launching kernels on the device. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_DEVICECONTEXT_HPP +#define MATHTEST_DEVICECONTEXT_HPP + +#include "mathtest/DeviceResources.hpp" +#include "mathtest/ErrorHandling.hpp" +#include "mathtest/Support.hpp" + +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mathtest { + +const llvm::SetVector &getPlatforms(); + +namespace detail { + +void allocManagedMemory(ol_device_handle_t DeviceHandle, std::size_t Size, + void **AllocationOut) noexcept; +} // namespace detail + +class DeviceContext { + // For simplicity, the current design of this class doesn't have support for + // asynchronous operations and all types of memory allocation. + // + // Other use cases could benefit from operations like enqueued kernel launch + // and enqueued memcpy, as well as device and host memory allocation. + +public: + explicit DeviceContext(std::size_t GlobalDeviceId = 0); + + explicit DeviceContext(llvm::StringRef Platform, std::size_t DeviceId = 0); + + template + ManagedBuffer createManagedBuffer(std::size_t Size) const noexcept { + void *UntypedAddress = nullptr; + + detail::allocManagedMemory(DeviceHandle, Size * sizeof(T), &UntypedAddress); + T *TypedAddress = static_cast(UntypedAddress); + + return ManagedBuffer(TypedAddress, Size); + } + + [[nodiscard]] llvm::Expected> + loadBinary(llvm::StringRef Directory, llvm::StringRef BinaryName) const; + + template + [[nodiscard]] llvm::Expected> + getKernel(const std::shared_ptr &Image, + llvm::StringRef KernelName) const { + assert(Image && "Image provided to getKernel is null"); + + if (Image->DeviceHandle != DeviceHandle) + return llvm::createStringError( + "Image provided to getKernel was created for a different device"); + + auto ExpectedHandle = getKernelHandle(Image->Handle, KernelName); + + if (!ExpectedHandle) + return ExpectedHandle.takeError(); + + return DeviceKernel(Image, *ExpectedHandle); + } + + template + void launchKernel(DeviceKernel Kernel, uint32_t NumGroups, + uint32_t GroupSize, ArgTypes &&...Args) const noexcept { + using ExpectedTypes = + typename FunctionTypeTraits::ArgTypesTuple; + using ProvidedTypes = std::tuple...>; + + static_assert(std::is_same_v, + "Argument types provided to launchKernel do not match the " + "kernel's signature"); + + if (Kernel.Image->DeviceHandle != DeviceHandle) + FATAL_ERROR("Kernel provided to launchKernel was created for a different " + "device"); + + if constexpr (sizeof...(Args) == 0) { + launchKernelImpl(Kernel.Handle, NumGroups, GroupSize, nullptr, 0); + } else { + auto KernelArgs = makeKernelArgsPack(std::forward(Args)...); + + static_assert( + (std::is_trivially_copyable_v> && ...), + "Argument types provided to launchKernel must be trivially copyable"); + + launchKernelImpl(Kernel.Handle, NumGroups, GroupSize, &KernelArgs, + sizeof(KernelArgs)); + } + } + + [[nodiscard]] llvm::StringRef getName() const noexcept; + + [[nodiscard]] llvm::StringRef getPlatform() const noexcept; + +private: + [[nodiscard]] llvm::Expected + getKernelHandle(ol_program_handle_t ProgramHandle, + llvm::StringRef KernelName) const noexcept; + + void launchKernelImpl(ol_symbol_handle_t KernelHandle, uint32_t NumGroups, + uint32_t GroupSize, const void *KernelArgs, + std::size_t KernelArgsSize) const noexcept; + + std::size_t GlobalDeviceId; + ol_device_handle_t DeviceHandle; +}; +} // namespace mathtest + +#endif // MATHTEST_DEVICECONTEXT_HPP diff --git a/offload/unittests/Conformance/include/mathtest/DeviceResources.hpp b/offload/unittests/Conformance/include/mathtest/DeviceResources.hpp new file mode 100644 index 0000000000000..860448afa3a01 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/DeviceResources.hpp @@ -0,0 +1,144 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of wrappers that manage device resources +/// like buffers, binaries, and kernels. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_DEVICERESOURCES_HPP +#define MATHTEST_DEVICERESOURCES_HPP + +#include "mathtest/OffloadForward.hpp" + +#include "llvm/ADT/ArrayRef.h" + +#include +#include +#include + +namespace mathtest { + +class DeviceContext; + +namespace detail { + +void freeDeviceMemory(void *Address) noexcept; +} // namespace detail + +//===----------------------------------------------------------------------===// +// ManagedBuffer +//===----------------------------------------------------------------------===// + +template class [[nodiscard]] ManagedBuffer { +public: + ~ManagedBuffer() noexcept { + if (Address) + detail::freeDeviceMemory(Address); + } + + ManagedBuffer(const ManagedBuffer &) = delete; + ManagedBuffer &operator=(const ManagedBuffer &) = delete; + + ManagedBuffer(ManagedBuffer &&Other) noexcept + : Address(Other.Address), Size(Other.Size) { + Other.Address = nullptr; + Other.Size = 0; + } + + ManagedBuffer &operator=(ManagedBuffer &&Other) noexcept { + if (this == &Other) + return *this; + + if (Address) + detail::freeDeviceMemory(Address); + + Address = Other.Address; + Size = Other.Size; + + Other.Address = nullptr; + Other.Size = 0; + + return *this; + } + + [[nodiscard]] T *data() noexcept { return Address; } + + [[nodiscard]] const T *data() const noexcept { return Address; } + + [[nodiscard]] std::size_t getSize() const noexcept { return Size; } + + [[nodiscard]] operator llvm::MutableArrayRef() noexcept { + return llvm::MutableArrayRef(data(), getSize()); + } + + [[nodiscard]] operator llvm::ArrayRef() const noexcept { + return llvm::ArrayRef(data(), getSize()); + } + +private: + friend class DeviceContext; + + explicit ManagedBuffer(T *Address, std::size_t Size) noexcept + : Address(Address), Size(Size) {} + + T *Address = nullptr; + std::size_t Size = 0; +}; + +//===----------------------------------------------------------------------===// +// DeviceImage +//===----------------------------------------------------------------------===// + +class [[nodiscard]] DeviceImage { +public: + ~DeviceImage() noexcept; + DeviceImage &operator=(DeviceImage &&Other) noexcept; + + DeviceImage(const DeviceImage &) = delete; + DeviceImage &operator=(const DeviceImage &) = delete; + + DeviceImage(DeviceImage &&Other) noexcept; + +private: + friend class DeviceContext; + + explicit DeviceImage(ol_device_handle_t DeviceHandle, + ol_program_handle_t Handle) noexcept; + + ol_device_handle_t DeviceHandle = nullptr; + ol_program_handle_t Handle = nullptr; +}; + +//===----------------------------------------------------------------------===// +// DeviceKernel +//===----------------------------------------------------------------------===// + +template class [[nodiscard]] DeviceKernel { +public: + DeviceKernel() = delete; + + DeviceKernel(const DeviceKernel &) = default; + DeviceKernel &operator=(const DeviceKernel &) = default; + DeviceKernel(DeviceKernel &&) noexcept = default; + DeviceKernel &operator=(DeviceKernel &&) noexcept = default; + +private: + friend class DeviceContext; + + explicit DeviceKernel(std::shared_ptr Image, + ol_symbol_handle_t Kernel) + : Image(std::move(Image)), Handle(Kernel) {} + + std::shared_ptr Image; + ol_symbol_handle_t Handle = nullptr; +}; +} // namespace mathtest + +#endif // MATHTEST_DEVICERESOURCES_HPP diff --git a/offload/unittests/Conformance/include/mathtest/ErrorHandling.hpp b/offload/unittests/Conformance/include/mathtest/ErrorHandling.hpp new file mode 100644 index 0000000000000..7ec276227acc3 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/ErrorHandling.hpp @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of error handling macros for reporting +/// fatal error conditions and validating Offload API calls. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_ERRORHANDLING_HPP +#define MATHTEST_ERRORHANDLING_HPP + +#include "mathtest/OffloadForward.hpp" + +#include "llvm/ADT/Twine.h" + +#define FATAL_ERROR(Message) \ + mathtest::detail::reportFatalError(Message, __FILE__, __LINE__, __func__) + +#define OL_CHECK(ResultExpr) \ + do { \ + ol_result_t Result = (ResultExpr); \ + if (Result != OL_SUCCESS) \ + mathtest::detail::reportOffloadError(#ResultExpr, Result, __FILE__, \ + __LINE__, __func__); \ + \ + } while (false) + +namespace mathtest { +namespace detail { + +[[noreturn]] void reportFatalError(const llvm::Twine &Message, const char *File, + int Line, const char *FuncName); + +[[noreturn]] void reportOffloadError(const char *ResultExpr, ol_result_t Result, + const char *File, int Line, + const char *FuncName); +} // namespace detail +} // namespace mathtest + +#endif // MATHTEST_ERRORHANDLING_HPP diff --git a/offload/unittests/Conformance/include/mathtest/ExhaustiveGenerator.hpp b/offload/unittests/Conformance/include/mathtest/ExhaustiveGenerator.hpp new file mode 100644 index 0000000000000..6f7f7a9b665d0 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/ExhaustiveGenerator.hpp @@ -0,0 +1,140 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the ExhaustiveGenerator class, a +/// concrete input generator that exhaustively creates inputs from a given +/// sequence of ranges. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_EXHAUSTIVEGENERATOR_HPP +#define MATHTEST_EXHAUSTIVEGENERATOR_HPP + +#include "mathtest/IndexedRange.hpp" +#include "mathtest/InputGenerator.hpp" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Parallel.h" + +#include +#include +#include +#include +#include +#include + +namespace mathtest { + +template +class [[nodiscard]] ExhaustiveGenerator final + : public InputGenerator { + static constexpr std::size_t NumInputs = sizeof...(InTypes); + static_assert(NumInputs > 0, "The number of inputs must be at least 1"); + +public: + explicit constexpr ExhaustiveGenerator( + const IndexedRange &...Ranges) noexcept + : RangesTuple(Ranges...) { + bool Overflowed = getSizeWithOverflow(Ranges..., Size); + + assert(!Overflowed && "The input space size is too large"); + assert((Size > 0) && "The input space size must be at least 1"); + + IndexArrayType DimSizes = {}; + std::size_t DimIndex = 0; + ((DimSizes[DimIndex++] = Ranges.getSize()), ...); + + Strides[NumInputs - 1] = 1; + if constexpr (NumInputs > 1) + for (int Index = static_cast(NumInputs) - 2; Index >= 0; --Index) + Strides[Index] = Strides[Index + 1] * DimSizes[Index + 1]; + } + + void reset() noexcept override { NextFlatIndex = 0; } + + [[nodiscard]] std::size_t + fill(llvm::MutableArrayRef... Buffers) noexcept override { + const std::array BufferSizes = {Buffers.size()...}; + const std::size_t BufferSize = BufferSizes[0]; + assert((BufferSize != 0) && "Buffer size cannot be zero"); + assert(std::all_of(BufferSizes.begin(), BufferSizes.end(), + [&](std::size_t Size) { return Size == BufferSize; }) && + "All input buffers must have the same size"); + + if (NextFlatIndex >= Size) + return 0; + + const auto BatchSize = std::min(BufferSize, Size - NextFlatIndex); + const auto CurrentFlatIndex = NextFlatIndex; + NextFlatIndex += BatchSize; + + auto BufferPtrsTuple = std::make_tuple(Buffers.data()...); + + llvm::parallelFor(0, BatchSize, [&](std::size_t Offset) { + writeInputs(CurrentFlatIndex, Offset, BufferPtrsTuple); + }); + + return static_cast(BatchSize); + } + +private: + using RangesTupleType = std::tuple...>; + using IndexArrayType = std::array; + + static bool getSizeWithOverflow(const IndexedRange &...Ranges, + uint64_t &Size) noexcept { + Size = 1; + bool Overflowed = false; + + auto Multiplier = [&](const uint64_t RangeSize) { + if (!Overflowed) + Overflowed = __builtin_mul_overflow(Size, RangeSize, &Size); + }; + + (Multiplier(Ranges.getSize()), ...); + + return Overflowed; + } + + template + void writeInputs(uint64_t CurrentFlatIndex, uint64_t Offset, + BufferPtrsTupleType BufferPtrsTuple) const noexcept { + auto NDIndex = getNDIndex(CurrentFlatIndex + Offset); + writeInputsImpl<0>(NDIndex, Offset, BufferPtrsTuple); + } + + constexpr IndexArrayType getNDIndex(uint64_t FlatIndex) const noexcept { + IndexArrayType NDIndex; + + for (std::size_t Index = 0; Index < NumInputs; ++Index) { + NDIndex[Index] = FlatIndex / Strides[Index]; + FlatIndex -= NDIndex[Index] * Strides[Index]; + } + + return NDIndex; + } + + template + void writeInputsImpl(IndexArrayType NDIndex, uint64_t Offset, + BufferPtrsTupleType BufferPtrsTuple) const noexcept { + if constexpr (Index < NumInputs) { + const auto &Range = std::get(RangesTuple); + std::get(BufferPtrsTuple)[Offset] = Range[NDIndex[Index]]; + writeInputsImpl(NDIndex, Offset, BufferPtrsTuple); + } + } + + uint64_t Size = 1; + RangesTupleType RangesTuple; + IndexArrayType Strides = {}; + uint64_t NextFlatIndex = 0; +}; +} // namespace mathtest + +#endif // MATHTEST_EXHAUSTIVEGENERATOR_HPP diff --git a/offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp b/offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp new file mode 100644 index 0000000000000..b88d6e9aebdc8 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the GpuMathTest class, a test harness +/// that orchestrates running a math function on a device (GPU) and verifying +/// its results. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_GPUMATHTEST_HPP +#define MATHTEST_GPUMATHTEST_HPP + +#include "mathtest/DeviceContext.hpp" +#include "mathtest/DeviceResources.hpp" +#include "mathtest/HostRefChecker.hpp" +#include "mathtest/InputGenerator.hpp" +#include "mathtest/Support.hpp" +#include "mathtest/TestResult.hpp" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace mathtest { + +template > +class [[nodiscard]] GpuMathTest final { + using FunctionTraits = FunctionTraits; + using OutType = typename FunctionTraits::ReturnType; + using InTypesTuple = typename FunctionTraits::ArgTypesTuple; + + template + using PartialResultType = TestResult; + using KernelSignature = KernelSignatureOf_t; + + template + using TypeIdentitiesTuple = std::tuple...>; + using InTypeIdentitiesTuple = + ApplyTupleTypes_t; + + static constexpr std::size_t DefaultBufferSize = + DefaultBufferSizeFor_v; + static constexpr uint32_t DefaultGroupSize = 512; + +public: + using FunctionConfig = FunctionConfig; + using ResultType = ApplyTupleTypes_t; + using GeneratorType = ApplyTupleTypes_t; + + [[nodiscard]] static llvm::Expected + create(std::shared_ptr Context, llvm::StringRef Provider, + llvm::StringRef DeviceBinaryDir) { + assert(Context && "Context must not be null"); + + auto ExpectedKernel = getKernel(*Context, Provider, DeviceBinaryDir); + if (!ExpectedKernel) + return ExpectedKernel.takeError(); + + return GpuMathTest(std::move(Context), Provider, *ExpectedKernel); + } + + ResultType run(GeneratorType &Generator, + std::size_t BufferSize = DefaultBufferSize, + uint32_t GroupSize = DefaultGroupSize) const noexcept { + assert(BufferSize > 0 && "Buffer size must be a positive value"); + assert(GroupSize > 0 && "Group size must be a positive value"); + + auto [InBuffersTuple, OutBuffer] = createBuffers(BufferSize); + ResultType FinalResult; + + while (true) { + const std::size_t BatchSize = std::apply( + [&](auto &...Buffers) { return Generator.fill(Buffers...); }, + InBuffersTuple); + + if (BatchSize == 0) + break; + + const auto BatchResult = + processBatch(InBuffersTuple, OutBuffer, BatchSize, GroupSize); + + FinalResult.accumulate(BatchResult); + } + + return FinalResult; + } + + [[nodiscard]] std::shared_ptr getContext() const noexcept { + return Context; + } + + [[nodiscard]] std::string getProvider() const noexcept { return Provider; } + +private: + explicit GpuMathTest(std::shared_ptr Context, + llvm::StringRef Provider, + DeviceKernel Kernel) + : Context(std::move(Context)), Provider(Provider), Kernel(Kernel) {} + + static llvm::Expected> + getKernel(const DeviceContext &Context, llvm::StringRef Provider, + llvm::StringRef DeviceBinaryDir) { + llvm::StringRef BinaryName = Provider; + + auto ExpectedImage = Context.loadBinary(DeviceBinaryDir, BinaryName); + if (!ExpectedImage) + return ExpectedImage.takeError(); + + auto ExpectedKernel = Context.getKernel( + *ExpectedImage, FunctionConfig::KernelName); + if (!ExpectedKernel) + return ExpectedKernel.takeError(); + + return *ExpectedKernel; + } + + [[nodiscard]] auto createBuffers(std::size_t BufferSize) const { + auto InBuffersTuple = std::apply( + [&](auto... InTypeIdentities) { + return std::make_tuple( + Context->createManagedBuffer< + typename decltype(InTypeIdentities)::type>(BufferSize)...); + }, + InTypeIdentitiesTuple{}); + auto OutBuffer = Context->createManagedBuffer(BufferSize); + + return std::make_pair(std::move(InBuffersTuple), std::move(OutBuffer)); + } + + template + [[nodiscard]] ResultType + processBatch(const InBuffersTupleType &InBuffersTuple, + ManagedBuffer &OutBuffer, std::size_t BatchSize, + uint32_t GroupSize) const noexcept { + const uint32_t NumGroups = (BatchSize + GroupSize - 1) / GroupSize; + const auto KernelArgsTuple = std::apply( + [&](const auto &...InBuffers) { + return std::make_tuple(InBuffers.data()..., OutBuffer.data(), + BatchSize); + }, + InBuffersTuple); + + std::apply( + [&](const auto &...KernelArgs) { + Context->launchKernel(Kernel, NumGroups, GroupSize, KernelArgs...); + }, + KernelArgsTuple); + + return check(InBuffersTuple, OutBuffer, BatchSize); + } + + template + [[nodiscard]] static ResultType + check(const InBuffersTupleType &InBuffersTuple, + const ManagedBuffer &OutBuffer, + std::size_t BatchSize) noexcept { + const auto InViewsTuple = std::apply( + [&](auto &...InBuffers) { + return std::make_tuple( + llvm::ArrayRef(InBuffers.data(), BatchSize)...); + }, + InBuffersTuple); + const auto OutView = llvm::ArrayRef(OutBuffer.data(), BatchSize); + + return Checker::check(InViewsTuple, OutView); + } + + std::shared_ptr Context; + std::string Provider; + DeviceKernel Kernel; +}; +} // namespace mathtest + +#endif // MATHTEST_GPUMATHTEST_HPP diff --git a/offload/unittests/Conformance/include/mathtest/HostRefChecker.hpp b/offload/unittests/Conformance/include/mathtest/HostRefChecker.hpp new file mode 100644 index 0000000000000..488aefda67ef4 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/HostRefChecker.hpp @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the HostRefChecker class, which +/// verifies the results of a device computation against a reference +/// implementation on the host. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_HOSTREFCHECKER_HPP +#define MATHTEST_HOSTREFCHECKER_HPP + +#include "mathtest/Numerics.hpp" +#include "mathtest/Support.hpp" +#include "mathtest/TestResult.hpp" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Sequence.h" +#include "llvm/Support/Parallel.h" + +#include +#include +#include + +namespace mathtest { + +template class HostRefChecker { + using FunctionTraits = FunctionTraits; + using InTypesTuple = typename FunctionTraits::ArgTypesTuple; + + using FunctionConfig = FunctionConfig; + + template + using BuffersTupleType = std::tuple...>; + +public: + using OutType = typename FunctionTraits::ReturnType; + +private: + template + using PartialResultType = TestResult; + +public: + using ResultType = ApplyTupleTypes_t; + using InBuffersTupleType = ApplyTupleTypes_t; + + HostRefChecker() = delete; + + static ResultType check(InBuffersTupleType InBuffersTuple, + llvm::ArrayRef OutBuffer) noexcept { + const std::size_t BufferSize = OutBuffer.size(); + std::apply( + [&](const auto &...InBuffers) { + assert( + ((InBuffers.size() == BufferSize) && ...) && + "All input buffers must have the same size as the output buffer"); + }, + InBuffersTuple); + + assert((BufferSize != 0) && "Buffer size cannot be zero"); + + ResultType Init; + + auto Transform = [&](std::size_t Index) { + auto CurrentInputsTuple = std::apply( + [&](const auto &...InBuffers) { + return std::make_tuple(InBuffers[Index]...); + }, + InBuffersTuple); + + const OutType Actual = OutBuffer[Index]; + const OutType Expected = std::apply(Func, CurrentInputsTuple); + + const auto UlpDistance = computeUlpDistance(Actual, Expected); + const bool IsFailure = UlpDistance > FunctionConfig::UlpTolerance; + + return ResultType(UlpDistance, IsFailure, + typename ResultType::TestCase( + std::move(CurrentInputsTuple), Actual, Expected)); + }; + + auto Reduce = [](ResultType A, const ResultType &B) { + A.accumulate(B); + return A; + }; + + const auto Indexes = llvm::seq(BufferSize); + return llvm::parallelTransformReduce(Indexes.begin(), Indexes.end(), Init, + Reduce, Transform); + } +}; +} // namespace mathtest + +#endif // MATHTEST_HOSTREFCHECKER_HPP diff --git a/offload/unittests/Conformance/include/mathtest/IndexedRange.hpp b/offload/unittests/Conformance/include/mathtest/IndexedRange.hpp new file mode 100644 index 0000000000000..24aa00f6c6d7d --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/IndexedRange.hpp @@ -0,0 +1,110 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the IndexedRange class, which provides +/// an indexable view over a contiguous range of numeric values. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_INDEXEDRANGE_HPP +#define MATHTEST_INDEXEDRANGE_HPP + +#include "mathtest/Numerics.hpp" + +#include "llvm/Support/MathExtras.h" + +#include +#include +#include +#include + +namespace mathtest { + +template class [[nodiscard]] IndexedRange { + static_assert(IsFloatingPoint_v || std::is_integral_v, + "Type T must be an integral or floating-point type"); + static_assert(sizeof(T) <= sizeof(uint64_t), + "Type T must be no wider than uint64_t"); + +public: + constexpr IndexedRange() noexcept + : IndexedRange(getMinOrNegInf(), getMaxOrInf(), true) {} + + explicit constexpr IndexedRange(T Begin, T End, bool Inclusive) noexcept + : MappedBegin(mapToOrderedUnsigned(Begin)), + MappedEnd(mapToOrderedUnsigned(End)) { + if (Inclusive) { + assert((Begin <= End) && "Begin must be less than or equal to End"); + } else { + assert((Begin < End) && "Begin must be less than End"); + --MappedEnd; + } + + assert(((MappedEnd - MappedBegin) < std::numeric_limits::max()) && + "The range is too large to index"); + } + + [[nodiscard]] constexpr uint64_t getSize() const noexcept { + return static_cast(MappedEnd) - MappedBegin + 1; + } + + [[nodiscard]] constexpr T operator[](uint64_t Index) const noexcept { + assert((Index < getSize()) && "Index is out of range"); + + StorageType MappedValue = MappedBegin + Index; + return mapFromOrderedUnsigned(MappedValue); + } + +private: + using StorageType = StorageTypeOf_t; + + // Linearise T values into an ordered unsigned space: + // * The mapping is monotonic: a >= b if, and only if, map(a) >= map(b). + // * The difference |map(a) − map(b)| equals the number of representable + // values between a and b within the same type. + static constexpr StorageType mapToOrderedUnsigned(T Value) { + if constexpr (IsFloatingPoint_v) { + constexpr StorageType SignMask = FPBits::SIGN_MASK; + StorageType Unsigned = FPBits(Value).uintval(); + return (Unsigned & SignMask) ? SignMask - (Unsigned - SignMask) - 1 + : SignMask + Unsigned; + } + + if constexpr (std::is_signed_v) { + constexpr StorageType SignMask = llvm::maskLeadingOnes(1); + return __builtin_bit_cast(StorageType, Value) ^ SignMask; + } + + return Value; + } + + static constexpr T mapFromOrderedUnsigned(StorageType MappedValue) { + if constexpr (IsFloatingPoint_v) { + constexpr StorageType SignMask = FPBits::SIGN_MASK; + StorageType Unsigned = (MappedValue < SignMask) + ? (SignMask - MappedValue) + SignMask - 1 + : MappedValue - SignMask; + + return FPBits(Unsigned).get_val(); + } + + if constexpr (std::is_signed_v) { + constexpr StorageType SignMask = llvm::maskLeadingOnes(1); + return __builtin_bit_cast(T, MappedValue ^ SignMask); + } + + return MappedValue; + } + + StorageType MappedBegin; + StorageType MappedEnd; +}; +} // namespace mathtest + +#endif // MATHTEST_INDEXEDRANGE_HPP diff --git a/offload/unittests/Conformance/include/mathtest/InputGenerator.hpp b/offload/unittests/Conformance/include/mathtest/InputGenerator.hpp new file mode 100644 index 0000000000000..0154d0b024762 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/InputGenerator.hpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of the InputGenerator class, which defines +/// the abstract interface for classes that generate math test inputs. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_INPUTGENERATOR_HPP +#define MATHTEST_INPUTGENERATOR_HPP + +#include "llvm/ADT/ArrayRef.h" + +namespace mathtest { + +template class InputGenerator { +public: + virtual ~InputGenerator() noexcept = default; + + virtual void reset() noexcept = 0; + + [[nodiscard]] virtual size_t + fill(llvm::MutableArrayRef... Buffers) noexcept = 0; +}; +} // namespace mathtest + +#endif // MATHTEST_INPUTGENERATOR_HPP diff --git a/offload/unittests/Conformance/include/mathtest/Numerics.hpp b/offload/unittests/Conformance/include/mathtest/Numerics.hpp new file mode 100644 index 0000000000000..8b0e900a4c823 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/Numerics.hpp @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of numeric utilities, including functions +/// to compute ULP distance and traits for floating-point types. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_NUMERICS_HPP +#define MATHTEST_NUMERICS_HPP + +#include "mathtest/Support.hpp" +#include "mathtest/TypeExtras.hpp" + +// These headers are in the shared LLVM-libc header library +#include "shared/fp_bits.h" +#include "shared/sign.h" + +#include +#include +#include +#include +#include + +namespace mathtest { + +template +using FPBits = LIBC_NAMESPACE::shared::FPBits; + +using Sign = LIBC_NAMESPACE::shared::Sign; + +//===----------------------------------------------------------------------===// +// Type Traits +//===----------------------------------------------------------------------===// + +template struct IsFloatingPoint : std::is_floating_point {}; + +template <> struct IsFloatingPoint : std::true_type {}; + +template +inline constexpr bool IsFloatingPoint_v // NOLINT(readability-identifier-naming) + = IsFloatingPoint::value; + +template struct StorageTypeOf { +private: + static constexpr auto getStorageType() noexcept { + if constexpr (IsFloatingPoint_v) + return TypeIdentityOf::StorageType>{}; + else if constexpr (std::is_unsigned_v) + return TypeIdentityOf{}; + else if constexpr (std::is_signed_v) + return TypeIdentityOf>{}; + else + static_assert(!std::is_same_v, "Unsupported type"); + } + +public: + using type = typename decltype(getStorageType())::type; +}; + +template using StorageTypeOf_t = typename StorageTypeOf::type; + +//===----------------------------------------------------------------------===// +// Numeric Functions +//===----------------------------------------------------------------------===// + +template [[nodiscard]] constexpr T getMinOrNegInf() noexcept { + if constexpr (IsFloatingPoint_v) { + // All currently supported floating-point types have infinity + return FPBits::inf(Sign::NEG).get_val(); + } else { + static_assert(std::is_integral_v, + "Type T must be an integral or floating-point type"); + + return std::numeric_limits::lowest(); + } +} + +template [[nodiscard]] constexpr T getMaxOrInf() noexcept { + if constexpr (IsFloatingPoint_v) { + // All currently supported floating-point types have infinity + return FPBits::inf(Sign::POS).get_val(); + } else { + static_assert(std::is_integral_v, + "Type T must be an integral or floating-point type"); + + return std::numeric_limits::max(); + } +} + +template +[[nodiscard]] uint64_t computeUlpDistance(FloatType X, FloatType Y) noexcept { + static_assert(IsFloatingPoint_v, + "FloatType must be a floating-point type"); + using FPBits = FPBits; + using StorageType = typename FPBits::StorageType; + + const FPBits XBits(X); + const FPBits YBits(Y); + + if (X == Y) { + if (XBits.sign() != YBits.sign()) [[unlikely]] { + // When X == Y, different sign bits imply that X and Y are +0.0 and -0.0 + // (in any order). Since we want to treat them as unequal in the context + // of accuracy testing of mathematical functions, we return the smallest + // non-zero value. + return 1; + } + return 0; + } + + const bool XIsNaN = XBits.is_nan(); + const bool YIsNaN = YBits.is_nan(); + + if (XIsNaN && YIsNaN) + return 0; + + if (XIsNaN || YIsNaN) + return std::numeric_limits::max(); + + constexpr StorageType SignMask = FPBits::SIGN_MASK; + + // Linearise FloatType values into an ordered unsigned space. Let a and b + // be bits(x), bits(y), respectively, where x and y are FloatType values. + // * The mapping is monotonic: x >= y if, and only if, map(a) >= map(b). + // * The difference |map(a) − map(b)| equals the number of std::nextafter + // steps between a and b within the same type. + auto MapToOrderedUnsigned = [](FPBits Bits) { + const StorageType Unsigned = Bits.uintval(); + return (Unsigned & SignMask) ? SignMask - (Unsigned - SignMask) + : SignMask + Unsigned; + }; + + const StorageType MappedX = MapToOrderedUnsigned(XBits); + const StorageType MappedY = MapToOrderedUnsigned(YBits); + return static_cast(MappedX > MappedY ? MappedX - MappedY + : MappedY - MappedX); +} +} // namespace mathtest + +#endif // MATHTEST_NUMERICS_HPP diff --git a/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp b/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp new file mode 100644 index 0000000000000..788989a0d4211 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/OffloadForward.hpp @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains forward declarations for the opaque types and handles +/// used by the Offload API. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_OFFLOADFORWARD_HPP +#define MATHTEST_OFFLOADFORWARD_HPP + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct ol_error_struct_t; +typedef const ol_error_struct_t *ol_result_t; +#define OL_SUCCESS (static_cast(nullptr)) + +struct ol_device_impl_t; +typedef struct ol_device_impl_t *ol_device_handle_t; + +struct ol_program_impl_t; +typedef struct ol_program_impl_t *ol_program_handle_t; + +struct ol_symbol_impl_t; +typedef struct ol_symbol_impl_t *ol_symbol_handle_t; + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // MATHTEST_OFFLOADFORWARD_HPP diff --git a/offload/unittests/Conformance/include/mathtest/Support.hpp b/offload/unittests/Conformance/include/mathtest/Support.hpp new file mode 100644 index 0000000000000..2d3dceef2a230 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/Support.hpp @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the definition of various metaprogramming helpers and +/// support utilities for the math test framework. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_SUPPORT_HPP +#define MATHTEST_SUPPORT_HPP + +#include +#include +#include +#include + +namespace mathtest { + +//===----------------------------------------------------------------------===// +// Function & Type Traits +//===----------------------------------------------------------------------===// + +namespace detail { + +template struct FunctionTraitsImpl; + +template +struct FunctionTraitsImpl { + using ReturnType = RetType; + using ArgTypesTuple = std::tuple; +}; + +template +struct FunctionTraitsImpl + : FunctionTraitsImpl {}; + +template +struct FunctionTraitsImpl : FunctionTraitsImpl {}; +} // namespace detail + +template +using FunctionTraits = detail::FunctionTraitsImpl< + std::remove_pointer_t>>; + +template +using FunctionTypeTraits = detail::FunctionTraitsImpl; + +template struct TypeIdentityOf { + using type = T; +}; + +template class Template> +struct ApplyTupleTypes; + +template