aboutsummaryrefslogtreecommitdiff
path: root/offload
diff options
context:
space:
mode:
Diffstat (limited to 'offload')
-rw-r--r--offload/unittests/CMakeLists.txt13
-rw-r--r--offload/unittests/Conformance/CMakeLists.txt7
-rw-r--r--offload/unittests/Conformance/device_code/CMakeLists.txt6
-rw-r--r--offload/unittests/Conformance/device_code/LLVMLibm.c37
-rw-r--r--offload/unittests/Conformance/device_code/sin.c4
-rw-r--r--offload/unittests/Conformance/include/mathtest/CommandLine.hpp101
-rw-r--r--offload/unittests/Conformance/include/mathtest/CommandLineExtras.hpp38
-rw-r--r--offload/unittests/Conformance/include/mathtest/DeviceContext.hpp137
-rw-r--r--offload/unittests/Conformance/include/mathtest/DeviceResources.hpp144
-rw-r--r--offload/unittests/Conformance/include/mathtest/ErrorHandling.hpp46
-rw-r--r--offload/unittests/Conformance/include/mathtest/ExhaustiveGenerator.hpp140
-rw-r--r--offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp188
-rw-r--r--offload/unittests/Conformance/include/mathtest/HostRefChecker.hpp100
-rw-r--r--offload/unittests/Conformance/include/mathtest/IndexedRange.hpp110
-rw-r--r--offload/unittests/Conformance/include/mathtest/InputGenerator.hpp33
-rw-r--r--offload/unittests/Conformance/include/mathtest/Numerics.hpp147
-rw-r--r--offload/unittests/Conformance/include/mathtest/OffloadForward.hpp39
-rw-r--r--offload/unittests/Conformance/include/mathtest/Support.hpp155
-rw-r--r--offload/unittests/Conformance/include/mathtest/TestConfig.hpp38
-rw-r--r--offload/unittests/Conformance/include/mathtest/TestResult.hpp86
-rw-r--r--offload/unittests/Conformance/include/mathtest/TestRunner.hpp207
-rw-r--r--offload/unittests/Conformance/include/mathtest/TypeExtras.hpp23
-rw-r--r--offload/unittests/Conformance/lib/CMakeLists.txt11
-rw-r--r--offload/unittests/Conformance/lib/CommandLineExtras.cpp45
-rw-r--r--offload/unittests/Conformance/lib/DeviceContext.cpp307
-rw-r--r--offload/unittests/Conformance/lib/DeviceResources.cpp65
-rw-r--r--offload/unittests/Conformance/lib/ErrorHandling.cpp51
-rw-r--r--offload/unittests/Conformance/lib/TestConfig.cpp56
-rw-r--r--offload/unittests/Conformance/sin.cpp8
-rw-r--r--offload/unittests/Conformance/tests/CMakeLists.txt2
-rw-r--r--offload/unittests/Conformance/tests/Hypotf16Test.cpp61
-rw-r--r--offload/unittests/Conformance/tests/LogfTest.cpp56
32 files changed, 2435 insertions, 26 deletions
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;
+}