diff --git a/include/dxc/Test/HlslTestUtils.h b/include/dxc/Test/HlslTestUtils.h index 44f3f6148a..dd89fda676 100644 --- a/include/dxc/Test/HlslTestUtils.h +++ b/include/dxc/Test/HlslTestUtils.h @@ -260,6 +260,29 @@ inline void LogErrorFmt(const wchar_t *fmt, ...) { WEX::Logging::Log::Error(buf.data()); } +inline void LogErrorFmtThrow(const char *fileName, int line, const wchar_t *fmt, + ...) { + va_list args; + va_start(args, fmt); + std::wstring buf(vFormatToWString(fmt, args)); + va_end(args); + + std::wstringstream wss; + wss << L"Error in file: " << fileName << L" at line: " << line << L"\n" + << buf.data() << L"\n" + << buf; + + WEX::Logging::Log::Error(wss.str().c_str()); + + // Throws an exception to abort the test. + VERIFY_FAIL(L"Test error"); +} + +// Macro to pass the file name and line number. Otherwise TAEF prints this file +// and line number. +#define LOG_ERROR_FMT_THROW(fmt, ...) \ + hlsl_test::LogErrorFmtThrow(__FILE__, __LINE__, fmt, __VA_ARGS__) + inline std::wstring GetPathToHlslDataFile(const wchar_t *relative, LPCWSTR paramName = HLSLDATAFILEPARAM, @@ -461,15 +484,17 @@ inline bool GetTestParamUseWARP(bool defaultVal) { #ifdef FP_SUBNORMAL -inline bool isdenorm(float f) { return FP_SUBNORMAL == std::fpclassify(f); } +template inline bool isdenorm(T f) { + return FP_SUBNORMAL == std::fpclassify(f); +} #else -inline bool isdenorm(float f) { - return (std::numeric_limits::denorm_min() <= f && - f < std::numeric_limits::min()) || - (-std::numeric_limits::min() < f && - f <= -std::numeric_limits::denorm_min()); +template inline bool isdenorm(T f) { + return (std::numeric_limits::denorm_min() <= f && + f < std::numeric_limits::min()) || + (-std::numeric_limits::min() < f && + f <= -std::numeric_limits::denorm_min()); } #endif // FP_SUBNORMAL @@ -517,6 +542,44 @@ inline bool isnanFloat16(uint16_t val) { uint16_t ConvertFloat32ToFloat16(float val) throw(); float ConvertFloat16ToFloat32(uint16_t val) throw(); +inline bool CompareDoubleULP( + const double &Src, const double &Ref, int64_t ULPTolerance, + hlsl::DXIL::Float32DenormMode Mode = hlsl::DXIL::Float32DenormMode::Any) { + if (Src == Ref) { + return true; + } + if (std::isnan(Src)) { + return std::isnan(Ref); + } + + if (Mode == hlsl::DXIL::Float32DenormMode::Any) { + // If denorm expected, output can be sign preserved zero. Otherwise output + // should pass the regular ulp testing. + if (isdenorm(Ref) && Src == 0 && std::signbit(Src) == std::signbit(Ref)) + return true; + } + + // For FTZ or Preserve mode, we should get the expected number within + // ULPTolerance for any operations. + int64_t Diff = *((const uint64_t *)&Src) - *((const uint64_t *)&Ref); + + uint64_t AbsoluteDiff = Diff < 0 ? -Diff : Diff; + return AbsoluteDiff <= (uint64_t)ULPTolerance; +} + +inline bool CompareDoubleEpsilon(const double &Src, const double &Ref, + float Epsilon) { + if (Src == Ref) { + return true; + } + if (std::isnan(Src)) { + return std::isnan(Ref); + } + // For FTZ or Preserve mode, we should get the expected number within + // epsilon for any operations. + return fabs(Src - Ref) < Epsilon; +} + inline bool CompareFloatULP( const float &fsrc, const float &fref, int ULPTolerance, hlsl::DXIL::Float32DenormMode mode = hlsl::DXIL::Float32DenormMode::Any) { @@ -568,12 +631,26 @@ inline bool CompareFloatRelativeEpsilon( inline bool CompareHalfULP(const uint16_t &fsrc, const uint16_t &fref, float ULPTolerance) { + // Treat +0 and -0 as equal + if ((fsrc & ~FLOAT16_BIT_SIGN) == 0 && (fref & ~FLOAT16_BIT_SIGN) == 0) + return true; if (fsrc == fref) return true; - if (isnanFloat16(fsrc)) - return isnanFloat16(fref); + + const bool nanRef = isnanFloat16(fref); + const bool nanSrc = isnanFloat16(fsrc); + if (nanRef || nanSrc) + return nanRef && nanSrc; + + // Map to monotonic ordering for correct ULP diff + auto toOrdered = [](uint16_t h) -> int { + return (h & FLOAT16_BIT_SIGN) ? (~h & 0xFFFF) : (h | 0x8000); + }; + // 16-bit floating point numbers must preserve denorms - int diff = fsrc - fref; + int i_fsrc = toOrdered(fsrc); + int i_fref = toOrdered(fref); + int diff = i_fsrc - i_fref; unsigned int uDiff = diff < 0 ? -diff : diff; return uDiff <= (unsigned int)ULPTolerance; } diff --git a/tools/clang/unittests/HLSLExec/CMakeLists.txt b/tools/clang/unittests/HLSLExec/CMakeLists.txt index df61aad854..b490ac94e9 100644 --- a/tools/clang/unittests/HLSLExec/CMakeLists.txt +++ b/tools/clang/unittests/HLSLExec/CMakeLists.txt @@ -9,6 +9,7 @@ add_clang_library(ExecHLSLTests SHARED ExecutionTest.cpp ShaderOpTest.cpp TableParameterHandler.cpp + LongVectors.cpp ExecHLSLTests.rc ) diff --git a/tools/clang/unittests/HLSLExec/ExecHLSLTests.rc b/tools/clang/unittests/HLSLExec/ExecHLSLTests.rc index 6f4659910c..29459ee825 100644 --- a/tools/clang/unittests/HLSLExec/ExecHLSLTests.rc +++ b/tools/clang/unittests/HLSLExec/ExecHLSLTests.rc @@ -1,3 +1,4 @@ #include -ShaderOpArithTable.xml DATASOURCE_XML "ShaderOpArithTable.xml" \ No newline at end of file +ShaderOpArithTable.xml DATASOURCE_XML "ShaderOpArithTable.xml" +LongVectorOpTable.xml DATASOURCE_XML "LongVectorOpTable.xml" diff --git a/tools/clang/unittests/HLSLExec/LongVectorOpTable.xml b/tools/clang/unittests/HLSLExec/LongVectorOpTable.xml new file mode 100644 index 0000000000..39a2fa481e --- /dev/null +++ b/tools/clang/unittests/HLSLExec/LongVectorOpTable.xml @@ -0,0 +1,515 @@ + + + + + + String + + String + String + String + + + + BinaryOpType_ScalarAdd + int16 + + + BinaryOpType_Add + int16 + + + BinaryOpType_ScalarSubtract + int16 + + + BinaryOpType_Subtract + int16 + + + BinaryOpType_ScalarMultiply + int16 + + + BinaryOpType_Multiply + int16 + + + BinaryOpType_ScalarDivide + int16 + + + BinaryOpType_Divide + int16 + + + BinaryOpType_ScalarModulus + int16 + + + BinaryOpType_Modulus + int16 + + + BinaryOpType_ScalarMin + int16 + + + BinaryOpType_Min + int16 + + + BinaryOpType_ScalarMax + int16 + + + BinaryOpType_Max + int16 + + + + BinaryOpType_ScalarAdd + int32 + + + BinaryOpType_Add + int32 + + + BinaryOpType_ScalarSubtract + int32 + + + BinaryOpType_Subtract + int32 + + + BinaryOpType_ScalarMultiply + int32 + + + BinaryOpType_Multiply + int32 + + + BinaryOpType_ScalarDivide + int32 + + + BinaryOpType_Divide + int32 + + + BinaryOpType_ScalarModulus + int32 + + + BinaryOpType_Modulus + int32 + + + BinaryOpType_ScalarMin + int32 + + + BinaryOpType_Min + int32 + + + BinaryOpType_ScalarMax + int32 + + + BinaryOpType_Max + int32 + + + + BinaryOpType_ScalarAdd + int64 + + + BinaryOpType_Add + int64 + + + BinaryOpType_ScalarSubtract + int64 + + + BinaryOpType_Subtract + int64 + + + BinaryOpType_ScalarMultiply + int64 + + + BinaryOpType_Multiply + int64 + + + BinaryOpType_ScalarDivide + int64 + + + BinaryOpType_Divide + int64 + + + BinaryOpType_ScalarModulus + int64 + + + BinaryOpType_Modulus + int64 + + + BinaryOpType_ScalarMin + int64 + + + BinaryOpType_Min + int64 + + + BinaryOpType_ScalarMax + int64 + + + BinaryOpType_Max + int64 + + + + BinaryOpType_ScalarAdd + uint16 + + + BinaryOpType_Add + uint16 + + + BinaryOpType_ScalarSubtract + uint16 + + + BinaryOpType_Subtract + uint16 + + + BinaryOpType_ScalarMultiply + uint16 + + + BinaryOpType_Multiply + uint16 + + + BinaryOpType_ScalarDivide + uint16 + + + BinaryOpType_Divide + uint16 + + + BinaryOpType_ScalarModulus + uint16 + + + BinaryOpType_Modulus + uint16 + + + BinaryOpType_ScalarMin + uint16 + + + BinaryOpType_Min + uint16 + + + BinaryOpType_ScalarMax + uint16 + + + BinaryOpType_Max + uint16 + + + + BinaryOpType_ScalarAdd + uint32 + + + BinaryOpType_Add + uint32 + + + BinaryOpType_ScalarSubtract + uint32 + + + BinaryOpType_Subtract + uint32 + + + BinaryOpType_ScalarMultiply + uint32 + + + BinaryOpType_Multiply + uint32 + + + BinaryOpType_ScalarDivide + uint32 + + + BinaryOpType_Divide + uint32 + + + BinaryOpType_ScalarModulus + uint32 + + + BinaryOpType_Modulus + uint32 + + + BinaryOpType_ScalarMin + uint32 + + + BinaryOpType_Min + uint32 + + + BinaryOpType_ScalarMax + uint32 + + + BinaryOpType_Max + uint32 + + + + BinaryOpType_ScalarAdd + uint64 + + + BinaryOpType_Add + uint64 + + + BinaryOpType_ScalarSubtract + uint64 + + + BinaryOpType_Subtract + uint64 + + + BinaryOpType_ScalarMultiply + uint64 + + + BinaryOpType_Multiply + uint64 + + + BinaryOpType_ScalarDivide + uint64 + + + BinaryOpType_Divide + uint64 + + + BinaryOpType_ScalarModulus + uint64 + + + BinaryOpType_Modulus + uint64 + + + BinaryOpType_ScalarMin + uint64 + + + BinaryOpType_Min + uint64 + + + BinaryOpType_ScalarMax + uint64 + + + BinaryOpType_Max + uint64 + + + + BinaryOpType_ScalarAdd + float32 + + + BinaryOpType_Add + float32 + + + BinaryOpType_ScalarSubtract + float32 + + + BinaryOpType_Subtract + float32 + + + BinaryOpType_ScalarMultiply + float32 + + + BinaryOpType_Multiply + float32 + + + BinaryOpType_ScalarDivide + float32 + + + BinaryOpType_Divide + float32 + + + BinaryOpType_ScalarModulus + float32 + + + BinaryOpType_Modulus + float32 + + + BinaryOpType_ScalarMin + float32 + + + BinaryOpType_Min + float32 + + + BinaryOpType_ScalarMax + float32 + + + BinaryOpType_Max + float32 + + + + BinaryOpType_ScalarAdd + float64 + + + BinaryOpType_Add + float64 + + + BinaryOpType_ScalarSubtract + float64 + + + BinaryOpType_Subtract + float64 + + + BinaryOpType_ScalarMultiply + float64 + + + BinaryOpType_Multiply + float64 + + + BinaryOpType_ScalarDivide + float64 + + + BinaryOpType_Divide + float64 + + + BinaryOpType_ScalarMin + float64 + + + BinaryOpType_Min + float64 + + + BinaryOpType_ScalarMax + float64 + + + BinaryOpType_Max + float64 + +
+ + + + String + String + String + + + + UnaryOpType_Initialize + int16 + + + + UnaryOpType_Initialize + int32 + + + + UnaryOpType_Initialize + int64 + + + + UnaryOpType_Initialize + uint16 + + + + UnaryOpType_Initialize + uint32 + + + + UnaryOpType_Initialize + uint64 + + + + UnaryOpType_Initialize + float32 + + + + UnaryOpType_Initialize + float64 + +
+
diff --git a/tools/clang/unittests/HLSLExec/LongVectorTestData.h b/tools/clang/unittests/HLSLExec/LongVectorTestData.h new file mode 100644 index 0000000000..002c765609 --- /dev/null +++ b/tools/clang/unittests/HLSLExec/LongVectorTestData.h @@ -0,0 +1,74 @@ +#ifndef LONGVECTORTESTDATA_H +#define LONGVECTORTESTDATA_H + +#include +#include +#include +#include +#include + +template struct LongVectorTestData { + static const std::map> Data; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {-6, 1, 7, 3, 8, 4, -3, 8, 8, -2}}, + {L"DefaultInputValueSet2", {5, -6, -3, -2, 9, 3, 1, -3, -7, 2}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {-6, 1, 7, 3, 8, 4, -3, 8, 8, -2}}, + {L"DefaultInputValueSet2", {5, -6, -3, -2, 9, 3, 1, -3, -7, 2}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {-6, 11, 7, 3, 8, 4, -3, 8, 8, -2}}, + {L"DefaultInputValueSet2", {5, -1337, -3, -2, 9, 3, 1, -3, 501, 2}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {1, 699, 3, 1023, 5, 6, 0, 8, 9, 10}}, + {L"DefaultInputValueSet2", {2, 111, 3, 4, 5, 9, 21, 8, 9, 10}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {1, 2, 3, 4, 5, 0, 7, 8, 9, 10}}, + {L"DefaultInputValueSet2", {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", {1, 2, 3, 4, 5, 0, 7, 1000, 9, 10}}, + {L"DefaultInputValueSet2", {1, 2, 1337, 4, 5, 6, 7, 8, 9, 10}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", + {1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0}}, + {L"DefaultInputValueSet2", + {1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0}}, + }; +}; + +template <> struct LongVectorTestData { + inline static const std::map> Data = { + {L"DefaultInputValueSet1", + {1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0}}, + {L"DefaultInputValueSet2", + {1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0}}, + }; +}; + +#endif // LONGVECTORTESTDATA_H diff --git a/tools/clang/unittests/HLSLExec/LongVectors.cpp b/tools/clang/unittests/HLSLExec/LongVectors.cpp new file mode 100644 index 0000000000..54e5224798 --- /dev/null +++ b/tools/clang/unittests/HLSLExec/LongVectors.cpp @@ -0,0 +1,316 @@ +#include "LongVectors.h" +#include "HlslExecTestUtils.h" +#include + +LongVector::BinaryOpType +LongVector::getBinaryOpType(const std::wstring &OpTypeString) { + return getLongVectorOpType( + binaryOpTypeStringToEnumMap, OpTypeString, + std::size(binaryOpTypeStringToEnumMap)); +} + +LongVector::UnaryOpType +LongVector::getUnaryOpType(const std::wstring &OpTypeString) { + return getLongVectorOpType( + unaryOpTypeStringToEnumMap, OpTypeString, + std::size(unaryOpTypeStringToEnumMap)); +} + +// These are helper arrays to be used with the TableParameterHandler that parses +// the LongVectorOpTable.xml file for us. +static TableParameter BinaryOpParameters[] = { + {L"DataType", TableParameter::STRING, true}, + {L"OpTypeEnum", TableParameter::STRING, true}, + {L"InputValueSetName1", TableParameter::STRING, false}, + {L"InputValueSetName2", TableParameter::STRING, false}, +}; + +static TableParameter UnaryOpParameters[] = { + {L"DataType", TableParameter::STRING, true}, + {L"OpTypeEnum", TableParameter::STRING, true}, + {L"InputValueSetName1", TableParameter::STRING, false}, +}; + +bool LongVector::OpTest::classSetup() { + // Run this only once. + if (!Initialized) { + Initialized = true; + + HMODULE Runtime = LoadLibraryW(L"d3d12.dll"); + if (Runtime == NULL) + return false; + // Do not: FreeLibrary(hRuntime); + // If we actually free the library, it defeats the purpose of + // enableAgilitySDK and enableExperimentalMode. + + HRESULT HR; + HR = enableAgilitySDK(Runtime); + + if (FAILED(HR)) + hlsl_test::LogCommentFmt(L"Unable to enable Agility SDK - 0x%08x.", HR); + else if (HR == S_FALSE) + hlsl_test::LogCommentFmt(L"Agility SDK not enabled."); + else + hlsl_test::LogCommentFmt(L"Agility SDK enabled."); + + HR = enableExperimentalMode(Runtime); + if (FAILED(HR)) + hlsl_test::LogCommentFmt( + L"Unable to enable shader experimental mode - 0x%08x.", HR); + else if (HR == S_FALSE) + hlsl_test::LogCommentFmt(L"Experimental mode not enabled."); + else + hlsl_test::LogCommentFmt(L"Experimental mode enabled."); + + HR = enableDebugLayer(); + if (FAILED(HR)) + hlsl_test::LogCommentFmt(L"Unable to enable debug layer - 0x%08x.", HR); + else if (HR == S_FALSE) + hlsl_test::LogCommentFmt(L"Debug layer not enabled."); + else + hlsl_test::LogCommentFmt(L"Debug layer enabled."); + } + + return true; +} + +TEST_F(LongVector::OpTest, binaryOpTest) { + WEX::TestExecution::SetVerifyOutput verifySettings( + WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + + using namespace WEX::Common; + + const int TableSize = sizeof(BinaryOpParameters) / sizeof(TableParameter); + TableParameterHandler Handler(BinaryOpParameters, TableSize); + + std::wstring DataType(Handler.GetTableParamByName(L"DataType")->m_str); + std::wstring OpTypeString(Handler.GetTableParamByName(L"OpTypeEnum")->m_str); + + auto OpType = LongVector::getBinaryOpType(OpTypeString); + dispatchTestByDataType(OpType, DataType, Handler); +} + +TEST_F(LongVector::OpTest, unaryOpTest) { + WEX::TestExecution::SetVerifyOutput verifySettings( + WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + + const int TableSize = sizeof(UnaryOpParameters) / sizeof(TableParameter); + TableParameterHandler Handler(UnaryOpParameters, TableSize); + + std::wstring DataType(Handler.GetTableParamByName(L"DataType")->m_str); + std::wstring OpTypeString(Handler.GetTableParamByName(L"OpTypeEnum")->m_str); + + auto OpType = LongVector::getUnaryOpType(OpTypeString); + dispatchTestByDataType(OpType, DataType, Handler); +} + +template +void LongVector::OpTest::dispatchTestByDataType( + LongVectorOpTypeT OpType, std::wstring DataType, + TableParameterHandler &Handler) { + using namespace WEX::Common; + + if (DataType == L"int16") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"int32") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"int64") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"uint16") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"uint32") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"uint64") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"float32") + dispatchTestByVectorSize(OpType, Handler); + else if (DataType == L"float64") + dispatchTestByVectorSize(OpType, Handler); + else + VERIFY_FAIL( + String().Format(L"DataType: %s is not recognized.", DataType.c_str())); +} + +template +void LongVector::OpTest::dispatchTestByVectorSize( + LongVectorOpTypeT opType, TableParameterHandler &Handler) { + WEX::TestExecution::SetVerifyOutput verifySettings( + WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + + LongVector::TestConfig TestConfig(opType); + + // InputValueSetName1 is optional. So the string may be empty. An empty + // string will result in the default value set for this DataType being used. + std::wstring InputValueSet1( + Handler.GetTableParamByName(L"InputValueSetName1")->m_str); + if (!InputValueSet1.empty()) + TestConfig.setInputValueSet1(InputValueSet1); + + // InputValueSetName2 is optional. So the string may be empty. An empty + // string will result in the default value set for this DataType being used. + if (TestConfig.isBinaryOp()) { + std::wstring InputValueSet2( + Handler.GetTableParamByName(L"InputValueSetName2")->m_str); + if (!InputValueSet2.empty()) + TestConfig.setInputValueSet2(InputValueSet2); + } + + std::vector InputVectorSizes = {3, 4, 5, 16, 17, 35, 100, 256, 1024}; + for (auto SizeToTest : InputVectorSizes) { + testBaseMethod(TestConfig, SizeToTest); + } +} + +template +void LongVector::OpTest::testBaseMethod( + LongVector::TestConfig &TestConfig, + size_t VectorSizeToTest) { + WEX::TestExecution::SetVerifyOutput verifySettings( + WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures); + + hlsl_test::LogCommentFmt(L"Running LongVectorOpTestBase<%S, %zu>", + typeid(DataTypeT).name(), VectorSizeToTest); + + bool LogInputs = false; + WEX::TestExecution::RuntimeParameters::TryGetValue(L"LongVectorLogInputs", + LogInputs); + + CComPtr D3DDevice; + if (!createDevice(&D3DDevice, ExecTestUtils::D3D_SHADER_MODEL_6_9, false)) { +#ifdef _HLK_CONF + LOG_ERROR_FMT_THROW( + L"Device does not support SM 6.9. Can't run these tests."); +#else + WEX::Logging::Log::Comment( + "Device does not support SM 6.9. Can't run these tests."); + WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped); + return; +#endif + } + + std::vector InputVector1; + InputVector1.reserve(VectorSizeToTest); + std::vector InputVector2; // May be unused, but must be defined. + InputVector2.reserve(VectorSizeToTest); + std::vector ScalarInput; // May be unused, but must be defined. + const bool IsVectorBinaryOp = + TestConfig.isBinaryOp() && !TestConfig.isScalarOp(); + + std::vector InputVector1ValueSet = TestConfig.getInputValueSet1(); + std::vector InputVector2ValueSet = + TestConfig.isBinaryOp() ? TestConfig.getInputValueSet2() + : std::vector(); + + if (TestConfig.isScalarOp()) + // Scalar ops are always binary ops. So InputVector2ValueSet is initialized + // with values above. + ScalarInput.push_back(InputVector2ValueSet[0]); + + // Fill the input vectors with values from the value set. Repeat the values + // when we reach the end of the value set. + for (size_t Index = 0; Index < VectorSizeToTest; Index++) { + InputVector1.push_back( + InputVector1ValueSet[Index % InputVector1ValueSet.size()]); + + if (IsVectorBinaryOp) + InputVector2.push_back( + InputVector2ValueSet[Index % InputVector2ValueSet.size()]); + } + + std::vector ExpectedVector; + ExpectedVector.reserve(VectorSizeToTest); + if (IsVectorBinaryOp) + ExpectedVector = + computeExpectedValues(InputVector1, InputVector2, TestConfig); + else if (TestConfig.isScalarOp()) + ExpectedVector = + computeExpectedValues(InputVector1, ScalarInput[0], TestConfig); + else // Must be a unary op + ExpectedVector = computeExpectedValues(InputVector1, TestConfig); + + if (LogInputs) { + logLongVector(InputVector1, L"InputVector1"); + + if (IsVectorBinaryOp) + logLongVector(InputVector2, L"InputVector2"); + else if (TestConfig.isScalarOp()) + logLongVector(ScalarInput, L"ScalarInput"); + } + + // We have to construct the string outside of the lambda. Otherwise it's + // cleaned up when the lambda finishes executing but before the shader runs. + std::string CompilerOptionsString = + TestConfig.getCompilerOptionsString(VectorSizeToTest); + + // The name of the shader we want to use in ShaderOpArith.xml. Could also add + // logic to set this name in ShaderOpArithTable.xml so we can use different + // shaders for different tests. + LPCSTR ShaderName = "LongVectorOp"; + // ShaderOpArith.xml defines the input/output resources and the shader source. + CComPtr TestXML; + readHlslDataIntoNewStream(L"ShaderOpArith.xml", &TestXML, DxcDllSupport); + + // RunShaderOpTest is a helper function that handles resource creation + // and setup. It also handles the shader compilation and execution. It takes a + // callback that is called when the shader is compiled, but before it is + // executed. + std::shared_ptr TestResult = st::RunShaderOpTest( + D3DDevice, DxcDllSupport, TestXML, ShaderName, + [&](LPCSTR Name, std::vector &ShaderData, st::ShaderOp *ShaderOp) { + hlsl_test::LogCommentFmt(L"RunShaderOpTest CallBack. Resource Name: %S", + Name); + + // This callback is called once for each resource defined for + // "LongVectorOp" in ShaderOpArith.xml. All callbacks are fired for each + // resource. We determine whether they are applicable to the test case + // when they run. + + // Process the callback for the OutputVector resource. + if (0 == _stricmp(Name, "OutputVector")) { + // We only need to set the compiler options string once. So this is a + // convenient place to do it. + ShaderOp->Shaders.at(0).Arguments = CompilerOptionsString.c_str(); + + return; + } + + // Process the callback for the InputFuncArgs resource. + if (0 == _stricmp(Name, "InputFuncArgs")) { + if (TestConfig.isScalarOp()) + fillShaderBufferFromLongVectorData(ShaderData, + ScalarInput); + return; + } + + // Process the callback for the InputVector1 resource. + if (0 == _stricmp(Name, "InputVector1")) { + fillShaderBufferFromLongVectorData(ShaderData, + InputVector1); + return; + } + + // Process the callback for the InputVector2 resource. + if (0 == _stricmp(Name, "InputVector2")) { + if (IsVectorBinaryOp) + fillShaderBufferFromLongVectorData(ShaderData, + InputVector2); + + return; + } + + LOG_ERROR_FMT_THROW( + L"RunShaderOpTest CallBack. Unexpected Resource Name: %S", Name); + }); + + // Map the data from GPU to CPU memory so we can verify our expectations. + MappedData ShaderOutData; + TestResult->Test->GetReadBackData("OutputVector", &ShaderOutData); + + std::vector OutputVector; + fillLongVectorDataFromShaderBuffer(ShaderOutData, OutputVector, + VectorSizeToTest); + + VERIFY_SUCCEEDED(doVectorsMatch(OutputVector, ExpectedVector, + TestConfig.getTolerance(), + TestConfig.getValidationType())); +} diff --git a/tools/clang/unittests/HLSLExec/LongVectors.h b/tools/clang/unittests/HLSLExec/LongVectors.h new file mode 100644 index 0000000000..392d059bcd --- /dev/null +++ b/tools/clang/unittests/HLSLExec/LongVectors.h @@ -0,0 +1,282 @@ +#ifndef LONGVECTORS_H +#define LONGVECTORS_H + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "LongVectorTestData.h" +#include "ShaderOpTest.h" +#include "TableParameterHandler.h" +#include "dxc/Support/WinIncludes.h" +#include "dxc/Support/dxcapi.use.h" +#include "dxc/Test/HlslTestUtils.h" + +namespace LongVector { +template +class TestConfig; // Forward declaration + +class OpTest { +public: + BEGIN_TEST_CLASS(OpTest) + END_TEST_CLASS() + + TEST_CLASS_SETUP(classSetup); + + BEGIN_TEST_METHOD(binaryOpTest) + TEST_METHOD_PROPERTY(L"DataSource", + L"Table:LongVectorOpTable.xml#BinaryOpTable") + END_TEST_METHOD() + + BEGIN_TEST_METHOD(unaryOpTest) + TEST_METHOD_PROPERTY(L"DataSource", + L"Table:LongVectorOpTable.xml#UnaryOpTable") + END_TEST_METHOD() + + template + void dispatchTestByDataType(LongVectorOpTypeT OpType, std::wstring DataType, + TableParameterHandler &Handler); + + template + void dispatchTestByVectorSize(LongVectorOpTypeT OpType, + TableParameterHandler &Handler); + + template + void testBaseMethod( + LongVector::TestConfig &TestConfig, + size_t VectorSizeToTest); + +private: + dxc::DxcDllSupport DxcDllSupport; + bool Initialized = false; +}; + +template +void fillShaderBufferFromLongVectorData(std::vector &ShaderBuffer, + std::vector &TestData); + +template +void fillLongVectorDataFromShaderBuffer(MappedData &ShaderBuffer, + std::vector &TestData, + size_t NumElements); + +template constexpr bool isFloatingPointType() { + return std::is_same_v || std::is_same_v; +} + +struct LongVectorOpTypeStringToEnumValue { + std::wstring OpTypeString; + uint32_t OpTypeValue; +}; + +template +DataTypeT getLongVectorOpType(const LongVectorOpTypeStringToEnumValue *Values, + const std::wstring &OpTypeString, + std::size_t Length); + +enum ValidationType { + ValidationType_Epsilon, + ValidationType_Ulp, +}; + +enum BasicOpType { + BasicOpType_Binary, + BasicOpType_Unary, + BasicOpType_ScalarBinary, + BasicOpType_EnumValueCount +}; + +enum BinaryOpType { + BinaryOpType_ScalarAdd, + BinaryOpType_ScalarMultiply, + BinaryOpType_ScalarSubtract, + BinaryOpType_ScalarDivide, + BinaryOpType_ScalarModulus, + BinaryOpType_Multiply, + BinaryOpType_Add, + BinaryOpType_Subtract, + BinaryOpType_Divide, + BinaryOpType_Modulus, + BinaryOpType_Min, + BinaryOpType_Max, + BinaryOpType_ScalarMin, + BinaryOpType_ScalarMax, + BinaryOpType_EnumValueCount +}; + +static const LongVectorOpTypeStringToEnumValue binaryOpTypeStringToEnumMap[] = { + {L"BinaryOpType_ScalarAdd", BinaryOpType_ScalarAdd}, + {L"BinaryOpType_ScalarMultiply", BinaryOpType_ScalarMultiply}, + {L"BinaryOpType_ScalarSubtract", BinaryOpType_ScalarSubtract}, + {L"BinaryOpType_ScalarDivide", BinaryOpType_ScalarDivide}, + {L"BinaryOpType_ScalarModulus", BinaryOpType_ScalarModulus}, + {L"BinaryOpType_Add", BinaryOpType_Add}, + {L"BinaryOpType_Multiply", BinaryOpType_Multiply}, + {L"BinaryOpType_Subtract", BinaryOpType_Subtract}, + {L"BinaryOpType_Divide", BinaryOpType_Divide}, + {L"BinaryOpType_Modulus", BinaryOpType_Modulus}, + {L"BinaryOpType_Min", BinaryOpType_Min}, + {L"BinaryOpType_Max", BinaryOpType_Max}, + {L"BinaryOpType_ScalarMin", BinaryOpType_ScalarMin}, + {L"BinaryOpType_ScalarMax", BinaryOpType_ScalarMax}, +}; + +static_assert(_countof(binaryOpTypeStringToEnumMap) == + BinaryOpType_EnumValueCount, + "binaryOpTypeStringToEnumMap size mismatch. Did you " + "add a new enum value?"); + +BinaryOpType getBinaryOpType(const std::wstring &OpTypeString); + +enum UnaryOpType { UnaryOpType_Initialize, UnaryOpType_EnumValueCount }; + +static const LongVectorOpTypeStringToEnumValue unaryOpTypeStringToEnumMap[] = { + {L"UnaryOpType_Initialize", UnaryOpType_Initialize}, +}; + +static_assert(_countof(unaryOpTypeStringToEnumMap) == + UnaryOpType_EnumValueCount, + "unaryOpTypeStringToEnumMap size mismatch. Did you add " + "a new enum value?"); + +UnaryOpType getUnaryOpType(const std::wstring &OpTypeString); + +template +std::vector getInputValueSetByKey(const std::wstring &Key, + bool LogKey = true) { + if (LogKey) + WEX::Logging::Log::Comment( + WEX::Common::String().Format(L"Using Value Set Key: %s", Key.c_str())); + return std::vector(LongVectorTestData::Data.at(Key)); +} + +template +DataTypeT mod(const DataTypeT &A, const DataTypeT &B); + +template struct TestConfigTraits { + TestConfigTraits(LongVectorOpTypeT OpType) : OpType(OpType) {} + // LongVectorOpTypeT* Enum values. We don't use a UINT because + // we want the type data. + LongVectorOpTypeT OpType; +}; + +template +bool doValuesMatch(DataTypeT A, DataTypeT B, float Tolerance, ValidationType); +bool doValuesMatch(float A, float B, float Tolerance, + ValidationType ValidationType); +bool doValuesMatch(double A, double B, float Tolerance, + ValidationType ValidationType); + +template +bool doVectorsMatch(const std::vector &ActualValues, + const std::vector &ExpectedValues, + float Tolerance, ValidationType ValidationType); +// Binary ops +template +std::vector +computeExpectedValues(const std::vector &InputVector1, + const std::vector &InputVector2, + const TestConfig &Config); + +// Binary scalar ops +template +std::vector +computeExpectedValues(const std::vector &InputVector1, + const DataTypeT &ScalarInput, + const TestConfig &Config); + +// Unary ops +template +std::vector +computeExpectedValues(const std::vector &InputVector1, + const TestConfig &Config); + +template +void logLongVector(const std::vector &Values, + const std::wstring &Name); + +// Used to pass into LongVectorOpTestBase +template class TestConfig { +public: + TestConfig() = default; + + TestConfig(UnaryOpType OpType); + TestConfig(BinaryOpType OpType); + + bool isBinaryOp() const { + return BasicOpType == LongVector::BasicOpType_Binary || + BasicOpType == LongVector::BasicOpType_ScalarBinary; + } + + bool isUnaryOp() const { + return BasicOpType == LongVector::BasicOpType_Unary; + } + + bool isScalarOp() const { + return BasicOpType == LongVector::BasicOpType_ScalarBinary; + } + + bool hasFunctionDefinition() const; + std::string getOPERAND2String() const; + + // A helper to get the hlsl type as a string for a given C++ type. + // Used in the long vector tests. + std::string getHLSLTypeString() const; + + DataTypeT computeExpectedValue(const DataTypeT &A, const DataTypeT &B, + BinaryOpType OpType) const; + DataTypeT computeExpectedValue(const DataTypeT &A, const DataTypeT &B) const; + DataTypeT computeExpectedValue(const DataTypeT &A, UnaryOpType OpType) const; + DataTypeT computeExpectedValue(const DataTypeT &A) const; + + void setInputValueSet1(const std::wstring &InputValueSetName) { + this->InputValueSetName1 = InputValueSetName; + } + + void setInputValueSet2(const std::wstring &InputValueSetName) { + this->InputValueSetName2 = InputValueSetName; + } + + std::vector getInputValueSet1() const { + return getInputValueSet(1); + } + + std::vector getInputValueSet2() const { + return getInputValueSet(2); + } + + float getTolerance() const { return Tolerance; } + LongVector::ValidationType getValidationType() const { + return ValidationType; + } + + std::string getCompilerOptionsString(size_t VectorSize) const; + +private: + std::vector getInputValueSet(size_t ValueSetIndex) const; + + // To be used for the value of -DOPERATOR + std::string OperatorString; + // To be used for the value of -DFUNC + std::string IntrinsicString; + LongVector::BasicOpType BasicOpType = LongVector::BasicOpType_EnumValueCount; + float Tolerance = 0.0; + LongVector::ValidationType ValidationType = + LongVector::ValidationType::ValidationType_Epsilon; + LongVector::TestConfigTraits OpTypeTraits; + std::wstring InputValueSetName1 = L"DefaultInputValueSet1"; + std::wstring InputValueSetName2 = L"DefaultInputValueSet2"; +}; // class LongVector::TestConfig + +}; // namespace LongVector + +#include "LongVectors.tpp" + +#endif // LONGVECTORS_H diff --git a/tools/clang/unittests/HLSLExec/LongVectors.tpp b/tools/clang/unittests/HLSLExec/LongVectors.tpp new file mode 100644 index 0000000000..de333cf863 --- /dev/null +++ b/tools/clang/unittests/HLSLExec/LongVectors.tpp @@ -0,0 +1,476 @@ +template +DataTypeT LongVector::getLongVectorOpType(const LongVectorOpTypeStringToEnumValue *Values, + const std::wstring &OpTypeString, + std::size_t Length) { + for (size_t i = 0; i < Length; i++) { + if (Values[i].OpTypeString == OpTypeString) + return static_cast(Values[i].OpTypeValue); + } + + LOG_ERROR_FMT_THROW(L"Invalid LongVectorOpType string: %s", + OpTypeString.c_str()); + + return static_cast(UINT_MAX); +} + +// Helper to fill the shader buffer based on type. Convenient to be used when +// copying HLSL*_t types so we can copy the underlying type directly instead of +// the struct. +template +void LongVector::fillShaderBufferFromLongVectorData(std::vector &ShaderBuffer, std::vector &TestData) { + + const size_t NumElements = TestData.size(); + const size_t DataSize = sizeof(DataTypeT) * NumElements; + ShaderBuffer.resize(DataSize); + + DataTypeT *ShaderBufferPtr = + reinterpret_cast(ShaderBuffer.data()); + for (size_t i = 0; i < NumElements; ++i) + ShaderBufferPtr[i] = TestData[i]; +} + +// Helpers so we do the right thing for float types. +template +DataTypeT LongVector::mod(const DataTypeT &A, const DataTypeT &B) { + return A % B; +} + +template <> float LongVector::mod(const float &A, const float &B) { + return std::fmod(A, B); +} + +template <> double LongVector::mod(const double &A, const double &B) { + return std::fmod(A, B); +} + +// Helper to fill the test data from the shader buffer based on type. Convenient +// to be used when copying HLSL*_t types so we can use the underlying type. +template +void LongVector::fillLongVectorDataFromShaderBuffer(MappedData &ShaderBuffer, + std::vector &TestData, + size_t NumElements) { + DataTypeT *ShaderBufferPtr = + reinterpret_cast(ShaderBuffer.data()); + for (size_t i = 0; i < NumElements; ++i) + TestData.push_back(ShaderBufferPtr[i]); +} + +template +bool LongVector::doValuesMatch(DataTypeT A, DataTypeT B, float Tolerance, + LongVector::ValidationType) { + if (Tolerance == 0.0f) + return A == B; + + DataTypeT Diff = A > B ? A - B : B - A; + return Diff <= Tolerance; +} + +bool LongVector::doValuesMatch(float A, float B, float Tolerance, + LongVector::ValidationType ValidationType) { + switch (ValidationType) { + case LongVector::ValidationType_Epsilon: + return CompareFloatEpsilon(A, B, Tolerance); + case LongVector::ValidationType_Ulp: { + // Tolerance is in ULPs. Convert to int for the comparison. + const int IntTolerance = static_cast(Tolerance); + return CompareFloatULP(A, B, IntTolerance); + }; + default: + WEX::Logging::Log::Error( + L"Invalid ValidationType. Expecting Epsilon or ULP."); + return false; + } +} + +bool LongVector::doValuesMatch(double A, double B, float Tolerance, + LongVector::ValidationType ValidationType) { + switch (ValidationType) { + case LongVector::ValidationType_Epsilon: + return CompareDoubleEpsilon(A, B, Tolerance); + case LongVector::ValidationType_Ulp: { + // Tolerance is in ULPs. Convert to int64_t for the comparison. + const int64_t IntTolerance = static_cast(Tolerance); + return CompareDoubleULP(A, B, IntTolerance); + }; + default: + WEX::Logging::Log::Error( + L"Invalid ValidationType. Expecting Epsilon or ULP."); + return false; + } +} + + +template +bool LongVector::doVectorsMatch(const std::vector &ActualValues, + const std::vector &ExpectedValues, + float Tolerance, + LongVector::ValidationType ValidationType) { + // Stash mismatched indexes for easy failure logging later + std::vector MismatchedIndexes; + VERIFY_IS_TRUE(ActualValues.size() == ExpectedValues.size(), + L"doVectorsMatch() called with mismatched vector sizes."); + for (size_t i = 0; i < ActualValues.size(); ++i) { + if (!doValuesMatch(ActualValues[i], ExpectedValues[i], Tolerance, + ValidationType)) + MismatchedIndexes.push_back(i); + } + + if (MismatchedIndexes.empty()) + return true; + + if (!MismatchedIndexes.empty()) { + for (size_t Index : MismatchedIndexes) { + std::wstringstream Wss(L""); + Wss << std::setprecision(15); // Set precision for floating point types + Wss << L"Mismatch at Index: " << Index; + Wss << L" Actual Value:" << ActualValues[Index] << ","; + Wss << L" Expected Value:" << ExpectedValues[Index]; + WEX::Logging::Log::Error(Wss.str().c_str()); + } + } + + return false; +} + +template +std::vector LongVector::computeExpectedValues( + const std::vector &InputVector1, + const std::vector &InputVector2, + const LongVector::TestConfig &Config) { + + VERIFY_IS_TRUE( + Config.isBinaryOp(), + L"computeExpectedValues() called with a non-binary op config."); + + std::vector ExpectedValues = {}; + + for (size_t i = 0; i < InputVector1.size(); ++i) + ExpectedValues.push_back( + Config.computeExpectedValue(InputVector1[i], InputVector2[i])); + + return ExpectedValues; +} + +template +std::vector LongVector::computeExpectedValues( + const std::vector &InputVector1, const DataTypeT &ScalarInput, + const LongVector::TestConfig &Config) { + + VERIFY_IS_TRUE(Config.isScalarOp(), L"computeExpectedValues() called with a " + L"non-binary non-scalar op config."); + + std::vector ExpectedValues; + + for (size_t i = 0; i < InputVector1.size(); ++i) + ExpectedValues.push_back( + Config.computeExpectedValue(InputVector1[i], ScalarInput)); + + return ExpectedValues; +} + +template +std::vector LongVector::computeExpectedValues( + const std::vector &InputVector1, + const LongVector::TestConfig &Config) { + + VERIFY_IS_TRUE(Config.isUnaryOp(), + L"computeExpectedValues() called with a non-unary op config."); + + std::vector ExpectedValues; + + for (size_t i = 0; i < InputVector1.size(); ++i) + ExpectedValues.push_back(Config.computeExpectedValue(InputVector1[i])); + + return ExpectedValues; +} + +template +void LongVector::logLongVector(const std::vector &Values, + const std::wstring &Name) { + WEX::Logging::Log::Comment( + WEX::Common::String().Format(L"LongVector Name: %s", Name.c_str())); + + const size_t LoggingWidth = 40; + + std::wstringstream Wss(L""); + Wss << L"LongVector Values: "; + Wss << L"["; + const size_t NumElements = Values.size(); + for (size_t i = 0; i < NumElements; i++) { + if (i % LoggingWidth == 0 && i != 0) + Wss << L"\n "; + Wss << Values[i]; + if (i != NumElements - 1) + Wss << L", "; + } + Wss << L" ]"; + + WEX::Logging::Log::Comment(Wss.str().c_str()); +} + +template +LongVector::TestConfig::TestConfig(LongVector::UnaryOpType OpType) + : OpTypeTraits(OpType) { + IntrinsicString = ""; + BasicOpType = LongVector::BasicOpType_Unary; + + if (isFloatingPointType()) + Tolerance = 1; + + switch (OpType) { + case LongVector::UnaryOpType_Initialize: + IntrinsicString = "TestInitialize"; + break; + default: + VERIFY_FAIL("Invalid UnaryOpType"); + } +} + +template +LongVector::TestConfig::TestConfig(LongVector::BinaryOpType OpType) + : OpTypeTraits(OpType) { + IntrinsicString = ""; + BasicOpType = LongVector::BasicOpType_Binary; + + if (isFloatingPointType()) + Tolerance = 1; + ValidationType = LongVector::ValidationType_Ulp; + + switch (OpType) { + case LongVector::BinaryOpType_ScalarAdd: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = "+"; + break; + case LongVector::BinaryOpType_ScalarMultiply: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = "*"; + break; + case LongVector::BinaryOpType_ScalarSubtract: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = "-"; + break; + case LongVector::BinaryOpType_ScalarDivide: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = "/"; + break; + case LongVector::BinaryOpType_ScalarModulus: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = "%"; + break; + case LongVector::BinaryOpType_Multiply: + OperatorString = "*"; + break; + case LongVector::BinaryOpType_Add: + OperatorString = "+"; + break; + case LongVector::BinaryOpType_Subtract: + OperatorString = "-"; + break; + case LongVector::BinaryOpType_Divide: + OperatorString = "/"; + break; + case LongVector::BinaryOpType_Modulus: + OperatorString = "%"; + break; + case LongVector::BinaryOpType_Min: + OperatorString = ","; + IntrinsicString = "min"; + break; + case LongVector::BinaryOpType_Max: + OperatorString = ","; + IntrinsicString = "max"; + break; + case LongVector::BinaryOpType_ScalarMin: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = ","; + IntrinsicString = "min"; + break; + case LongVector::BinaryOpType_ScalarMax: + BasicOpType = LongVector::BasicOpType_ScalarBinary; + OperatorString = ","; + IntrinsicString = "max"; + break; + default: + VERIFY_FAIL("Invalid BinaryOpType"); + } +} + +template +bool LongVector::TestConfig::hasFunctionDefinition() const { + if constexpr (std::is_same_v) { + if (OpTypeTraits.OpType == LongVector::UnaryOpType_Initialize) + return true; + else + return false; + } + + return false; +} + +template +std::string LongVector::TestConfig::getOPERAND2String() const { + if (hasFunctionDefinition()) { + switch (static_cast(OpTypeTraits.OpType)) { + case LongVector::UnaryOpType_Initialize: + return std::string(" -DFUNC_INITIALIZE=1"); + default: + VERIFY_FAIL("Invalid UnaryOpType"); + } + } + return std::string(""); +} + +template +std::string LongVector::TestConfig::getHLSLTypeString() const { + if (std::is_same_v) + return "float"; + if (std::is_same_v) + return "double"; + if (std::is_same_v) + return "int16_t"; + if (std::is_same_v) + return "int"; + if (std::is_same_v) + return "int64_t"; + if (std::is_same_v) + return "uint16_t"; + if (std::is_same_v) + return "uint32_t"; + if (std::is_same_v) + return "uint64_t"; + + std::string ErrStr("getHLSLTypeString() Unsupported type: "); + ErrStr.append(typeid(DataTypeT).name()); + VERIFY_IS_TRUE(false, ErrStr.c_str()); + return "UnknownType"; +} + +template +DataTypeT LongVector::TestConfig::computeExpectedValue(const DataTypeT &A, const DataTypeT &B, + LongVector::BinaryOpType OpType) const { + switch (OpType) { + case LongVector::BinaryOpType_ScalarAdd: + return A + B; + case LongVector::BinaryOpType_ScalarMultiply: + return A * B; + case LongVector::BinaryOpType_ScalarSubtract: + return A - B; + case LongVector::BinaryOpType_ScalarDivide: + return A / B; + case LongVector::BinaryOpType_ScalarModulus: + return mod(A, B); + case LongVector::BinaryOpType_Multiply: + return A * B; + case LongVector::BinaryOpType_Add: + return A + B; + case LongVector::BinaryOpType_Subtract: + return A - B; + case LongVector::BinaryOpType_Divide: + return A / B; + case LongVector::BinaryOpType_Modulus: + return mod(A, B); + case LongVector::BinaryOpType_Min: + // std::max and std::min are wrapped in () to avoid collisions with the // + // macro defintions for min and max in windows.h + return (std::min)(A, B); + case LongVector::BinaryOpType_Max: + return (std::max)(A, B); + case LongVector::BinaryOpType_ScalarMin: + return (std::min)(A, B); + case LongVector::BinaryOpType_ScalarMax: + return (std::max)(A, B); + default: + LOG_ERROR_FMT_THROW(L"Unknown BinaryOpType: %d", OpTypeTraits.OpType); + return DataTypeT(); + } +} + +template +DataTypeT LongVector::TestConfig::computeExpectedValue(const DataTypeT &A, const DataTypeT &B) const { + if(!isBinaryOp()) + LOG_ERROR_FMT_THROW( + L"computeExpectedValue(const DataTypeT &A, const DataTypeT &B) called " + L"on a unary op: %d", + OpTypeTraits.OpType); + + return computeExpectedValue(A, B, static_cast(OpTypeTraits.OpType)); +} + + +template +DataTypeT LongVector::TestConfig::computeExpectedValue(const DataTypeT &A, + LongVector::UnaryOpType OpType) const { + switch (OpType) { + case LongVector::UnaryOpType_Initialize: + return A; + default: + LOG_ERROR_FMT_THROW(L"Unknown UnaryOpType :%d", OpTypeTraits.OpType); + return DataTypeT(); + } +} + +template +DataTypeT LongVector::TestConfig::computeExpectedValue(const DataTypeT &A) const { + + if constexpr (std::is_same_v) { + const auto OpType = static_cast(OpTypeTraits.OpType); + return computeExpectedValue(A, OpType); + } + + LOG_ERROR_FMT_THROW( + L"computeExpectedValue(const DataType&A) called on an unrecognized binary op: %d", + OpTypeTraits.OpType); + + return DataTypeT(); +} + +template +std::string LongVector::TestConfig::getCompilerOptionsString(size_t VectorSize) const { + std::stringstream CompilerOptions(""); + std::string HLSLType = getHLSLTypeString(); + CompilerOptions << "-DTYPE="; + CompilerOptions << HLSLType; + CompilerOptions << " -DNUM="; + CompilerOptions << VectorSize; + const bool Is16BitType = + (HLSLType == "int16_t" || HLSLType == "uint16_t" || HLSLType == "half"); + CompilerOptions << (Is16BitType ? " -enable-16bit-types" : ""); + CompilerOptions << " -DOPERATOR="; + CompilerOptions << OperatorString; + + if (isBinaryOp()) { + CompilerOptions << " -DOPERAND2="; + CompilerOptions << (isScalarOp() ? "InputScalar" : "InputVector2"); + + if (isScalarOp()) + CompilerOptions << " -DIS_SCALAR_OP=1"; + else + CompilerOptions << " -DIS_BINARY_VECTOR_OP=1"; + + CompilerOptions << " -DFUNC="; + CompilerOptions << IntrinsicString; + } else { // Unary Op + CompilerOptions << " -DFUNC="; + CompilerOptions << IntrinsicString; + CompilerOptions << " -DOPERAND2="; + CompilerOptions << getOPERAND2String(); + } + + return CompilerOptions.str(); +} + +template +std::vector LongVector::TestConfig::getInputValueSet(size_t ValueSetIndex) const { + if (ValueSetIndex == 2 && !isBinaryOp()) + VERIFY_FAIL("ValueSetindex==2 is only valid for binary ops."); + + std::wstring InputValueSetName = L""; + if (ValueSetIndex == 1) + InputValueSetName = InputValueSetName1; + else if (ValueSetIndex == 2) + InputValueSetName = InputValueSetName2; + else + VERIFY_FAIL("Invalid ValueSetIndex"); + + return getInputValueSetByKey(InputValueSetName); +} diff --git a/tools/clang/unittests/HLSLExec/ShaderOpArith.xml b/tools/clang/unittests/HLSLExec/ShaderOpArith.xml index e768f205f1..a782bd97ae 100644 --- a/tools/clang/unittests/HLSLExec/ShaderOpArith.xml +++ b/tools/clang/unittests/HLSLExec/ShaderOpArith.xml @@ -3750,4 +3750,71 @@ void MSMain(uint GID : SV_GroupIndex, + + RootFlags(0), UAV(u0), UAV(u1), UAV(u2), + UAV(u3) + + + + + + + + + + + + + + + TestInitialize(vector Vector) + { + vector VectorCopy = Vector; + return VectorCopy; + } + #endif + + RWByteAddressBuffer g_InputFuncArgs : register(u0); + RWByteAddressBuffer g_InputVector1 : register(u1); + RWByteAddressBuffer g_InputVector2 : register(u2); + RWByteAddressBuffer g_OutputVector : register(u3); + [numthreads(1,1,1)] + void main(uint GI : SV_GroupIndex) { + + vector InputVector1 = g_InputVector1.Load< vector >(0); + + #ifdef IS_BINARY_VECTOR_OP + vector InputVector2 = g_InputVector2.Load< vector >(0); + #endif + + #ifdef IS_SCALAR_OP + TYPE InputScalar = g_InputFuncArgs.Load(0); + #endif + + #ifdef FUNC_CLAMP + TYPE Clamp_ArgMin = g_InputFuncArgs.Load(0); + TYPE Clamp_ArgMax = g_InputFuncArgs.Load(sizeof(TYPE)); + vector ClampArgMinMax = {Clamp_ArgMin, Clamp_ArgMax}; + #endif + + vector OutputVector = FUNC(InputVector1 OPERATOR OPERAND2); + + g_OutputVector.Store< vector >(0, OutputVector); + }; + ]]> + +