Skip to content

Commit 198e13e

Browse files
authored
Implement ULP Comparison for doubles (#203)
* compare ulp for doubles * clang format * capitalize Fn + add L to tests * forgot requires double * respond to pr comments
1 parent 3b9aee2 commit 198e13e

File tree

4 files changed

+336
-20
lines changed

4 files changed

+336
-20
lines changed

lib/Support/Check.cpp

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,35 @@
1414
#include "llvm/Support/raw_ostream.h"
1515
#include <cmath>
1616

17-
static bool isDenorm(float F) { return std::fpclassify(F) == FP_SUBNORMAL; }
17+
// limited to float, double, and long double
18+
template <typename T> static bool isDenorm(T F) {
19+
return std::fpclassify(F) == FP_SUBNORMAL;
20+
}
1821

1922
static bool isFloat16NAN(uint16_t Val) {
2023
return (Val & 0x7c00) == 0x7c00 && (Val & 0x03ff) != 0;
2124
}
2225

26+
static bool compareDoubleULP(const double &FSrc, const double &FRef,
27+
unsigned ULPTolerance,
28+
offloadtest::DenormMode DM) {
29+
if (FSrc == FRef)
30+
return true;
31+
if (std::isnan(FSrc) || std::isnan(FRef))
32+
return std::isnan(FRef) && std::isnan(FSrc);
33+
if (DM == offloadtest::DenormMode::Any) {
34+
// If denorm expected, output can be sign preserved zero. Otherwise output
35+
// should pass the regular ulp testing.
36+
if (isDenorm(FRef) && FSrc == 0 && std::signbit(FSrc) == std::signbit(FRef))
37+
return true;
38+
}
39+
// For FTZ or Preserve mode, we should get the expected number within
40+
// ULPTolerance for any operations.
41+
const int64_t Diff = *((const uint64_t *)&FSrc) - *((const uint64_t *)&FRef);
42+
const uint64_t AbsDiff = Diff < 0 ? -Diff : Diff;
43+
return AbsDiff <= ULPTolerance;
44+
}
45+
2346
static bool compareFloatULP(const float &FSrc, const float &FRef,
2447
unsigned ULPTolerance, offloadtest::DenormMode DM) {
2548
if (FSrc == FRef)
@@ -61,40 +84,54 @@ static bool testBufferExact(offloadtest::Buffer *B1, offloadtest::Buffer *B2) {
6184
return true;
6285
}
6386

87+
template <typename T>
88+
static bool testAll(std::function<bool(const T &, const T &)> ComparisonFn,
89+
llvm::ArrayRef<T> Arr1, llvm::ArrayRef<T> Arr2) {
90+
if (Arr1.size() != Arr2.size())
91+
return false;
92+
93+
for (size_t I = 0, E = Arr1.size(); I < E; ++I) {
94+
if (!ComparisonFn(Arr1[I], Arr2[I]))
95+
return false;
96+
}
97+
return true;
98+
}
99+
64100
static bool testBufferFuzzy(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
65101
unsigned ULPT, offloadtest::DenormMode DM) {
102+
assert(B1->Format == B2->Format && "Buffer types must be the same");
66103
switch (B1->Format) {
104+
case offloadtest::DataFormat::Float64: {
105+
const llvm::ArrayRef<double> Arr1(
106+
reinterpret_cast<double *>(B1->Data.get()), B1->Size / sizeof(double));
107+
const llvm::ArrayRef<double> Arr2(
108+
reinterpret_cast<double *>(B2->Data.get()), B2->Size / sizeof(double));
109+
auto Fn = [ULPT, DM](const double &FS, const double &FR) {
110+
return compareDoubleULP(FS, FR, ULPT, DM);
111+
};
112+
return testAll<double>(Fn, Arr1, Arr2);
113+
}
67114
case offloadtest::DataFormat::Float32: {
68-
if (B1->Size != B2->Size)
69-
return false;
70115
const llvm::ArrayRef<float> Arr1(reinterpret_cast<float *>(B1->Data.get()),
71116
B1->Size / sizeof(float));
72-
assert(B2->Format == offloadtest::DataFormat::Float32 &&
73-
"Buffer types must be the same");
74117
const llvm::ArrayRef<float> Arr2(reinterpret_cast<float *>(B2->Data.get()),
75118
B2->Size / sizeof(float));
76-
for (unsigned I = 0, E = Arr1.size(); I < E; ++I) {
77-
if (!compareFloatULP(Arr1[I], Arr2[I], ULPT, DM))
78-
return false;
79-
}
80-
return true;
119+
auto Fn = [ULPT, DM](const float &FS, const float &FR) {
120+
return compareFloatULP(FS, FR, ULPT, DM);
121+
};
122+
return testAll<float>(Fn, Arr1, Arr2);
81123
}
82124
case offloadtest::DataFormat::Float16: {
83-
if (B1->Size != B2->Size)
84-
return false;
85125
const llvm::ArrayRef<uint16_t> Arr1(
86126
reinterpret_cast<uint16_t *>(B1->Data.get()),
87127
B1->Size / sizeof(uint16_t));
88-
assert(B2->Format == offloadtest::DataFormat::Float16 &&
89-
"Buffer types must be the same");
90128
const llvm::ArrayRef<uint16_t> Arr2(
91129
reinterpret_cast<uint16_t *>(B2->Data.get()),
92130
B2->Size / sizeof(uint16_t));
93-
for (unsigned I = 0, E = Arr1.size(); I < E; ++I) {
94-
if (!compareFloat16ULP(Arr1[I], Arr2[I], ULPT))
95-
return false;
96-
}
97-
return true;
131+
auto Fn = [ULPT](const uint16_t &FS, const uint16_t &FR) {
132+
return compareFloat16ULP(FS, FR, ULPT);
133+
};
134+
return testAll<uint16_t>(Fn, Arr1, Arr2);
98135
}
99136
default:
100137
llvm_unreachable("Only float types are supported by the fuzzy test.");

lib/Support/Pipeline.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
using namespace offloadtest;
1515

1616
static bool isFloatingPointFormat(DataFormat Format) {
17-
return Format == DataFormat::Float16 || Format == DataFormat::Float32;
17+
return Format == DataFormat::Float16 || Format == DataFormat::Float32 ||
18+
Format == DataFormat::Float64;
1819
}
1920

2021
namespace llvm {
@@ -49,6 +50,8 @@ void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
4950
if (!isFloatingPointFormat(R.ActualPtr->Format) ||
5051
!isFloatingPointFormat(R.ExpectedPtr->Format))
5152
I.setError(Twine("BufferFuzzy only accepts Float buffers"));
53+
if (R.ActualPtr->Format != R.ExpectedPtr->Format)
54+
I.setError(Twine("Buffers must have the same type"));
5255
}
5356
}
5457

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#--- source.hlsl
2+
3+
RWStructuredBuffer<double> Out1 : register(u0);
4+
RWStructuredBuffer<double> Out2 : register(u1);
5+
RWStructuredBuffer<double> Out3 : register(u2);
6+
RWStructuredBuffer<double> Out4 : register(u3);
7+
8+
[numthreads(1,1,1)]
9+
void main() {
10+
Out1[0] = 0.0;
11+
Out2[0] = 3.14159265L; // Need L or clang will resolve this to a float and test will fail
12+
Out3[0] = asdouble(0x00000000, 0x7FF80000); // Should be NaN
13+
Out4[0] = 5.4L; // Need L or clang will resolve this to a float and test will fail
14+
}
15+
16+
//--- pipeline.yaml
17+
18+
---
19+
Shaders:
20+
- Stage: Compute
21+
Entry: main
22+
DispatchSize: [1, 1, 1]
23+
Buffers:
24+
- Name: Out1
25+
Format: Float64
26+
Stride: 8
27+
ZeroInitSize: 8
28+
- Name: Expected1
29+
Format: Float64
30+
Stride: 8
31+
Data: [ 0x0.fffffffffffffp-1022 ] # isDenorm will return true for this value: 2.22507385850720104E-308
32+
- Name: Out2
33+
Format: Float64
34+
Stride: 8
35+
ZeroInitSize: 8
36+
- Name: Expected2
37+
Format: Float64
38+
Stride: 8
39+
Data: [ 3.14159265 ]
40+
- Name: Out3
41+
Format: Float64
42+
Stride: 8
43+
ZeroInitSize: 8
44+
- Name: Expected3
45+
Format: Float64
46+
Stride: 8
47+
Data: [ NaN ]
48+
- Name: Out4
49+
Format: Float64
50+
Stride: 8
51+
ZeroInitSize: 8
52+
- Name: Expected4
53+
Format: Float64
54+
Stride: 8
55+
Data: [ 5.399999999999999 ] # Should be 2 ulp away
56+
Results:
57+
- Result: Test1 # Testing Expected is Denorm and Out is zero, and both have same sign bit
58+
Rule: BufferFuzzy
59+
ULPT: 1
60+
DenormMode: Any
61+
Actual: Out1
62+
Expected: Expected1
63+
- Result: Test2 # Test two values are exactly the same
64+
Rule: BufferFuzzy
65+
ULPT: 0 # ulp shouldn't matter for this test
66+
Actual: Out2
67+
Expected: Expected2
68+
- Result: Test3 # Test both are NaN
69+
Rule: BufferFuzzy
70+
ULPT: 0 # ulp shouldn't matter for this test
71+
Actual: Out3
72+
Expected: Expected3
73+
- Result: Test4 # Actual and Expected are not the same but they are within the ULPT
74+
Rule: BufferFuzzy
75+
ULPT: 2
76+
Actual: Out4
77+
Expected: Expected4
78+
DescriptorSets:
79+
- Resources:
80+
- Name: Out1
81+
Kind: RWStructuredBuffer
82+
DirectXBinding:
83+
Register: 0
84+
Space: 0
85+
VulkanBinding:
86+
Binding: 0
87+
- Name: Out2
88+
Kind: RWStructuredBuffer
89+
DirectXBinding:
90+
Register: 1
91+
Space: 0
92+
VulkanBinding:
93+
Binding: 1
94+
- Name: Out3
95+
Kind: RWStructuredBuffer
96+
DirectXBinding:
97+
Register: 2
98+
Space: 0
99+
VulkanBinding:
100+
Binding: 2
101+
- Name: Out4
102+
Kind: RWStructuredBuffer
103+
DirectXBinding:
104+
Register: 3
105+
Space: 0
106+
VulkanBinding:
107+
Binding: 3
108+
...
109+
#--- end
110+
111+
# REQUIRES: Double
112+
# RUN: split-file %s %t
113+
# RUN: %dxc_target -T cs_6_5 -Fo %t.o %t/source.hlsl
114+
# RUN: %offloader %t/pipeline.yaml %t.o

0 commit comments

Comments
 (0)