This patch extends the conformance testing infrastructure to support two new providers of math function implementations for GPUs: CUDA Math (`cuda-math`) and HIP Math (`hip-math`).
208 lines
6.8 KiB
C++
208 lines
6.8 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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::errs() << "[" << (Index + 1) << "/" << Total << "] "
|
|
<< "Running conformance test '" << FunctionConfig::Name
|
|
<< "' with '" << Config.Provider << "' on '" << Config.Platform
|
|
<< "'\n";
|
|
llvm::errs().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
|