Skip to content

Commit 6a03c3f

Browse files
authored
Enhance offload test suite to compare test results with two new rules 'BufferExact' and 'BufferFuzzy' (#77)
Add support for testing results in the offload test suite. Add two testing functions, 'BufferExact' and 'BufferFuzzy' BufferExact tests if two buffers contain exactly the same content, using == on each element. BufferFuzzy works on only floating point values and performs a ULP comparison on each floating point value in two buffers being compared. The BufferFuzzy rule requires a ULPT field in the YAML which specifies the allowed ULP difference. It also takes an optional DenormMode field, which defaults to Any. Add a test each for BufferExact and BufferFuzzy which show what error output looks like. Closes #76
1 parent 0d417f2 commit 6a03c3f

File tree

13 files changed

+614
-5
lines changed

13 files changed

+614
-5
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,25 @@ Buffers:
5858
- Name: In2
5959
Format: Hex16
6060
Data: [ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]
61+
- Name: Out1 # Buffer where our output will go
62+
Format: Float32
63+
Stride: 4
64+
ZeroInitSize: 8
65+
- Name: Expected1 # Buffer which stores the expected result of our test
66+
Format: Float32
67+
Stride: 4
68+
Data: [ 0.0, 1.0 ]
69+
Results: # Using Result can verify test values without filecheck
70+
- Result: Test1
71+
Rule: BufferFuzzy # Rule which can be used to compare Float Buffers; They are compared within a ULP range
72+
ULPT: 1 # ULP to use
73+
DenormMode: Any # if DenormMode Field is not Specified, 'Any' is the default; FTZ and Preserve are the other options.
74+
Actual: Out1 # First buffer to compare
75+
Expected: Expected1 # Second buffer to compare against first
76+
- Result: Test2
77+
Rule: BufferExact # Compares Two Buffers for == equality between each value elementwise
78+
Actual: Out1
79+
Expected: Expected1
6180
DescriptorSets:
6281
- Resources:
6382
- Name: Constants

include/Support/Check.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===- Check.h - Functions for checking test results-------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
//
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef OFFLOADTEST_SUPPORT_CHECK_H
14+
#define OFFLOADTEST_SUPPORT_CHECK_H
15+
16+
#include "Pipeline.h"
17+
18+
/// verifies an offload test Result
19+
/// Calls the test, corresponding to the Rule specified in the Result,
20+
/// On the Actual and Expected Buffers
21+
/// \param R Result to verify
22+
/// \returns Success if the test passes according to the specified Rule
23+
llvm::Error verifyResult(offloadtest::Result R);
24+
25+
#endif // OFFLOADTEST_SUPPORT_CHECK_H

include/Support/Pipeline.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ namespace offloadtest {
2525

2626
enum class Stages { Compute };
2727

28+
enum class Rule { BufferExact, BufferFuzzy };
29+
30+
enum class DenormMode { Any, FTZ, Preserve };
31+
2832
enum class DataFormat {
2933
Hex8,
3034
Hex16,
@@ -107,6 +111,17 @@ struct Buffer {
107111
}
108112
};
109113

114+
struct Result {
115+
std::string Name;
116+
Rule Rule;
117+
std::string Actual;
118+
std::string Expected;
119+
Buffer *ActualPtr = nullptr;
120+
Buffer *ExpectedPtr = nullptr;
121+
DenormMode DM = DenormMode::Any;
122+
unsigned ULPT; // ULP Tolerance
123+
};
124+
110125
struct Resource {
111126
ResourceKind Kind;
112127
std::string Name;
@@ -212,6 +227,7 @@ struct Pipeline {
212227
llvm::SmallVector<Shader> Shaders;
213228
RuntimeSettings Settings;
214229
llvm::SmallVector<Buffer> Buffers;
230+
llvm::SmallVector<Result> Results;
215231
llvm::SmallVector<DescriptorSet> Sets;
216232

217233
uint32_t getDescriptorCount() const {
@@ -235,6 +251,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Resource)
235251
LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Buffer)
236252
LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Shader)
237253
LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::dx::RootParameter)
254+
LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::Result)
238255

239256
namespace llvm {
240257
namespace yaml {
@@ -251,6 +268,10 @@ template <> struct MappingTraits<offloadtest::Buffer> {
251268
static void mapping(IO &I, offloadtest::Buffer &R);
252269
};
253270

271+
template <> struct MappingTraits<offloadtest::Result> {
272+
static void mapping(IO &I, offloadtest::Result &R);
273+
};
274+
254275
template <> struct MappingTraits<offloadtest::Resource> {
255276
static void mapping(IO &I, offloadtest::Resource &R);
256277
};
@@ -287,6 +308,25 @@ template <> struct MappingTraits<offloadtest::RuntimeSettings> {
287308
static void mapping(IO &I, offloadtest::RuntimeSettings &S);
288309
};
289310

311+
template <> struct ScalarEnumerationTraits<offloadtest::Rule> {
312+
static void enumeration(IO &I, offloadtest::Rule &V) {
313+
#define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::Rule::Val)
314+
ENUM_CASE(BufferExact);
315+
ENUM_CASE(BufferFuzzy);
316+
#undef ENUM_CASE
317+
}
318+
};
319+
320+
template <> struct ScalarEnumerationTraits<offloadtest::DenormMode> {
321+
static void enumeration(IO &I, offloadtest::DenormMode &V) {
322+
#define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::DenormMode::Val)
323+
ENUM_CASE(Any);
324+
ENUM_CASE(FTZ);
325+
ENUM_CASE(Preserve);
326+
#undef ENUM_CASE
327+
}
328+
};
329+
290330
template <> struct ScalarEnumerationTraits<offloadtest::DataFormat> {
291331
static void enumeration(IO &I, offloadtest::DataFormat &V) {
292332
#define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::DataFormat::Val)

lib/Support/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
add_offloadtest_library(Support Pipeline.cpp)
1+
add_offloadtest_library(Support Pipeline.cpp Check.cpp)

lib/Support/Check.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//===- Check.cpp - Check test results ------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#include "Support/Check.h"
13+
#include "llvm/Support/Error.h"
14+
#include "llvm/Support/raw_ostream.h"
15+
16+
static bool isDenorm(float F) { return std::fpclassify(F) == FP_SUBNORMAL; }
17+
18+
static bool compareFloatULP(const float &FSrc, const float &FRef,
19+
unsigned ULPTolerance, offloadtest::DenormMode DM) {
20+
if (FSrc == FRef)
21+
return true;
22+
if (std::isnan(FSrc))
23+
return std::isnan(FRef);
24+
if (DM == offloadtest::DenormMode::Any) {
25+
// If denorm expected, output can be sign preserved zero. Otherwise output
26+
// should pass the regular ulp testing.
27+
if (isDenorm(FRef) && FSrc == 0 && std::signbit(FSrc) == std::signbit(FRef))
28+
return true;
29+
}
30+
// For FTZ or Preserve mode, we should get the expected number within
31+
// ULPTolerance for any operations.
32+
int Diff = *((const uint32_t *)&FSrc) - *((const uint32_t *)&FRef);
33+
unsigned int AbsDiff = Diff < 0 ? -Diff : Diff;
34+
return AbsDiff <= ULPTolerance;
35+
}
36+
37+
static bool testBufferExact(offloadtest::Buffer *B1, offloadtest::Buffer *B2) {
38+
if (B1->size() != B2->size())
39+
return false;
40+
for (uint32_t I = 0; I < B1->size(); ++I) {
41+
if (B1->Data[I] != B2->Data[I])
42+
return false;
43+
}
44+
return true;
45+
}
46+
47+
static bool testBufferFuzzy(offloadtest::Buffer *B1, offloadtest::Buffer *B2,
48+
unsigned ULPT, offloadtest::DenormMode DM) {
49+
switch (B1->Format) {
50+
case offloadtest::DataFormat::Float32: {
51+
if (B1->Size != B2->Size)
52+
return false;
53+
llvm::MutableArrayRef<float> Arr1(reinterpret_cast<float *>(B1->Data.get()),
54+
B1->Size / sizeof(float));
55+
assert(B2->Format == offloadtest::DataFormat::Float32 &&
56+
"Buffer types must be the same");
57+
llvm::MutableArrayRef<float> Arr2(reinterpret_cast<float *>(B2->Data.get()),
58+
B2->Size / sizeof(float));
59+
for (unsigned I = 0; I < Arr1.size(); ++I) {
60+
if (!compareFloatULP(Arr1[I], Arr2[I], ULPT, DM))
61+
return false;
62+
}
63+
return true;
64+
}
65+
default:
66+
llvm_unreachable("Only float types are supported by the fuzzy test.");
67+
}
68+
return false;
69+
}
70+
71+
llvm::Error verifyResult(offloadtest::Result R) {
72+
switch (R.Rule) {
73+
case offloadtest::Rule::BufferExact: {
74+
if (testBufferExact(R.ActualPtr, R.ExpectedPtr))
75+
return llvm::Error::success();
76+
llvm::SmallString<256> Str;
77+
llvm::raw_svector_ostream OS(Str);
78+
OS << "Test failed: " << R.Name << "\nExpected:\n";
79+
llvm::yaml::Output YAMLOS(OS);
80+
YAMLOS << *R.ExpectedPtr;
81+
OS << "Got:\n";
82+
YAMLOS << *R.ActualPtr;
83+
return llvm::createStringError(Str.c_str());
84+
}
85+
case offloadtest::Rule::BufferFuzzy: {
86+
if (testBufferFuzzy(R.ActualPtr, R.ExpectedPtr, R.ULPT, R.DM))
87+
return llvm::Error::success();
88+
llvm::SmallString<256> Str;
89+
llvm::raw_svector_ostream OS(Str);
90+
OS << "Test failed: " << R.Name << "\nExpected:\n";
91+
llvm::yaml::Output YAMLOS(OS);
92+
YAMLOS << *R.ExpectedPtr;
93+
OS << "Got:\n";
94+
YAMLOS << *R.ActualPtr;
95+
return llvm::createStringError(Str.c_str());
96+
}
97+
}
98+
}

lib/Support/Pipeline.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
2222
I.mapOptional("RuntimeSettings", P.Settings);
2323

2424
I.mapRequired("Buffers", P.Buffers);
25+
I.mapOptional("Results", P.Results);
2526
I.mapRequired("DescriptorSets", P.Sets);
2627

2728
if (!I.outputting()) {
@@ -32,6 +33,21 @@ void MappingTraits<offloadtest::Pipeline>::mapping(IO &I,
3233
I.setError(Twine("Referenced buffer ") + R.Name + " not found!");
3334
}
3435
}
36+
// Initialize result Buffers
37+
for (auto &R : P.Results) {
38+
R.ActualPtr = P.getBuffer(R.Actual);
39+
if (!R.ActualPtr)
40+
I.setError(Twine("Reference buffer ") + R.Actual + " not found!");
41+
R.ExpectedPtr = P.getBuffer(R.Expected);
42+
if (!R.ExpectedPtr)
43+
I.setError(Twine("Reference buffer ") + R.Expected + " not found!");
44+
if (R.Rule == offloadtest::Rule::BufferFuzzy) {
45+
if (R.ActualPtr->Format != offloadtest::DataFormat::Float32 ||
46+
R.ExpectedPtr->Format != offloadtest::DataFormat::Float32)
47+
I.setError(Twine("BufferFuzzy only accepts Float buffers"));
48+
}
49+
}
50+
3551
uint32_t DescriptorTableCount = 0;
3652
for (auto &R : P.Settings.DX.RootParams) {
3753
switch (R.Kind) {
@@ -196,5 +212,22 @@ void MappingTraits<offloadtest::Shader>::mapping(IO &I,
196212
I.mapRequired("DispatchSize", MutableDispatchSize);
197213
}
198214
}
215+
void MappingTraits<offloadtest::Result>::mapping(IO &I,
216+
offloadtest::Result &R) {
217+
I.mapRequired("Result", R.Name);
218+
I.mapRequired("Rule", R.Rule);
219+
I.mapRequired("Actual", R.Actual);
220+
I.mapRequired("Expected", R.Expected);
221+
222+
switch (R.Rule) {
223+
case Rule::BufferFuzzy: {
224+
I.mapRequired("ULPT", R.ULPT);
225+
I.mapOptional("DenormMode", R.DM);
226+
break;
227+
}
228+
default:
229+
break;
230+
}
231+
}
199232
} // namespace yaml
200233
} // namespace llvm

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ list(APPEND OFFLOADTEST_DEPS
6060
FileCheck
6161
split-file
6262
imgdiff
63-
OffloadTestUnit)
63+
OffloadTestUnit
64+
not)
6465

6566
if (OFFLOADTEST_TEST_CLANG)
6667
list(APPEND OFFLOADTEST_DEPS clang)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#--- source.hlsl
2+
3+
RWStructuredBuffer<int> Out : register(u0);
4+
5+
[numthreads(1,1,1)]
6+
void main() {
7+
Out[0] = 20;
8+
Out[1] = 30;
9+
Out[2] = 40;
10+
Out[3] = 50;
11+
}
12+
13+
//--- pipeline.yaml
14+
15+
---
16+
Shaders:
17+
- Stage: Compute
18+
Entry: main
19+
DispatchSize: [1, 1, 1]
20+
Buffers:
21+
- Name: Out
22+
Format: Int32
23+
Stride: 4
24+
ZeroInitSize: 16
25+
- Name: Expected1
26+
Format: Int32
27+
Stride: 4
28+
Data: [ 1, 2, 3, 4 ]
29+
Results:
30+
- Result: Test1
31+
Rule: BufferExact
32+
Actual: Out
33+
Expected: Expected1
34+
DescriptorSets:
35+
- Resources:
36+
- Name: Out
37+
Kind: RWStructuredBuffer
38+
DirectXBinding:
39+
Register: 0
40+
Space: 0
41+
VulkanBinding:
42+
Binding: 0
43+
...
44+
#--- end
45+
46+
# UNSUPPORTED: Clang-Vulkan
47+
# RUN: split-file %s %t
48+
# RUN: %dxc_target -T cs_6_5 -Fo %t.o %t/source.hlsl
49+
# RUN: not %offloader %t/pipeline.yaml %t.o 2>&1 | FileCheck %s
50+
51+
# CHECK: Test failed: Test1
52+
# CHECK: Expected:
53+
# CHECK: ---
54+
# CHECK: Name: Expected1
55+
# CHECK: Format: Int32
56+
# CHECK: Stride: 4
57+
# CHECK: Data: [ 1, 2, 3, 4 ]
58+
# CHECK: OutputProps:
59+
# CHECK: Height: 0
60+
# CHECK: Width: 0
61+
# CHECK: Depth: 0
62+
# CHECK: ...
63+
# CHECK: Got:
64+
# CHECK: ---
65+
# CHECK: Name: Out
66+
# CHECK: Format: Int32
67+
# CHECK: Stride: 4
68+
# CHECK: Data: [ 20, 30, 40, 50 ]
69+
# CHECK: OutputProps:
70+
# CHECK: Height: 0
71+
# CHECK: Width: 0
72+
# CHECK: Depth: 0
73+

0 commit comments

Comments
 (0)