Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
94db5ff
Dot fp32 precision
alsepkow Oct 7, 2025
97691fb
Change error tolerance for dot
alsepkow Oct 21, 2025
856e1ff
Added tolerance for dot based on discussion with Tex. Needs some cleanup
alsepkow Oct 22, 2025
06f0223
Clang format
alsepkow Oct 22, 2025
547db3c
Working with updates from talking with Tex. Need to clean-up and do s…
alsepkow Oct 22, 2025
2443751
Some minor cleanup
alsepkow Oct 23, 2025
4e5c969
Fix error tolerance for dot
alsepkow Oct 23, 2025
8593673
Revert CIndexDiagnostic.h
alsepkow Oct 23, 2025
a7ef0a4
chore: autopublish 2025-10-23T18:21:18Z
github-actions[bot] Oct 23, 2025
aa8805f
revert argument type on op when reference not needed
alsepkow Oct 24, 2025
45914bf
Some code review updates for Tex
alsepkow Oct 27, 2025
542fd0c
chore: autopublish 2025-10-27T20:30:01Z
github-actions[bot] Oct 27, 2025
f90fc56
Some more feedback from Tex
alsepkow Oct 27, 2025
7e1f9ea
Update tools/clang/unittests/HLSLExec/LongVectors.cpp
alsepkow Oct 27, 2025
ad6cbde
Update tools/clang/unittests/HLSLExec/LongVectors.cpp
alsepkow Oct 27, 2025
93c08ee
Fix minor issue with sum loop
alsepkow Oct 28, 2025
435b670
Add assert to prevent inf and nan in HLSLHalf_t::GetULP
alsepkow Oct 28, 2025
a4f4472
Can use std functions
alsepkow Oct 28, 2025
a9ddd02
chore: autopublish 2025-10-28T00:31:17Z
github-actions[bot] Oct 28, 2025
a5fa085
Dont need to include numeric anymore
alsepkow Oct 28, 2025
09bc6cb
Change validation config to use double instead of float. Higher preci…
alsepkow Oct 29, 2025
3391ef6
Clang format
alsepkow Oct 29, 2025
44d7bc1
chore: autopublish 2025-10-29T03:42:42Z
github-actions[bot] Oct 29, 2025
e0d0a2b
Restore CIndexDiagnostic.h, again, again
alsepkow Oct 29, 2025
c0edadd
merge conflict
alsepkow Oct 29, 2025
00b6b2f
Last minor feedback from Tex
alsepkow Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/dxc/Test/HlslTestUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ inline bool CompareDoubleULP(
}

inline bool CompareDoubleEpsilon(const double &Src, const double &Ref,
float Epsilon) {
double Epsilon) {
if (Src == Ref) {
return true;
}
Expand Down
18 changes: 14 additions & 4 deletions tools/clang/unittests/HLSLExec/LongVectorTestData.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <DirectXMath.h>
#include <DirectXPackedVector.h>

#include "dxc/Support/Global.h"

namespace LongVector {

// A helper struct because C++ bools are 1 byte and HLSL bools are 4 bytes.
Expand Down Expand Up @@ -118,6 +120,18 @@ struct HLSLHalf_t {
// float.
HLSLHalf_t(DirectX::PackedVector::HALF) = delete;

static double GetULP(HLSLHalf_t A) {
DXASSERT(!std::isnan(A) && !std::isinf(A),
"ULP of NaN or infinity is undefined");

HLSLHalf_t Next = A;
++Next.Val;

double NextD = Next;
double AD = A;
return NextD - AD;
}

static HLSLHalf_t FromHALF(DirectX::PackedVector::HALF Half) {
HLSLHalf_t H;
H.Val = Half;
Expand Down Expand Up @@ -184,10 +198,6 @@ struct HLSLHalf_t {
return FromHALF((DirectX::PackedVector::XMConvertFloatToHalf(A + B)));
}

HLSLHalf_t &operator+=(const HLSLHalf_t &Other) {
return *this = *this + Other;
}

HLSLHalf_t operator-(const HLSLHalf_t &Other) const {
const float A = DirectX::PackedVector::XMConvertHalfToFloat(Val);
const float B = DirectX::PackedVector::XMConvertHalfToFloat(Other.Val);
Expand Down
128 changes: 105 additions & 23 deletions tools/clang/unittests/HLSLExec/LongVectors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "HlslExecTestUtils.h"

#include <algorithm>
#include <array>
#include <bitset>
#include <iomanip>
Expand Down Expand Up @@ -175,37 +176,37 @@ enum class ValidationType {
};

template <typename T>
bool doValuesMatch(T A, T B, float Tolerance, ValidationType) {
if (Tolerance == 0.0f)
bool doValuesMatch(T A, T B, double Tolerance, ValidationType) {
if (Tolerance == 0.0)
return A == B;

T Diff = A > B ? A - B : B - A;
return Diff <= Tolerance;
}

bool doValuesMatch(HLSLBool_t A, HLSLBool_t B, float, ValidationType) {
bool doValuesMatch(HLSLBool_t A, HLSLBool_t B, double, ValidationType) {
return A == B;
}

bool doValuesMatch(HLSLHalf_t A, HLSLHalf_t B, float Tolerance,
bool doValuesMatch(HLSLHalf_t A, HLSLHalf_t B, double Tolerance,
ValidationType ValidationType) {
switch (ValidationType) {
case ValidationType::Epsilon:
return CompareHalfEpsilon(A.Val, B.Val, Tolerance);
return CompareHalfEpsilon(A.Val, B.Val, static_cast<float>(Tolerance));
case ValidationType::Ulp:
return CompareHalfULP(A.Val, B.Val, Tolerance);
return CompareHalfULP(A.Val, B.Val, static_cast<float>(Tolerance));
default:
hlsl_test::LogErrorFmt(
L"Invalid ValidationType. Expecting Epsilon or ULP.");
return false;
}
}

bool doValuesMatch(float A, float B, float Tolerance,
bool doValuesMatch(float A, float B, double Tolerance,
ValidationType ValidationType) {
switch (ValidationType) {
case ValidationType::Epsilon:
return CompareFloatEpsilon(A, B, Tolerance);
return CompareFloatEpsilon(A, B, static_cast<float>(Tolerance));
case ValidationType::Ulp: {
// Tolerance is in ULPs. Convert to int for the comparison.
const int IntTolerance = static_cast<int>(Tolerance);
Expand All @@ -218,7 +219,7 @@ bool doValuesMatch(float A, float B, float Tolerance,
}
}

bool doValuesMatch(double A, double B, float Tolerance,
bool doValuesMatch(double A, double B, double Tolerance,
ValidationType ValidationType) {
switch (ValidationType) {
case ValidationType::Epsilon:
Expand All @@ -237,7 +238,7 @@ bool doValuesMatch(double A, double B, float Tolerance,

template <typename T>
bool doVectorsMatch(const std::vector<T> &ActualValues,
const std::vector<T> &ExpectedValues, float Tolerance,
const std::vector<T> &ExpectedValues, double Tolerance,
ValidationType ValidationType, bool VerboseLogging) {

DXASSERT(
Expand All @@ -247,6 +248,11 @@ bool doVectorsMatch(const std::vector<T> &ActualValues,
if (VerboseLogging) {
logLongVector(ActualValues, L"ActualValues");
logLongVector(ExpectedValues, L"ExpectedValues");

hlsl_test::LogCommentFmt(
L"ValidationType: %s, Tolerance: %17g",
ValidationType == ValidationType::Epsilon ? L"Epsilon" : L"ULP",
Tolerance);
}

// Stash mismatched indexes for easy failure logging later
Expand Down Expand Up @@ -534,14 +540,14 @@ InputSets<T> buildTestInputs(size_t VectorSize, const InputSet OpInputSets[3],
}

struct ValidationConfig {
float Tolerance = 0.0f;
double Tolerance = 0.0;
ValidationType Type = ValidationType::Epsilon;

static ValidationConfig Epsilon(float Tolerance) {
static ValidationConfig Epsilon(double Tolerance) {
return ValidationConfig{Tolerance, ValidationType::Epsilon};
}

static ValidationConfig Ulp(float Tolerance) {
static ValidationConfig Ulp(double Tolerance) {
return ValidationConfig{Tolerance, ValidationType::Ulp};
}
};
Expand Down Expand Up @@ -593,7 +599,8 @@ template <typename T> struct DefaultValidation {
}
};

// Strict Validation - require exact matches for all types
// Strict Validation - Defaults to exact matches.
// Tolerance can be set to a non-zero value to allow for a wider range.
struct StrictValidation {
ValidationConfig ValidationConfig;
};
Expand Down Expand Up @@ -935,7 +942,7 @@ struct Op<OpType::AsUint_SplitDouble, double, 1> : StrictValidation {};
// values.
template <> struct ExpectedBuilder<OpType::AsUint_SplitDouble, double> {
static std::vector<uint32_t>
buildExpected(Op<OpType::AsUint_SplitDouble, double, 1>,
buildExpected(Op<OpType::AsUint_SplitDouble, double, 1> &,
const InputSets<double> &Inputs) {
DXASSERT_NOMSG(Inputs.size() == 1);

Expand Down Expand Up @@ -1009,7 +1016,7 @@ DEFAULT_OP_1(OpType::Log2, (std::log2(A)));
template <> struct Op<OpType::Frexp, float, 1> : DefaultValidation<float> {};

template <> struct ExpectedBuilder<OpType::Frexp, float> {
static std::vector<float> buildExpected(Op<OpType::Frexp, float, 1>,
static std::vector<float> buildExpected(Op<OpType::Frexp, float, 1> &,
const InputSets<float> &Inputs) {
DXASSERT_NOMSG(Inputs.size() == 1);

Expand Down Expand Up @@ -1079,7 +1086,7 @@ OP_3(OpType::Select, StrictValidation, (static_cast<bool>(A) ? B : C));
#define REDUCTION_OP(OP, STDFUNC) \
template <typename T> struct Op<OP, T, 1> : StrictValidation {}; \
template <typename T> struct ExpectedBuilder<OP, T> { \
static std::vector<HLSLBool_t> buildExpected(Op<OP, T, 1>, \
static std::vector<HLSLBool_t> buildExpected(Op<OP, T, 1> &, \
const InputSets<T> &Inputs) { \
const bool Res = STDFUNC(Inputs[0].begin(), Inputs[0].end(), \
[](T A) { return A != static_cast<T>(0); }); \
Expand All @@ -1097,22 +1104,97 @@ REDUCTION_OP(OpType::All_Zero, (std::all_of));

#undef REDUCTION_OP

template <typename T> struct Op<OpType::Dot, T, 2> : DefaultValidation<T> {};
template <typename T> struct Op<OpType::Dot, T, 2> : StrictValidation {};
template <typename T> struct ExpectedBuilder<OpType::Dot, T> {
static std::vector<T> buildExpected(Op<OpType::Dot, T, 2>,
// For Dot, buildExpected is a special case: it also computes an absolute
// epsilon for validation because Dot is a compound operation. Expected value
// is computed by multiplying and accumulating in fp64 for higher precision.
// Absolute epsilon is computed by reordering the accumulation into a
// worst-case sequence, then summing the per-step epsilons to produce a
// conservative error tolerance for the entire Dot operation.
static std::vector<T> buildExpected(Op<OpType::Dot, T, 2> &Op,
const InputSets<T> &Inputs) {
T DotProduct = T();

for (size_t I = 0; I < Inputs[0].size(); ++I) {
DotProduct += Inputs[0][I] * Inputs[1][I];
std::vector<double> PositiveProducts;
std::vector<double> NegativeProducts;

const size_t VectorSize = Inputs[0].size();

// Floating point ops have a tolerance of 0.5 ULPs per operation as per the
// DX spec.
const double ULPTolerance = 0.5;

// Accumulate in fp64 to improve precision.
double DotProduct = 0.0; // computed reference result
double AbsoluteEpsilon = 0.0; // computed tolerance
for (size_t I = 0; I < VectorSize; ++I) {
double Product = Inputs[0][I] * Inputs[1][I];
AbsoluteEpsilon += computeAbsoluteEpsilon<T>(Product, ULPTolerance);

DotProduct += Product;

if (Product >= 0.0)
PositiveProducts.push_back(Product);
else
NegativeProducts.push_back(Product);
}

// Sort each by magnitude so that we can accumulate them in worst case
// order.
std::sort(PositiveProducts.begin(), PositiveProducts.end(),
std::greater<double>());
std::sort(NegativeProducts.begin(), NegativeProducts.end());

// Helper to sum the products and compute/add to the running absolute
// epsilon total.
auto SumProducts = [&AbsoluteEpsilon,
ULPTolerance](const std::vector<double> &Values) {
double Sum = Values.empty() ? 0.0 : Values[0];
for (size_t I = 1; I < Values.size(); ++I) {
Sum += Values[I];
AbsoluteEpsilon += computeAbsoluteEpsilon<T>(Sum, ULPTolerance);
}
return Sum;
};

// Accumulate products in the worst case order while computing the absolute
// epsilon error for each intermediate step. And accumulate that error.
const double SumPos = SumProducts(PositiveProducts);
const double SumNeg = SumProducts(NegativeProducts);

if (!PositiveProducts.empty() && !NegativeProducts.empty())
AbsoluteEpsilon +=
computeAbsoluteEpsilon<T>((SumPos + SumNeg), ULPTolerance);

Op.ValidationConfig = ValidationConfig::Epsilon(AbsoluteEpsilon);

std::vector<T> Expected;
Expected.push_back(DotProduct);
Expected.push_back(static_cast<T>(DotProduct));
return Expected;
}
};

template <typename T>
static double computeAbsoluteEpsilon(double A, double ULPTolerance) {
DXASSERT((!isinf(A) && !isnan(A)),
"Input values should not produce inf or nan results");

// ULP is a positive value by definition. So, working with abs(A) simplifies
// our logic for computing ULP in the first place.
A = std::abs(A);

double ULP = 0.0;

if constexpr (std::is_same_v<T, HLSLHalf_t>)
ULP = HLSLHalf_t::GetULP(A);
else
ULP =
std::nextafter(static_cast<T>(A), std::numeric_limits<T>::infinity()) -
static_cast<T>(A);

return ULP * ULPTolerance;
}

template <typename T>
struct Op<OpType::ShuffleVector, T, 1> : DefaultValidation<T> {};
template <typename T> struct ExpectedBuilder<OpType::ShuffleVector, T> {
Expand Down