[Offload] Add framework for math conformance tests (#149242)
This PR introduces the initial version of a C++ framework for the conformance testing of GPU math library functions, building upon the skeleton provided in #146391. The main goal of this framework is to systematically measure the accuracy of math functions in the GPU libc, verifying correctness or at least conformance to standards like OpenCL via exhaustive or random accuracy tests.
This commit is contained in:
parent
15980624ac
commit
2abd58cb7e
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
37
offload/unittests/Conformance/device_code/LLVMLibm.c
Normal file
37
offload/unittests/Conformance/device_code/LLVMLibm.c
Normal file
@ -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]);
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
#include <gpuintrin.h>
|
||||
#include <math.h>
|
||||
|
||||
__gpu_kernel void kernel(double *out) { *out = sin(*out); }
|
101
offload/unittests/Conformance/include/mathtest/CommandLine.hpp
Normal file
101
offload/unittests/Conformance/include/mathtest/CommandLine.hpp
Normal file
@ -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
|
@ -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
|
137
offload/unittests/Conformance/include/mathtest/DeviceContext.hpp
Normal file
137
offload/unittests/Conformance/include/mathtest/DeviceContext.hpp
Normal file
@ -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
|
@ -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
|
@ -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
|
@ -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
|
188
offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp
Normal file
188
offload/unittests/Conformance/include/mathtest/GpuMathTest.hpp
Normal file
@ -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
|
@ -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
|
110
offload/unittests/Conformance/include/mathtest/IndexedRange.hpp
Normal file
110
offload/unittests/Conformance/include/mathtest/IndexedRange.hpp
Normal file
@ -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
|
@ -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
|
147
offload/unittests/Conformance/include/mathtest/Numerics.hpp
Normal file
147
offload/unittests/Conformance/include/mathtest/Numerics.hpp
Normal file
@ -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
|
@ -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
|
155
offload/unittests/Conformance/include/mathtest/Support.hpp
Normal file
155
offload/unittests/Conformance/include/mathtest/Support.hpp
Normal file
@ -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
|
@ -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
|
@ -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
|
207
offload/unittests/Conformance/include/mathtest/TestRunner.hpp
Normal file
207
offload/unittests/Conformance/include/mathtest/TestRunner.hpp
Normal file
@ -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
|
@ -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
|
11
offload/unittests/Conformance/lib/CMakeLists.txt
Normal file
11
offload/unittests/Conformance/lib/CMakeLists.txt
Normal file
@ -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)
|
45
offload/unittests/Conformance/lib/CommandLineExtras.cpp
Normal file
45
offload/unittests/Conformance/lib/CommandLineExtras.cpp
Normal file
@ -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");
|
||||
}
|
307
offload/unittests/Conformance/lib/DeviceContext.cpp
Normal file
307
offload/unittests/Conformance/lib/DeviceContext.cpp
Normal file
@ -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;
|
||||
}
|
65
offload/unittests/Conformance/lib/DeviceResources.cpp
Normal file
65
offload/unittests/Conformance/lib/DeviceResources.cpp
Normal file
@ -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) {}
|
51
offload/unittests/Conformance/lib/ErrorHandling.cpp
Normal file
51
offload/unittests/Conformance/lib/ErrorHandling.cpp
Normal file
@ -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
|
||||
}
|
56
offload/unittests/Conformance/lib/TestConfig.cpp
Normal file
56
offload/unittests/Conformance/lib/TestConfig.cpp
Normal file
@ -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;
|
||||
};
|
@ -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"; }
|
2
offload/unittests/Conformance/tests/CMakeLists.txt
Normal file
2
offload/unittests/Conformance/tests/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_conformance_test(hypotf16 Hypotf16Test.cpp)
|
||||
add_conformance_test(logf LogfTest.cpp)
|
61
offload/unittests/Conformance/tests/Hypotf16Test.cpp
Normal file
61
offload/unittests/Conformance/tests/Hypotf16Test.cpp
Normal file
@ -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;
|
||||
}
|
56
offload/unittests/Conformance/tests/LogfTest.cpp
Normal file
56
offload/unittests/Conformance/tests/LogfTest.cpp
Normal file
@ -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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user