diff options
Diffstat (limited to 'offload')
33 files changed, 2441 insertions, 26 deletions
diff --git a/offload/tools/offload-tblgen/CMakeLists.txt b/offload/tools/offload-tblgen/CMakeLists.txt index 15525dc..a5ae1c3 100644 --- a/offload/tools/offload-tblgen/CMakeLists.txt +++ b/offload/tools/offload-tblgen/CMakeLists.txt @@ -22,5 +22,11 @@ add_tablegen(offload-tblgen OFFLOAD RecordTypes.hpp ) +# Make sure that C++ headers are available, if libcxx is built at the same +# time. This is important if clang is set to prefer libc++ over libstdc++ +if(TARGET cxx-headers) + add_dependencies(offload-tblgen cxx-headers) +endif() + set(OFFLOAD_TABLEGEN_EXE "${OFFLOAD_TABLEGEN_EXE}" CACHE INTERNAL "") set(OFFLOAD_TABLEGEN_TARGET "${OFFLOAD_TABLEGEN_TARGET}" CACHE INTERNAL "") diff --git a/offload/unittests/CMakeLists.txt b/offload/unittests/CMakeLists.txt index 388d15f..6d165ff 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 $<TARGET_FILE:${target_name}> - 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 bc31417..ce04215 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 223f04c..18f54b8 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 0000000..fe5196a --- /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 <gpuintrin.h> +#include <math.h> +#include <stddef.h> +#include <stdint.h> + +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 e969e60..0000000 --- a/offload/unittests/Conformance/device_code/sin.c +++ /dev/null @@ -1,4 +0,0 @@ -#include <gpuintrin.h> -#include <math.h> - -__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 0000000..b28594a --- /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 <string> + +namespace llvm { +namespace cl { + +struct TestConfigsArg { + enum class Mode { Default, All, Explicit } Mode = Mode::Default; + llvm::SmallVector<mathtest::TestConfig, 4> Explicit; +}; + +template <> class parser<TestConfigsArg> : public basic_parser<TestConfigsArg> { +public: + parser(Option &O) : basic_parser<TestConfigsArg>(O) {} + + static bool isAllowed(const mathtest::TestConfig &Config) { + static const llvm::SmallVector<mathtest::TestConfig, 4> &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<StringRef, 8> Pairs; + ArgValue.split(Pairs, ',', /*MaxSplit=*/-1, /*KeepEmpty=*/false); + + Val.Mode = TestConfigsArg::Mode::Explicit; + Val.Explicit.clear(); + + for (StringRef Pair : Pairs) { + llvm::SmallVector<StringRef, 2> Parts; + Pair.split(Parts, ':'); + + if (Parts.size() != 2) + return O.error("Expected '<provider>:<platform>', 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 0000000..e80fdbf --- /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<bool> IsVerbose; + +namespace detail { + +extern llvm::cl::opt<llvm::cl::TestConfigsArg> TestConfigsOpt; +} // namespace detail + +const llvm::SmallVector<TestConfig, 4> &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 0000000..5c31fc3 --- /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 <cassert> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <tuple> +#include <type_traits> +#include <utility> + +namespace mathtest { + +const llvm::SetVector<llvm::StringRef> &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 <typename T> + ManagedBuffer<T> createManagedBuffer(std::size_t Size) const noexcept { + void *UntypedAddress = nullptr; + + detail::allocManagedMemory(DeviceHandle, Size * sizeof(T), &UntypedAddress); + T *TypedAddress = static_cast<T *>(UntypedAddress); + + return ManagedBuffer<T>(TypedAddress, Size); + } + + [[nodiscard]] llvm::Expected<std::shared_ptr<DeviceImage>> + loadBinary(llvm::StringRef Directory, llvm::StringRef BinaryName) const; + + template <typename KernelSignature> + [[nodiscard]] llvm::Expected<DeviceKernel<KernelSignature>> + getKernel(const std::shared_ptr<DeviceImage> &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<KernelSignature>(Image, *ExpectedHandle); + } + + template <typename KernelSignature, typename... ArgTypes> + void launchKernel(DeviceKernel<KernelSignature> Kernel, uint32_t NumGroups, + uint32_t GroupSize, ArgTypes &&...Args) const noexcept { + using ExpectedTypes = + typename FunctionTypeTraits<KernelSignature>::ArgTypesTuple; + using ProvidedTypes = std::tuple<std::decay_t<ArgTypes>...>; + + static_assert(std::is_same_v<ExpectedTypes, ProvidedTypes>, + "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<ArgTypes>(Args)...); + + static_assert( + (std::is_trivially_copyable_v<std::decay_t<ArgTypes>> && ...), + "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<ol_symbol_handle_t> + 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 0000000..860448a --- /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 <cstddef> +#include <memory> +#include <utility> + +namespace mathtest { + +class DeviceContext; + +namespace detail { + +void freeDeviceMemory(void *Address) noexcept; +} // namespace detail + +//===----------------------------------------------------------------------===// +// ManagedBuffer +//===----------------------------------------------------------------------===// + +template <typename T> 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<T>() noexcept { + return llvm::MutableArrayRef<T>(data(), getSize()); + } + + [[nodiscard]] operator llvm::ArrayRef<T>() const noexcept { + return llvm::ArrayRef<T>(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 <typename KernelSignature> 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<DeviceImage> Image, + ol_symbol_handle_t Kernel) + : Image(std::move(Image)), Handle(Kernel) {} + + std::shared_ptr<DeviceImage> 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 0000000..7ec2762 --- /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 0000000..6f7f7a9 --- /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 <algorithm> +#include <array> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <tuple> + +namespace mathtest { + +template <typename... InTypes> +class [[nodiscard]] ExhaustiveGenerator final + : public InputGenerator<InTypes...> { + 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<InTypes> &...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<int>(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<InTypes>... Buffers) noexcept override { + const std::array<std::size_t, NumInputs> 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<uint64_t>(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<std::size_t>(BatchSize); + } + +private: + using RangesTupleType = std::tuple<IndexedRange<InTypes>...>; + using IndexArrayType = std::array<uint64_t, NumInputs>; + + static bool getSizeWithOverflow(const IndexedRange<InTypes> &...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 <typename BufferPtrsTupleType> + 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 <std::size_t Index, typename BufferPtrsTupleType> + void writeInputsImpl(IndexArrayType NDIndex, uint64_t Offset, + BufferPtrsTupleType BufferPtrsTuple) const noexcept { + if constexpr (Index < NumInputs) { + const auto &Range = std::get<Index>(RangesTuple); + std::get<Index>(BufferPtrsTuple)[Offset] = Range[NDIndex[Index]]; + writeInputsImpl<Index + 1>(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 0000000..b88d6e9 --- /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 <cassert> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <string> +#include <tuple> +#include <utility> + +namespace mathtest { + +template <auto Func, typename Checker = HostRefChecker<Func>> +class [[nodiscard]] GpuMathTest final { + using FunctionTraits = FunctionTraits<Func>; + using OutType = typename FunctionTraits::ReturnType; + using InTypesTuple = typename FunctionTraits::ArgTypesTuple; + + template <typename... Ts> + using PartialResultType = TestResult<OutType, Ts...>; + using KernelSignature = KernelSignatureOf_t<Func>; + + template <typename... Ts> + using TypeIdentitiesTuple = std::tuple<TypeIdentityOf<Ts>...>; + using InTypeIdentitiesTuple = + ApplyTupleTypes_t<InTypesTuple, TypeIdentitiesTuple>; + + static constexpr std::size_t DefaultBufferSize = + DefaultBufferSizeFor_v<OutType, InTypesTuple>; + static constexpr uint32_t DefaultGroupSize = 512; + +public: + using FunctionConfig = FunctionConfig<Func>; + using ResultType = ApplyTupleTypes_t<InTypesTuple, PartialResultType>; + using GeneratorType = ApplyTupleTypes_t<InTypesTuple, InputGenerator>; + + [[nodiscard]] static llvm::Expected<GpuMathTest> + create(std::shared_ptr<DeviceContext> 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<DeviceContext> getContext() const noexcept { + return Context; + } + + [[nodiscard]] std::string getProvider() const noexcept { return Provider; } + +private: + explicit GpuMathTest(std::shared_ptr<DeviceContext> Context, + llvm::StringRef Provider, + DeviceKernel<KernelSignature> Kernel) + : Context(std::move(Context)), Provider(Provider), Kernel(Kernel) {} + + static llvm::Expected<DeviceKernel<KernelSignature>> + 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<KernelSignature>( + *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<OutType>(BufferSize); + + return std::make_pair(std::move(InBuffersTuple), std::move(OutBuffer)); + } + + template <typename InBuffersTupleType> + [[nodiscard]] ResultType + processBatch(const InBuffersTupleType &InBuffersTuple, + ManagedBuffer<OutType> &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 <typename InBuffersTupleType> + [[nodiscard]] static ResultType + check(const InBuffersTupleType &InBuffersTuple, + const ManagedBuffer<OutType> &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<OutType>(OutBuffer.data(), BatchSize); + + return Checker::check(InViewsTuple, OutView); + } + + std::shared_ptr<DeviceContext> Context; + std::string Provider; + DeviceKernel<KernelSignature> 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 0000000..488aefd --- /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 <cstddef> +#include <tuple> +#include <utility> + +namespace mathtest { + +template <auto Func> class HostRefChecker { + using FunctionTraits = FunctionTraits<Func>; + using InTypesTuple = typename FunctionTraits::ArgTypesTuple; + + using FunctionConfig = FunctionConfig<Func>; + + template <typename... Ts> + using BuffersTupleType = std::tuple<llvm::ArrayRef<Ts>...>; + +public: + using OutType = typename FunctionTraits::ReturnType; + +private: + template <typename... Ts> + using PartialResultType = TestResult<OutType, Ts...>; + +public: + using ResultType = ApplyTupleTypes_t<InTypesTuple, PartialResultType>; + using InBuffersTupleType = ApplyTupleTypes_t<InTypesTuple, BuffersTupleType>; + + HostRefChecker() = delete; + + static ResultType check(InBuffersTupleType InBuffersTuple, + llvm::ArrayRef<OutType> 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 0000000..24aa00f --- /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 <cassert> +#include <cstdint> +#include <limits> +#include <type_traits> + +namespace mathtest { + +template <typename T> class [[nodiscard]] IndexedRange { + static_assert(IsFloatingPoint_v<T> || std::is_integral_v<T>, + "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<T>(), getMaxOrInf<T>(), 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<uint64_t>::max()) && + "The range is too large to index"); + } + + [[nodiscard]] constexpr uint64_t getSize() const noexcept { + return static_cast<uint64_t>(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<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<T>) { + constexpr StorageType SignMask = FPBits<T>::SIGN_MASK; + StorageType Unsigned = FPBits<T>(Value).uintval(); + return (Unsigned & SignMask) ? SignMask - (Unsigned - SignMask) - 1 + : SignMask + Unsigned; + } + + if constexpr (std::is_signed_v<T>) { + constexpr StorageType SignMask = llvm::maskLeadingOnes<StorageType>(1); + return __builtin_bit_cast(StorageType, Value) ^ SignMask; + } + + return Value; + } + + static constexpr T mapFromOrderedUnsigned(StorageType MappedValue) { + if constexpr (IsFloatingPoint_v<T>) { + constexpr StorageType SignMask = FPBits<T>::SIGN_MASK; + StorageType Unsigned = (MappedValue < SignMask) + ? (SignMask - MappedValue) + SignMask - 1 + : MappedValue - SignMask; + + return FPBits<T>(Unsigned).get_val(); + } + + if constexpr (std::is_signed_v<T>) { + constexpr StorageType SignMask = llvm::maskLeadingOnes<StorageType>(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 0000000..0154d0b --- /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 <typename... InTypes> class InputGenerator { +public: + virtual ~InputGenerator() noexcept = default; + + virtual void reset() noexcept = 0; + + [[nodiscard]] virtual size_t + fill(llvm::MutableArrayRef<InTypes>... 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 0000000..8b0e900 --- /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 <climits> +#include <cstdint> +#include <limits> +#include <math.h> +#include <type_traits> + +namespace mathtest { + +template <typename FloatType> +using FPBits = LIBC_NAMESPACE::shared::FPBits<FloatType>; + +using Sign = LIBC_NAMESPACE::shared::Sign; + +//===----------------------------------------------------------------------===// +// Type Traits +//===----------------------------------------------------------------------===// + +template <typename T> struct IsFloatingPoint : std::is_floating_point<T> {}; + +template <> struct IsFloatingPoint<float16> : std::true_type {}; + +template <typename T> +inline constexpr bool IsFloatingPoint_v // NOLINT(readability-identifier-naming) + = IsFloatingPoint<T>::value; + +template <typename T> struct StorageTypeOf { +private: + static constexpr auto getStorageType() noexcept { + if constexpr (IsFloatingPoint_v<T>) + return TypeIdentityOf<typename FPBits<T>::StorageType>{}; + else if constexpr (std::is_unsigned_v<T>) + return TypeIdentityOf<T>{}; + else if constexpr (std::is_signed_v<T>) + return TypeIdentityOf<std::make_unsigned_t<T>>{}; + else + static_assert(!std::is_same_v<T, T>, "Unsupported type"); + } + +public: + using type = typename decltype(getStorageType())::type; +}; + +template <typename T> using StorageTypeOf_t = typename StorageTypeOf<T>::type; + +//===----------------------------------------------------------------------===// +// Numeric Functions +//===----------------------------------------------------------------------===// + +template <typename T> [[nodiscard]] constexpr T getMinOrNegInf() noexcept { + if constexpr (IsFloatingPoint_v<T>) { + // All currently supported floating-point types have infinity + return FPBits<T>::inf(Sign::NEG).get_val(); + } else { + static_assert(std::is_integral_v<T>, + "Type T must be an integral or floating-point type"); + + return std::numeric_limits<T>::lowest(); + } +} + +template <typename T> [[nodiscard]] constexpr T getMaxOrInf() noexcept { + if constexpr (IsFloatingPoint_v<T>) { + // All currently supported floating-point types have infinity + return FPBits<T>::inf(Sign::POS).get_val(); + } else { + static_assert(std::is_integral_v<T>, + "Type T must be an integral or floating-point type"); + + return std::numeric_limits<T>::max(); + } +} + +template <typename FloatType> +[[nodiscard]] uint64_t computeUlpDistance(FloatType X, FloatType Y) noexcept { + static_assert(IsFloatingPoint_v<FloatType>, + "FloatType must be a floating-point type"); + using FPBits = FPBits<FloatType>; + 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<uint64_t>::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<uint64_t>(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 0000000..788989a --- /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<ol_result_t>(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 0000000..2d3dcee --- /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 <cstddef> +#include <tuple> +#include <type_traits> +#include <utility> + +namespace mathtest { + +//===----------------------------------------------------------------------===// +// Function & Type Traits +//===----------------------------------------------------------------------===// + +namespace detail { + +template <typename T> struct FunctionTraitsImpl; + +template <typename RetType, typename... ArgTypes> +struct FunctionTraitsImpl<RetType(ArgTypes...)> { + using ReturnType = RetType; + using ArgTypesTuple = std::tuple<ArgTypes...>; +}; + +template <typename RetType, typename... ArgTypes> +struct FunctionTraitsImpl<RetType(ArgTypes...) noexcept> + : FunctionTraitsImpl<RetType(ArgTypes...)> {}; + +template <typename FuncType> +struct FunctionTraitsImpl<FuncType *> : FunctionTraitsImpl<FuncType> {}; +} // namespace detail + +template <auto Func> +using FunctionTraits = detail::FunctionTraitsImpl< + std::remove_pointer_t<std::decay_t<decltype(Func)>>>; + +template <typename FuncType> +using FunctionTypeTraits = detail::FunctionTraitsImpl<FuncType>; + +template <typename T> struct TypeIdentityOf { + using type = T; +}; + +template <typename TupleTypes, template <typename...> class Template> +struct ApplyTupleTypes; + +template <template <typename...> class Template, typename... Ts> +struct ApplyTupleTypes<std::tuple<Ts...>, Template> { + using type = Template<Ts...>; +}; + +template <typename TupleTypes, template <typename...> class Template> +using ApplyTupleTypes_t = typename ApplyTupleTypes<TupleTypes, Template>::type; + +namespace detail { + +template <typename T> struct KernelSignatureOfImpl; + +template <typename RetType, typename... ArgTypes> +struct KernelSignatureOfImpl<RetType(ArgTypes...)> { + using type = void(const std::decay_t<ArgTypes> *..., RetType *, std::size_t); +}; + +template <typename RetType, typename... ArgTypes> +struct KernelSignatureOfImpl<RetType(ArgTypes...) noexcept> + : KernelSignatureOfImpl<RetType(ArgTypes...)> {}; +} // namespace detail + +template <auto Func> +using KernelSignatureOf = detail::KernelSignatureOfImpl< + std::remove_pointer_t<std::decay_t<decltype(Func)>>>; + +template <auto Func> +using KernelSignatureOf_t = typename KernelSignatureOf<Func>::type; + +//===----------------------------------------------------------------------===// +// Kernel Argument Packing +//===----------------------------------------------------------------------===// + +template <typename... ArgTypes> struct KernelArgsPack; + +template <typename ArgType> struct KernelArgsPack<ArgType> { + std::decay_t<ArgType> Arg; + + constexpr KernelArgsPack(ArgType &&Arg) : Arg(std::forward<ArgType>(Arg)) {} +}; + +template <typename ArgType0, typename ArgType1, typename... ArgTypes> +struct KernelArgsPack<ArgType0, ArgType1, ArgTypes...> { + std::decay_t<ArgType0> Arg0; + KernelArgsPack<ArgType1, ArgTypes...> Args; + + constexpr KernelArgsPack(ArgType0 &&Arg0, ArgType1 &&Arg1, ArgTypes &&...Args) + : Arg0(std::forward<ArgType0>(Arg0)), + Args(std::forward<ArgType1>(Arg1), std::forward<ArgTypes>(Args)...) {} +}; + +template <typename... ArgTypes> +KernelArgsPack<ArgTypes...> makeKernelArgsPack(ArgTypes &&...Args) { + return KernelArgsPack<ArgTypes...>(std::forward<ArgTypes>(Args)...); +} + +//===----------------------------------------------------------------------===// +// Configuration Helpers +//===----------------------------------------------------------------------===// + +template <auto Func> struct FunctionConfig; + +namespace detail { + +template <typename... BufferTypes> +static constexpr std::size_t getDefaultBufferSize() { + static_assert(sizeof...(BufferTypes) > 0, + "At least one buffer type must be provided"); + + constexpr std::size_t TotalMemoryInBytes = 512ULL << 20; // 512 MiB + constexpr std::size_t ElementTupleSize = (sizeof(BufferTypes) + ...); + + static_assert(ElementTupleSize > 0, + "Cannot calculate buffer size for empty types"); + + return TotalMemoryInBytes / ElementTupleSize; +} +} // namespace detail + +template <typename BufferType, typename BufferTupleTypes> +struct DefaultBufferSizeFor; + +template <typename BufferType, typename... BufferTypes> +struct DefaultBufferSizeFor<BufferType, std::tuple<BufferTypes...>> { + static constexpr std::size_t value // NOLINT(readability-identifier-naming) + = detail::getDefaultBufferSize<BufferType, BufferTypes...>(); +}; + +template <typename OutType, typename InTypesTuple> +inline constexpr std::size_t + DefaultBufferSizeFor_v // NOLINT(readability-identifier-naming) + = DefaultBufferSizeFor<OutType, InTypesTuple>::value; +} // namespace mathtest + +#endif // MATHTEST_SUPPORT_HPP diff --git a/offload/unittests/Conformance/include/mathtest/TestConfig.hpp b/offload/unittests/Conformance/include/mathtest/TestConfig.hpp new file mode 100644 index 0000000..49fe8d8 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/TestConfig.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 definition of the TestConfig struct and declares the +/// functions for retrieving the set of all and default test configurations. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_TESTCONFIG_HPP +#define MATHTEST_TESTCONFIG_HPP + +#include "llvm/ADT/SmallVector.h" + +#include <string> + +namespace mathtest { + +struct TestConfig { + std::string Provider; + std::string Platform; + + [[nodiscard]] bool operator==(const TestConfig &RHS) const noexcept { + return Provider == RHS.Provider && Platform == RHS.Platform; + } +}; + +[[nodiscard]] const llvm::SmallVector<TestConfig, 4> &getAllTestConfigs(); + +[[nodiscard]] const llvm::SmallVector<TestConfig, 4> &getDefaultTestConfigs(); +} // namespace mathtest + +#endif // MATHTEST_TESTCONFIG_HPP diff --git a/offload/unittests/Conformance/include/mathtest/TestResult.hpp b/offload/unittests/Conformance/include/mathtest/TestResult.hpp new file mode 100644 index 0000000..303ef4d --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/TestResult.hpp @@ -0,0 +1,86 @@ +//===----------------------------------------------------------------------===// +// +// 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 TestResult class, which aggregates +/// and stores the results of a math test run. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_TESTRESULT_HPP +#define MATHTEST_TESTRESULT_HPP + +#include <cstdint> +#include <optional> +#include <tuple> +#include <utility> + +namespace mathtest { + +template <typename OutType, typename... InTypes> +class [[nodiscard]] TestResult { +public: + struct [[nodiscard]] TestCase { + std::tuple<InTypes...> Inputs; + OutType Actual; + OutType Expected; + + explicit constexpr TestCase(std::tuple<InTypes...> &&Inputs, OutType Actual, + OutType Expected) noexcept + : Inputs(std::move(Inputs)), Actual(std::move(Actual)), + Expected(std::move(Expected)) {} + }; + + TestResult() = default; + + explicit TestResult(uint64_t UlpDistance, bool IsFailure, + TestCase &&Case) noexcept + : MaxUlpDistance(UlpDistance), FailureCount(IsFailure ? 1 : 0), + TestCaseCount(1) { + if (IsFailure) + WorstFailingCase.emplace(std::move(Case)); + } + + void accumulate(const TestResult &Other) noexcept { + if (Other.MaxUlpDistance > MaxUlpDistance) { + MaxUlpDistance = Other.MaxUlpDistance; + WorstFailingCase = Other.WorstFailingCase; + } + + FailureCount += Other.FailureCount; + TestCaseCount += Other.TestCaseCount; + } + + [[nodiscard]] bool hasPassed() const noexcept { return FailureCount == 0; } + + [[nodiscard]] uint64_t getMaxUlpDistance() const noexcept { + return MaxUlpDistance; + } + + [[nodiscard]] uint64_t getFailureCount() const noexcept { + return FailureCount; + } + + [[nodiscard]] uint64_t getTestCaseCount() const noexcept { + return TestCaseCount; + } + + [[nodiscard]] const std::optional<TestCase> & + getWorstFailingCase() const noexcept { + return WorstFailingCase; + } + +private: + uint64_t MaxUlpDistance = 0; + uint64_t FailureCount = 0; + uint64_t TestCaseCount = 0; + std::optional<TestCase> WorstFailingCase; +}; +} // namespace mathtest + +#endif // MATHTEST_TESTRESULT_HPP diff --git a/offload/unittests/Conformance/include/mathtest/TestRunner.hpp b/offload/unittests/Conformance/include/mathtest/TestRunner.hpp new file mode 100644 index 0000000..f89d151 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/TestRunner.hpp @@ -0,0 +1,207 @@ +//===----------------------------------------------------------------------===// +// +// 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 runTests function, which executes a +/// a suite of tests and print a formatted report for each. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_TESTRUNNER_HPP +#define MATHTEST_TESTRUNNER_HPP + +#include "mathtest/DeviceContext.hpp" +#include "mathtest/GpuMathTest.hpp" +#include "mathtest/Numerics.hpp" +#include "mathtest/TestConfig.hpp" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" + +#include <chrono> +#include <cstddef> +#include <memory> +#include <tuple> + +namespace mathtest { +namespace detail { + +template <auto Func> +void printPreamble(const TestConfig &Config, size_t Index, + size_t Total) noexcept { + using FunctionConfig = FunctionConfig<Func>; + + llvm::outs() << "[" << (Index + 1) << "/" << Total << "] " + << "Running conformance test '" << FunctionConfig::Name + << "' with '" << Config.Provider << "' on '" << Config.Platform + << "'\n"; + llvm::outs().flush(); +} + +template <typename T> +void printValue(llvm::raw_ostream &OS, const T &Value) noexcept { + if constexpr (IsFloatingPoint_v<T>) { + if constexpr (sizeof(T) < sizeof(float)) + OS << float(Value); + else + OS << Value; + + const FPBits<T> Bits(Value); + OS << llvm::formatv(" (0x{0})", llvm::Twine::utohexstr(Bits.uintval())); + } else { + OS << Value; + } +} + +template <typename... Ts> +void printValues(llvm::raw_ostream &OS, + const std::tuple<Ts...> &ValuesTuple) noexcept { + std::apply( + [&OS](const auto &...Values) { + bool IsFirst = true; + auto PrintWithComma = [&](const auto &Value) { + if (!IsFirst) + OS << ", "; + printValue(OS, Value); + IsFirst = false; + }; + (PrintWithComma(Values), ...); + }, + ValuesTuple); +} + +template <typename TestCaseType> +void printWorstFailingCase(llvm::raw_ostream &OS, + const TestCaseType &TestCase) noexcept { + OS << "--- Worst Failing Case ---\n"; + OS << llvm::formatv(" {0,-14} : ", "Input(s)"); + printValues(OS, TestCase.Inputs); + OS << "\n"; + + OS << llvm::formatv(" {0,-14} : ", "Actual"); + printValue(OS, TestCase.Actual); + OS << "\n"; + + OS << llvm::formatv(" {0,-14} : ", "Expected"); + printValue(OS, TestCase.Expected); + OS << "\n"; +} + +template <typename TestType, typename ResultType> +void printReport(const TestType &Test, const ResultType &Result, + const std::chrono::steady_clock::duration &Duration) noexcept { + using FunctionConfig = typename TestType::FunctionConfig; + + const auto Context = Test.getContext(); + const auto ElapsedMilliseconds = + std::chrono::duration_cast<std::chrono::milliseconds>(Duration).count(); + const bool Passed = Result.hasPassed(); + + llvm::errs() << llvm::formatv("=== Test Report for '{0}' === \n", + FunctionConfig::Name); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Provider", + Test.getProvider()); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Platform", + Context->getPlatform()); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Device", Context->getName()); + llvm::errs() << llvm::formatv("{0,-17}: {1} ms\n", "Elapsed time", + ElapsedMilliseconds); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "ULP tolerance", + FunctionConfig::UlpTolerance); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Max ULP distance", + Result.getMaxUlpDistance()); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Test cases", + Result.getTestCaseCount()); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Failures", + Result.getFailureCount()); + llvm::errs() << llvm::formatv("{0,-17}: {1}\n", "Status", + Passed ? "PASSED" : "FAILED"); + + if (const auto &Worst = Result.getWorstFailingCase()) + printWorstFailingCase(llvm::errs(), Worst.value()); + + llvm::errs().flush(); +} + +template <auto Func, typename TestType = GpuMathTest<Func>> +[[nodiscard]] llvm::Expected<bool> +runTest(typename TestType::GeneratorType &Generator, const TestConfig &Config, + llvm::StringRef DeviceBinaryDir) { + const auto &Platforms = getPlatforms(); + + if (!llvm::any_of(Platforms, [&](llvm::StringRef Platform) { + return Platform.equals_insensitive(Config.Platform); + })) + return llvm::createStringError("Platform '" + Config.Platform + + "' is not available on this system"); + + auto Context = + std::make_shared<DeviceContext>(Config.Platform, /*DeviceId=*/0); + auto ExpectedTest = + TestType::create(Context, Config.Provider, DeviceBinaryDir); + + if (!ExpectedTest) + return ExpectedTest.takeError(); + + const auto StartTime = std::chrono::steady_clock::now(); + + auto Result = ExpectedTest->run(Generator); + + const auto EndTime = std::chrono::steady_clock::now(); + const auto Duration = EndTime - StartTime; + + printReport(*ExpectedTest, Result, Duration); + + return Result.hasPassed(); +} +} // namespace detail + +template <auto Func, typename TestType = GpuMathTest<Func>> +[[nodiscard]] bool runTests(typename TestType::GeneratorType &Generator, + const llvm::SmallVector<TestConfig, 4> &Configs, + llvm::StringRef DeviceBinaryDir, + bool IsVerbose = false) { + const size_t NumConfigs = Configs.size(); + + if (NumConfigs == 0) + llvm::errs() << "There is no test configuration to run a test\n"; + + bool Passed = true; + + for (const auto &[Index, Config] : llvm::enumerate(Configs)) { + detail::printPreamble<Func>(Config, Index, NumConfigs); + + Generator.reset(); + + auto ExpectedPassed = + detail::runTest<Func, TestType>(Generator, Config, DeviceBinaryDir); + + if (!ExpectedPassed) { + const auto Details = llvm::toString(ExpectedPassed.takeError()); + llvm::errs() + << "WARNING: Conformance test not supported on this system\n"; + + if (IsVerbose) + llvm::errs() << "Details: " << Details << "\n"; + } else { + Passed &= *ExpectedPassed; + } + + llvm::errs() << "\n"; + } + + return Passed; +} +} // namespace mathtest + +#endif // MATHTEST_TESTRUNNER_HPP diff --git a/offload/unittests/Conformance/include/mathtest/TypeExtras.hpp b/offload/unittests/Conformance/include/mathtest/TypeExtras.hpp new file mode 100644 index 0000000..9991a44 --- /dev/null +++ b/offload/unittests/Conformance/include/mathtest/TypeExtras.hpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// 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 type aliases for extended floating +/// -point types. +/// +//===----------------------------------------------------------------------===// + +#ifndef MATHTEST_TYPEEXTRAS_HPP +#define MATHTEST_TYPEEXTRAS_HPP + +namespace mathtest { + +using float16 = _Float16; +} // namespace mathtest + +#endif // MATHTEST_TYPEEXTRAS_HPP diff --git a/offload/unittests/Conformance/lib/CMakeLists.txt b/offload/unittests/Conformance/lib/CMakeLists.txt new file mode 100644 index 0000000..8e86f10 --- /dev/null +++ b/offload/unittests/Conformance/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(MathTest STATIC + CommandLineExtras.cpp DeviceContext.cpp DeviceResources.cpp ErrorHandling.cpp TestConfig.cpp) + +target_include_directories(MathTest PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") + +if(NOT LLVM_REQUIRES_RTTI) + target_compile_options(MathTest PUBLIC -fno-rtti) +endif() + +include(FindLibcCommonUtils) +target_link_libraries(MathTest PUBLIC LLVMOffload LLVMSupport LLVMDemangle llvm-libc-common-utilities) diff --git a/offload/unittests/Conformance/lib/CommandLineExtras.cpp b/offload/unittests/Conformance/lib/CommandLineExtras.cpp new file mode 100644 index 0000000..96f5058 --- /dev/null +++ b/offload/unittests/Conformance/lib/CommandLineExtras.cpp @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// 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 command-line options and the +/// implementation of the logic for selecting test configurations. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/CommandLineExtras.hpp" + +#include "mathtest/CommandLine.hpp" +#include "mathtest/TestConfig.hpp" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/CommandLine.h" + +using namespace mathtest; + +llvm::cl::opt<bool> mathtest::cl::IsVerbose( + "verbose", + llvm::cl::desc("Enable verbose output for failed and unsupported tests"), + llvm::cl::init(false)); + +llvm::cl::opt<llvm::cl::TestConfigsArg> mathtest::cl::detail::TestConfigsOpt( + "test-configs", llvm::cl::Optional, + llvm::cl::desc("Select test configurations"), + llvm::cl::value_desc("all|provider:platform[,provider:platform...]")); + +const llvm::SmallVector<TestConfig, 4> &mathtest::cl::getTestConfigs() { + switch (detail::TestConfigsOpt.Mode) { + case llvm::cl::TestConfigsArg::Mode::Default: + return getDefaultTestConfigs(); + case llvm::cl::TestConfigsArg::Mode::All: + return getAllTestConfigs(); + case llvm::cl::TestConfigsArg::Mode::Explicit: + return detail::TestConfigsOpt.Explicit; + } + llvm_unreachable("Unknown TestConfigsArg mode"); +} diff --git a/offload/unittests/Conformance/lib/DeviceContext.cpp b/offload/unittests/Conformance/lib/DeviceContext.cpp new file mode 100644 index 0000000..a0068c3 --- /dev/null +++ b/offload/unittests/Conformance/lib/DeviceContext.cpp @@ -0,0 +1,307 @@ +//===----------------------------------------------------------------------===// +// +// 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 helpers and non-template member +/// functions for the DeviceContext class. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/DeviceContext.hpp" + +#include "mathtest/ErrorHandling.hpp" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" + +#include <OffloadAPI.h> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <system_error> +#include <vector> + +using namespace mathtest; + +//===----------------------------------------------------------------------===// +// Helpers +//===----------------------------------------------------------------------===// + +namespace { + +// The static 'Wrapper' instance ensures olInit() is called once at program +// startup and olShutDown() is called once at program termination +struct OffloadInitWrapper { + OffloadInitWrapper() { OL_CHECK(olInit()); } + ~OffloadInitWrapper() { OL_CHECK(olShutDown()); } +}; +static OffloadInitWrapper Wrapper{}; + +[[nodiscard]] std::string getDeviceName(ol_device_handle_t DeviceHandle) { + std::size_t PropSize = 0; + OL_CHECK(olGetDeviceInfoSize(DeviceHandle, OL_DEVICE_INFO_NAME, &PropSize)); + + if (PropSize == 0) + return ""; + + std::string PropValue(PropSize, '\0'); + OL_CHECK(olGetDeviceInfo(DeviceHandle, OL_DEVICE_INFO_NAME, PropSize, + PropValue.data())); + PropValue.pop_back(); // Remove the null terminator + + return PropValue; +} + +[[nodiscard]] ol_platform_handle_t +getDevicePlatform(ol_device_handle_t DeviceHandle) noexcept { + ol_platform_handle_t PlatformHandle = nullptr; + OL_CHECK(olGetDeviceInfo(DeviceHandle, OL_DEVICE_INFO_PLATFORM, + sizeof(PlatformHandle), &PlatformHandle)); + return PlatformHandle; +} + +[[nodiscard]] std::string getPlatformName(ol_platform_handle_t PlatformHandle) { + std::size_t PropSize = 0; + OL_CHECK( + olGetPlatformInfoSize(PlatformHandle, OL_PLATFORM_INFO_NAME, &PropSize)); + + if (PropSize == 0) + return ""; + + std::string PropValue(PropSize, '\0'); + OL_CHECK(olGetPlatformInfo(PlatformHandle, OL_PLATFORM_INFO_NAME, PropSize, + PropValue.data())); + PropValue.pop_back(); // Remove the null terminator + + return PropValue; +} + +[[nodiscard]] ol_platform_backend_t +getPlatformBackend(ol_platform_handle_t PlatformHandle) noexcept { + ol_platform_backend_t Backend = OL_PLATFORM_BACKEND_UNKNOWN; + OL_CHECK(olGetPlatformInfo(PlatformHandle, OL_PLATFORM_INFO_BACKEND, + sizeof(Backend), &Backend)); + return Backend; +} + +struct Device { + ol_device_handle_t Handle; + std::string Name; + std::string Platform; + ol_platform_backend_t Backend; +}; + +const std::vector<Device> &getDevices() { + // Thread-safe initialization of a static local variable + static auto Devices = []() { + std::vector<Device> TmpDevices; + + // Discovers all devices that are not the host + const auto *const ResultFromIterate = olIterateDevices( + [](ol_device_handle_t DeviceHandle, void *Data) { + ol_platform_handle_t PlatformHandle = getDevicePlatform(DeviceHandle); + ol_platform_backend_t Backend = getPlatformBackend(PlatformHandle); + + if (Backend != OL_PLATFORM_BACKEND_HOST) { + auto Name = getDeviceName(DeviceHandle); + auto Platform = getPlatformName(PlatformHandle); + + static_cast<std::vector<Device> *>(Data)->push_back( + {DeviceHandle, Name, Platform, Backend}); + } + + return true; + }, + &TmpDevices); + + OL_CHECK(ResultFromIterate); + + return TmpDevices; + }(); + + return Devices; +} +} // namespace + +const llvm::SetVector<llvm::StringRef> &mathtest::getPlatforms() { + // Thread-safe initialization of a static local variable + static auto Platforms = []() { + llvm::SetVector<llvm::StringRef> TmpPlatforms; + + for (const auto &Device : getDevices()) + TmpPlatforms.insert(Device.Platform); + + return TmpPlatforms; + }(); + + return Platforms; +} + +void detail::allocManagedMemory(ol_device_handle_t DeviceHandle, + std::size_t Size, + void **AllocationOut) noexcept { + OL_CHECK( + olMemAlloc(DeviceHandle, OL_ALLOC_TYPE_MANAGED, Size, AllocationOut)); +} + +//===----------------------------------------------------------------------===// +// DeviceContext +//===----------------------------------------------------------------------===// + +DeviceContext::DeviceContext(std::size_t GlobalDeviceId) + : GlobalDeviceId(GlobalDeviceId), DeviceHandle(nullptr) { + const auto &Devices = getDevices(); + + if (GlobalDeviceId >= Devices.size()) + FATAL_ERROR("Invalid GlobalDeviceId: " + llvm::Twine(GlobalDeviceId) + + ", but the number of available devices is " + + llvm::Twine(Devices.size())); + + DeviceHandle = Devices[GlobalDeviceId].Handle; +} + +DeviceContext::DeviceContext(llvm::StringRef Platform, std::size_t DeviceId) + : DeviceHandle(nullptr) { + const auto &Platforms = getPlatforms(); + + if (!llvm::any_of(Platforms, [&](llvm::StringRef CurrentPlatform) { + return CurrentPlatform.equals_insensitive(Platform); + })) + FATAL_ERROR("There is no platform that matches with '" + + llvm::Twine(Platform) + + "'. Available platforms are: " + llvm::join(Platforms, ", ")); + + const auto &Devices = getDevices(); + + std::optional<std::size_t> FoundGlobalDeviceId; + std::size_t MatchCount = 0; + + for (std::size_t Index = 0; Index < Devices.size(); ++Index) { + if (Platform.equals_insensitive(Devices[Index].Platform)) { + if (MatchCount == DeviceId) { + FoundGlobalDeviceId = Index; + break; + } + MatchCount++; + } + } + + if (!FoundGlobalDeviceId) + FATAL_ERROR("Invalid DeviceId: " + llvm::Twine(DeviceId) + + ", but the number of available devices on '" + Platform + + "' is " + llvm::Twine(MatchCount)); + + GlobalDeviceId = *FoundGlobalDeviceId; + DeviceHandle = Devices[GlobalDeviceId].Handle; +} + +[[nodiscard]] llvm::Expected<std::shared_ptr<DeviceImage>> +DeviceContext::loadBinary(llvm::StringRef Directory, + llvm::StringRef BinaryName) const { + auto Backend = getDevices()[GlobalDeviceId].Backend; + llvm::StringRef Extension; + + switch (Backend) { + case OL_PLATFORM_BACKEND_AMDGPU: + Extension = ".amdgpu.bin"; + break; + case OL_PLATFORM_BACKEND_CUDA: + Extension = ".nvptx64.bin"; + break; + default: + return llvm::createStringError( + "Unsupported backend to infer binary extension"); + } + + llvm::SmallString<128> FullPath(Directory); + llvm::sys::path::append(FullPath, llvm::Twine(BinaryName) + Extension); + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> FileOrErr = + llvm::MemoryBuffer::getFile(FullPath); + + if (std::error_code ErrorCode = FileOrErr.getError()) + return llvm::createStringError( + llvm::Twine("Failed to read device binary file '") + FullPath + + "': " + ErrorCode.message()); + + std::unique_ptr<llvm::MemoryBuffer> &BinaryData = *FileOrErr; + + ol_program_handle_t ProgramHandle = nullptr; + const ol_result_t OlResult = + olCreateProgram(DeviceHandle, BinaryData->getBufferStart(), + BinaryData->getBufferSize(), &ProgramHandle); + + if (OlResult != OL_SUCCESS) { + llvm::StringRef Details = + OlResult->Details ? OlResult->Details : "No details provided"; + + // clang-format off + return llvm::createStringError( + llvm::Twine(Details) + + " (code " + llvm::Twine(OlResult->Code) + ")"); + // clang-format on + } + + return std::shared_ptr<DeviceImage>( + new DeviceImage(DeviceHandle, ProgramHandle)); +} + +[[nodiscard]] llvm::Expected<ol_symbol_handle_t> +DeviceContext::getKernelHandle(ol_program_handle_t ProgramHandle, + llvm::StringRef KernelName) const noexcept { + ol_symbol_handle_t Handle = nullptr; + llvm::SmallString<32> NameBuffer(KernelName); + + const ol_result_t OlResult = olGetSymbol(ProgramHandle, NameBuffer.c_str(), + OL_SYMBOL_KIND_KERNEL, &Handle); + + if (OlResult != OL_SUCCESS) { + llvm::StringRef Details = + OlResult->Details ? OlResult->Details : "No details provided"; + + // clang-format off + return llvm::createStringError( + llvm::Twine(Details) + + " (code " + llvm::Twine(OlResult->Code) + ")"); + // clang-format on + } + + return Handle; +} + +void DeviceContext::launchKernelImpl( + ol_symbol_handle_t KernelHandle, uint32_t NumGroups, uint32_t GroupSize, + const void *KernelArgs, std::size_t KernelArgsSize) const noexcept { + ol_kernel_launch_size_args_t LaunchSizeArgs; + LaunchSizeArgs.Dimensions = 1; + LaunchSizeArgs.NumGroups = {NumGroups, 1, 1}; + LaunchSizeArgs.GroupSize = {GroupSize, 1, 1}; + LaunchSizeArgs.DynSharedMemory = 0; + + OL_CHECK(olLaunchKernel(nullptr, DeviceHandle, KernelHandle, KernelArgs, + KernelArgsSize, &LaunchSizeArgs)); +} + +[[nodiscard]] llvm::StringRef DeviceContext::getName() const noexcept { + return getDevices()[GlobalDeviceId].Name; +} + +[[nodiscard]] llvm::StringRef DeviceContext::getPlatform() const noexcept { + return getDevices()[GlobalDeviceId].Platform; +} diff --git a/offload/unittests/Conformance/lib/DeviceResources.cpp b/offload/unittests/Conformance/lib/DeviceResources.cpp new file mode 100644 index 0000000..d1c7b90 --- /dev/null +++ b/offload/unittests/Conformance/lib/DeviceResources.cpp @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// 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 helpers and non-template member +/// functions for the device resource classes. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/DeviceResources.hpp" + +#include "mathtest/ErrorHandling.hpp" + +#include <OffloadAPI.h> + +using namespace mathtest; + +//===----------------------------------------------------------------------===// +// Helpers +//===----------------------------------------------------------------------===// + +void detail::freeDeviceMemory(void *Address) noexcept { + if (Address) + OL_CHECK(olMemFree(Address)); +} + +//===----------------------------------------------------------------------===// +// DeviceImage +//===----------------------------------------------------------------------===// + +DeviceImage::~DeviceImage() noexcept { + if (Handle) + OL_CHECK(olDestroyProgram(Handle)); +} + +DeviceImage &DeviceImage::operator=(DeviceImage &&Other) noexcept { + if (this == &Other) + return *this; + + if (Handle) + OL_CHECK(olDestroyProgram(Handle)); + + DeviceHandle = Other.DeviceHandle; + Handle = Other.Handle; + + Other.DeviceHandle = nullptr; + Other.Handle = nullptr; + + return *this; +} + +DeviceImage::DeviceImage(DeviceImage &&Other) noexcept + : DeviceHandle(Other.DeviceHandle), Handle(Other.Handle) { + Other.DeviceHandle = nullptr; + Other.Handle = nullptr; +} + +DeviceImage::DeviceImage(ol_device_handle_t DeviceHandle, + ol_program_handle_t Handle) noexcept + : DeviceHandle(DeviceHandle), Handle(Handle) {} diff --git a/offload/unittests/Conformance/lib/ErrorHandling.cpp b/offload/unittests/Conformance/lib/ErrorHandling.cpp new file mode 100644 index 0000000..f757087 --- /dev/null +++ b/offload/unittests/Conformance/lib/ErrorHandling.cpp @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// 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 helper functions for the error +/// handling macros. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/ErrorHandling.hpp" + +#include "llvm/ADT/Twine.h" +#include "llvm/Support/ErrorHandling.h" + +#include <OffloadAPI.h> + +using namespace mathtest; + +[[noreturn]] void detail::reportFatalError(const llvm::Twine &Message, + const char *File, int Line, + const char *FuncName) { + // clang-format off + llvm::report_fatal_error( + llvm::Twine("Fatal error in '") + FuncName + + "' at " + File + ":" + llvm::Twine(Line) + + "\n Message: " + Message, + /*gen_crash_diag=*/false); + // clang-format on +} + +[[noreturn]] void detail::reportOffloadError(const char *ResultExpr, + ol_result_t Result, + const char *File, int Line, + const char *FuncName) { + // clang-format off + llvm::report_fatal_error( + llvm::Twine("OL_CHECK failed") + + "\n Location: " + File + ":" + llvm::Twine(Line) + + "\n Function: " + FuncName + + "\n Expression: " + ResultExpr + + "\n Error code: " + llvm::Twine(Result->Code) + + "\n Details: " + + (Result->Details ? Result->Details : "No details provided"), + /*gen_crash_diag=*/false); + // clang-format on +} diff --git a/offload/unittests/Conformance/lib/TestConfig.cpp b/offload/unittests/Conformance/lib/TestConfig.cpp new file mode 100644 index 0000000..b4396fc --- /dev/null +++ b/offload/unittests/Conformance/lib/TestConfig.cpp @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 for the functions that define the set +/// of all and default test configurations. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/TestConfig.hpp" + +#include "mathtest/DeviceContext.hpp" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/SmallVectorExtras.h" + +using namespace mathtest; + +[[nodiscard]] const llvm::SmallVector<TestConfig, 4> & +mathtest::getAllTestConfigs() { + // Thread-safe initialization of a static local variable + static auto AllTestConfigs = []() -> llvm::SmallVector<TestConfig, 4> { + return { + {"llvm-libm", "amdgpu"}, + {"llvm-libm", "cuda"}, + {"cuda-math", "cuda"}, + {"hip-math", "amdgpu"}, + }; + }(); + + return AllTestConfigs; +}; + +[[nodiscard]] const llvm::SmallVector<TestConfig, 4> & +mathtest::getDefaultTestConfigs() { + // Thread-safe initialization of a static local variable + static auto DefaultTestConfigs = []() -> llvm::SmallVector<TestConfig, 4> { + const auto Platforms = getPlatforms(); + const auto AllTestConfigs = getAllTestConfigs(); + llvm::StringRef Provider = "llvm-libm"; + + return llvm::filter_to_vector(AllTestConfigs, [&](const auto &Config) { + return Provider.equals_insensitive(Config.Provider) && + llvm::any_of(Platforms, [&](llvm::StringRef Platform) { + return Platform.equals_insensitive(Config.Platform); + }); + }); + }(); + + return DefaultTestConfigs; +}; diff --git a/offload/unittests/Conformance/sin.cpp b/offload/unittests/Conformance/sin.cpp deleted file mode 100644 index 9e15690..0000000 --- a/offload/unittests/Conformance/sin.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/raw_ostream.h" -#include <OffloadAPI.h> -#include <math.h> - -llvm::StringRef DeviceBinsDirectory = DEVICE_CODE_PATH; - -int main() { llvm::errs() << sin(0.0) << "\n"; } diff --git a/offload/unittests/Conformance/tests/CMakeLists.txt b/offload/unittests/Conformance/tests/CMakeLists.txt new file mode 100644 index 0000000..b1aa22b4 --- /dev/null +++ b/offload/unittests/Conformance/tests/CMakeLists.txt @@ -0,0 +1,2 @@ +add_conformance_test(hypotf16 Hypotf16Test.cpp) +add_conformance_test(logf LogfTest.cpp) diff --git a/offload/unittests/Conformance/tests/Hypotf16Test.cpp b/offload/unittests/Conformance/tests/Hypotf16Test.cpp new file mode 100644 index 0000000..fbc001a --- /dev/null +++ b/offload/unittests/Conformance/tests/Hypotf16Test.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// 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 conformance test of the hypotf16 function. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/CommandLineExtras.hpp" +#include "mathtest/ExhaustiveGenerator.hpp" +#include "mathtest/IndexedRange.hpp" +#include "mathtest/TestConfig.hpp" +#include "mathtest/TestRunner.hpp" +#include "mathtest/TypeExtras.hpp" + +#include "llvm/ADT/StringRef.h" + +#include <cstdlib> +#include <math.h> + +using namespace mathtest; + +extern "C" { + +float16 hypotf16(float16, float16); +} + +namespace mathtest { + +template <> struct FunctionConfig<hypotf16> { + static constexpr llvm::StringRef Name = "hypotf16"; + static constexpr llvm::StringRef KernelName = "hypotf16Kernel"; + + // Source: The Khronos Group, The OpenCL C Specification v3.0.19, Sec. 7.4, + // Table 69 (Full Profile), Khronos Registry [July 10, 2025]. + static constexpr uint64_t UlpTolerance = 2; +}; +} // namespace mathtest + +int main(int argc, const char **argv) { + llvm::cl::ParseCommandLineOptions( + argc, argv, "Conformance test of the hypotf16 function"); + + IndexedRange<float16> RangeX; + IndexedRange<float16> RangeY; + ExhaustiveGenerator<float16, float16> Generator(RangeX, RangeY); + + const auto Configs = cl::getTestConfigs(); + const llvm::StringRef DeviceBinaryDir = DEVICE_BINARY_DIR; + const bool IsVerbose = cl::IsVerbose; + + bool Passed = + runTests<hypotf16>(Generator, Configs, DeviceBinaryDir, IsVerbose); + + return Passed ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/offload/unittests/Conformance/tests/LogfTest.cpp b/offload/unittests/Conformance/tests/LogfTest.cpp new file mode 100644 index 0000000..97249ed --- /dev/null +++ b/offload/unittests/Conformance/tests/LogfTest.cpp @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 conformance test of the logf function. +/// +//===----------------------------------------------------------------------===// + +#include "mathtest/CommandLineExtras.hpp" +#include "mathtest/ExhaustiveGenerator.hpp" +#include "mathtest/IndexedRange.hpp" +#include "mathtest/TestConfig.hpp" +#include "mathtest/TestRunner.hpp" + +#include "llvm/ADT/StringRef.h" + +#include <cstdlib> +#include <limits> +#include <math.h> + +namespace mathtest { + +template <> struct FunctionConfig<logf> { + static constexpr llvm::StringRef Name = "logf"; + static constexpr llvm::StringRef KernelName = "logfKernel"; + + // Source: The Khronos Group, The OpenCL C Specification v3.0.19, Sec. 7.4, + // Table 65, Khronos Registry [July 10, 2025]. + static constexpr uint64_t UlpTolerance = 3; +}; +} // namespace mathtest + +int main(int argc, const char **argv) { + llvm::cl::ParseCommandLineOptions(argc, argv, + "Conformance test of the logf function"); + + using namespace mathtest; + + IndexedRange<float> Range(/*Begin=*/0.0f, + /*End=*/std::numeric_limits<float>::infinity(), + /*Inclusive=*/true); + ExhaustiveGenerator<float> Generator(Range); + + const auto Configs = cl::getTestConfigs(); + const llvm::StringRef DeviceBinaryDir = DEVICE_BINARY_DIR; + const bool IsVerbose = cl::IsVerbose; + + bool Passed = runTests<logf>(Generator, Configs, DeviceBinaryDir, IsVerbose); + + return Passed ? EXIT_SUCCESS : EXIT_FAILURE; +} |