Skip to content

Commit 7ed2d26

Browse files
Execution Tests: Long Vector tests add Special Math Ops (microsoft#7737)
This PR adds the Ldexp and Frexp tests. Resolves microsoft#7465 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent ea90fd3 commit 7ed2d26

File tree

4 files changed

+165
-21
lines changed

4 files changed

+165
-21
lines changed

tools/clang/unittests/HLSLExec/LongVectorOpTable.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,15 @@
625625
<Parameter Name="OpTypeEnum">BinaryMathOpType_Max</Parameter>
626626
<Parameter Name="DataType">float16</Parameter>
627627
</Row>
628+
<Row Name="Ldexp_float16">
629+
<Parameter Name="OpTypeEnum">BinaryMathOpType_Ldexp</Parameter>
630+
<Parameter Name="DataType">float16</Parameter>
631+
</Row>
632+
<Row Name="ScalarLdexp_float16">
633+
<Parameter Name="OpTypeEnum">BinaryMathOpType_Ldexp</Parameter>
634+
<Parameter Name="DataType">float16</Parameter>
635+
<Parameter Name="ScalarInputFlags">0x2</Parameter>
636+
</Row>
628637
<!-- LongVectorBinaryMathOpTypeTable DataType: float32 -->
629638
<Row Name="ScalarAdd_float32">
630639
<Parameter Name="OpTypeEnum">BinaryMathOpType_Add</Parameter>
@@ -689,6 +698,15 @@
689698
<Parameter Name="OpTypeEnum">BinaryMathOpType_Max</Parameter>
690699
<Parameter Name="DataType">float32</Parameter>
691700
</Row>
701+
<Row Name="Ldexp_float32">
702+
<Parameter Name="OpTypeEnum">BinaryMathOpType_Ldexp</Parameter>
703+
<Parameter Name="DataType">float32</Parameter>
704+
</Row>
705+
<Row Name="ScalarLdexp_float32">
706+
<Parameter Name="OpTypeEnum">BinaryMathOpType_Ldexp</Parameter>
707+
<Parameter Name="DataType">float32</Parameter>
708+
<Parameter Name="ScalarInputFlags">0x2</Parameter>
709+
</Row>
692710
<!-- LongVectorBinaryMathOpTypeTable DataType: float64 -->
693711
<Row Name="ScalarAdd_float64">
694712
<Parameter Name="OpTypeEnum">BinaryMathOpType_Add</Parameter>
@@ -1181,6 +1199,10 @@
11811199
<Parameter Name="OpTypeEnum">UnaryMathOpType_Log2</Parameter>
11821200
<Parameter Name="DataType">float32</Parameter>
11831201
</Row>
1202+
<Row Name="Frexp_float32">
1203+
<Parameter Name="OpTypeEnum">UnaryMathOpType_Frexp</Parameter>
1204+
<Parameter Name="DataType">float32</Parameter>
1205+
</Row>
11841206
<!--UnaryMathOpTable float64-->
11851207
<Row Name="Abs_float64">
11861208
<Parameter Name="OpTypeEnum">UnaryMathOpType_Abs</Parameter>

tools/clang/unittests/HLSLExec/LongVectors.cpp

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,12 @@ bool doVectorsMatch(const std::vector<T> &ActualValues,
162162
template <typename T, typename ComputeFnT>
163163
VariantVector generateExpectedVector(size_t Count, ComputeFnT ComputeFn) {
164164

165-
VariantVector ExpectedVector = std::vector<T>{};
166-
auto *TypedExpectedValues = std::get_if<std::vector<T>>(&ExpectedVector);
167-
168-
// A TestConfig may be reused for a different vector length. So this is a
169-
// good time to make sure we clear the expected vector.
170-
TypedExpectedValues->clear();
165+
std::vector<T> Values;
171166

172167
for (size_t Index = 0; Index < Count; ++Index)
173-
TypedExpectedValues->push_back(ComputeFn(Index));
168+
Values.push_back(ComputeFn(Index));
174169

175-
return ExpectedVector;
170+
return Values;
176171
}
177172

178173
template <typename T>
@@ -962,7 +957,7 @@ void AsTypeOpTestConfig<T>::computeExpectedValues(const TestInputs<T> &Inputs) {
962957

963958
template <typename T>
964959
void AsTypeOpTestConfig<T>::computeExpectedValues_SplitDouble(
965-
const std::vector<T> &InputVector1) {
960+
const std::vector<T> &InputVector) {
966961

967962
DXASSERT_NOMSG(OpType == AsTypeOpType_AsUint_SplitDouble);
968963

@@ -971,17 +966,19 @@ void AsTypeOpTestConfig<T>::computeExpectedValues_SplitDouble(
971966
// half with the high bits of each input double. Doing things this way
972967
// helps keep the rest of the generic logic in the LongVector test code
973968
// simple.
974-
ExpectedVector = std::vector<uint32_t>{};
975-
auto *TypedExpectedValues =
976-
std::get_if<std::vector<uint32_t>>(&ExpectedVector);
977-
TypedExpectedValues->resize(InputVector1.size() * 2);
969+
std::vector<uint32_t> Values;
970+
Values.resize(InputVector.size() * 2);
971+
978972
uint32_t LowBits, HighBits;
979-
const size_t InputSize = InputVector1.size();
973+
const size_t InputSize = InputVector.size();
974+
980975
for (size_t Index = 0; Index < InputSize; ++Index) {
981-
splitDouble(InputVector1[Index], LowBits, HighBits);
982-
(*TypedExpectedValues)[Index] = LowBits;
983-
(*TypedExpectedValues)[Index + InputSize] = HighBits;
976+
splitDouble(InputVector[Index], LowBits, HighBits);
977+
Values[Index] = LowBits;
978+
Values[Index + InputSize] = HighBits;
984979
}
980+
981+
ExpectedVector = std::move(Values);
985982
}
986983

987984
template <typename T>
@@ -1088,16 +1085,67 @@ UnaryMathOpTestConfig<T>::UnaryMathOpTestConfig(
10881085
ValidationType = ValidationType_Ulp;
10891086
}
10901087

1091-
if (OpType == UnaryMathOpType_Sign) {
1088+
switch (OpType) {
1089+
case UnaryMathOpType_Sign: {
10921090
// Sign has overridden special logic.
10931091
auto ComputeFunc = [this](const T &A) { return this->sign(A); };
10941092
InitUnaryOpValueComputer<int32_t>(ComputeFunc);
1095-
} else {
1093+
break;
1094+
}
1095+
case UnaryMathOpType_Frexp:
1096+
// Don't initialize a ValueComputer, Frexp has special logic for handling
1097+
// its output
1098+
SpecialDefines = " -DFUNC_FREXP=1";
1099+
break;
1100+
default: {
10961101
auto ComputeFunc = [this](const T &A) {
10971102
return this->computeExpectedValue(A);
10981103
};
10991104
InitUnaryOpValueComputer<T>(ComputeFunc);
11001105
}
1106+
}
1107+
}
1108+
1109+
template <typename T>
1110+
void UnaryMathOpTestConfig<T>::computeExpectedValues(
1111+
const TestInputs<T> &Inputs) {
1112+
1113+
// Base case
1114+
if (ExpectedValueComputer) {
1115+
ExpectedVector = ExpectedValueComputer->computeExpectedValues(Inputs);
1116+
return;
1117+
}
1118+
1119+
computeExpectedValues_Frexp(Inputs.InputVector1);
1120+
}
1121+
1122+
// Frexp has a return value as well as an output paramater. So we handle it
1123+
// with special logic. Frexp is only supported for fp32 values.
1124+
template <typename T>
1125+
void UnaryMathOpTestConfig<T>::computeExpectedValues_Frexp(
1126+
const std::vector<T> &InputVector) {
1127+
1128+
DXASSERT_NOMSG(OpType == UnaryMathOpType_Frexp);
1129+
1130+
std::vector<float> Values;
1131+
1132+
// Expected values size is doubled. In the first half we store the Mantissas
1133+
// and in the second half we store the Exponents. This way we can leverage the
1134+
// existing logic which verify expected values in a single vector. We just
1135+
// need to make sure that we organize the output in the same way in the shader
1136+
// and when we read it back.
1137+
const size_t InputSize = InputVector.size();
1138+
Values.resize(InputSize * 2);
1139+
float Exp = 0;
1140+
float Man = 0;
1141+
1142+
for (size_t Index = 0; Index < InputSize; ++Index) {
1143+
Man = frexp(InputVector[Index], &Exp);
1144+
Values[Index] = Man;
1145+
Values[Index + InputSize] = Exp;
1146+
}
1147+
1148+
ExpectedVector = std::move(Values);
11011149
}
11021150

11031151
template <typename T>
@@ -1206,6 +1254,8 @@ T BinaryMathOpTestConfig<T>::computeExpectedValue(const T &A,
12061254
return (std::min)(A, B);
12071255
case BinaryMathOpType_Max:
12081256
return (std::max)(A, B);
1257+
case BinaryMathOpType_Ldexp:
1258+
return ldexp(A, B);
12091259
default:
12101260
LOG_ERROR_FMT_THROW(L"Unknown BinaryMathOpType: %ls", OpTypeName.c_str());
12111261
return T();

tools/clang/unittests/HLSLExec/LongVectors.h

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ enum UnaryMathOpType {
235235
UnaryMathOpType_Log2,
236236
UnaryMathOpType_Log10,
237237
UnaryMathOpType_Rcp,
238+
UnaryMathOpType_Frexp,
238239
UnaryMathOpType_EnumValueCount
239240
};
240241

@@ -255,6 +256,7 @@ static const OpTypeMetaData<UnaryMathOpType>
255256
{L"UnaryMathOpType_Log2", UnaryMathOpType_Log2, "log2"},
256257
{L"UnaryMathOpType_Log10", UnaryMathOpType_Log10, "log10"},
257258
{L"UnaryMathOpType_Rcp", UnaryMathOpType_Rcp, "rcp"},
259+
{L"UnaryMathOpType_Frexp", UnaryMathOpType_Frexp, "TestFrexp"},
258260
};
259261

260262
static_assert(_countof(unaryMathOpTypeStringToOpMetaData) ==
@@ -276,6 +278,7 @@ enum BinaryMathOpType {
276278
BinaryMathOpType_Modulus,
277279
BinaryMathOpType_Min,
278280
BinaryMathOpType_Max,
281+
BinaryMathOpType_Ldexp,
279282
BinaryMathOpType_EnumValueCount
280283
};
281284

@@ -292,6 +295,7 @@ static const OpTypeMetaData<BinaryMathOpType>
292295
"%"},
293296
{L"BinaryMathOpType_Min", BinaryMathOpType_Min, "min", ","},
294297
{L"BinaryMathOpType_Max", BinaryMathOpType_Max, "max", ","},
298+
{L"BinaryMathOpType_Ldexp", BinaryMathOpType_Ldexp, "ldexp", ","},
295299
};
296300

297301
static_assert(_countof(binaryMathOpTypeStringToOpMetaData) ==
@@ -670,7 +674,7 @@ template <typename T> class AsTypeOpTestConfig : public TestConfig<T> {
670674
void computeExpectedValues(const TestInputs<T> &Inputs) override;
671675

672676
private:
673-
void computeExpectedValues_SplitDouble(const std::vector<T> &InputVector1);
677+
void computeExpectedValues_SplitDouble(const std::vector<T> &InputVector);
674678

675679
template <typename T>
676680
HLSLHalf_t asFloat16([[maybe_unused]] const T &A) const {
@@ -818,9 +822,14 @@ template <typename T> class UnaryMathOpTestConfig : public TestConfig<T> {
818822
public:
819823
UnaryMathOpTestConfig(const OpTypeMetaData<UnaryMathOpType> &OpTypeMd);
820824

825+
// Override the base class method so we can handle frexp as it has
826+
// an out parameter.
827+
void computeExpectedValues(const TestInputs<T> &Inputs) override;
821828
T computeExpectedValue(const T &A) const;
822829

823830
private:
831+
void computeExpectedValues_Frexp(const std::vector<T> &InputVector);
832+
824833
UnaryMathOpType OpType = UnaryMathOpType_EnumValueCount;
825834

826835
// The majority of HLSL intrinsics return a DataType matching the
@@ -842,6 +851,31 @@ template <typename T> class UnaryMathOpTestConfig : public TestConfig<T> {
842851
// lost precision.
843852
return static_cast<T>((std::abs)(A));
844853
}
854+
855+
template <typename T = DataTypeT>
856+
typename std::enable_if<!(std::is_same<T, float>::value), float>::type
857+
frexp([[maybe_unused]] const T &A, [[maybe_unused]] float *Exponent) const {
858+
LOG_ERROR_FMT_THROW(L"Programmer Error: frexp only accepts floats. "
859+
L"Have DataTypeT: %s",
860+
typeid(T).name());
861+
return 0.0f;
862+
}
863+
864+
template <typename T = DataTypeT>
865+
typename std::enable_if<(std::is_same<T, float>::value), T>::type
866+
frexp(const T &A, T *Exponent) const {
867+
int IntExp = 0;
868+
869+
// std::frexp returns a signed mantissa. But the HLSL implmentation returns
870+
// an unsigned mantissa.
871+
T mantissa = std::abs(std::frexp(A, &IntExp));
872+
873+
// std::frexp returns the exponent as an int, but HLSL stores it as a float.
874+
// However, the HLSL exponents fractional component is always 0. So it can
875+
// conversion between float and int is safe.
876+
*Exponent = static_cast<T>(IntExp);
877+
return mantissa;
878+
}
845879
};
846880

847881
template <typename T> class BinaryMathOpTestConfig : public TestConfig<T> {
@@ -855,7 +889,9 @@ template <typename T> class BinaryMathOpTestConfig : public TestConfig<T> {
855889

856890
// Helpers so we do the right thing for float types. HLSLHalf_t is handled in
857891
// an operator overload.
858-
template <typename T> T mod(const T &A, const T &B) const { return A % B; }
892+
template <typename T = DataTypeT> T mod(const T &A, const T &B) const {
893+
return A % B;
894+
}
859895

860896
template <> float mod(const float &A, const float &B) const {
861897
return std::fmod(A, B);
@@ -864,6 +900,25 @@ template <typename T> class BinaryMathOpTestConfig : public TestConfig<T> {
864900
template <> double mod(const double &A, const double &B) const {
865901
return std::fmod(A, B);
866902
}
903+
904+
template <typename T = T>
905+
typename std::enable_if<!(std::is_same<T, float>::value ||
906+
std::is_same<T, HLSLHalf_t>::value),
907+
T>::type
908+
ldexp([[maybe_unused]] const T &A, [[maybe_unused]] const T &Exponent) const {
909+
LOG_ERROR_FMT_THROW(L"Programmer Error: ldexp only accepts floatlikes. "
910+
L"Have T: %s",
911+
typeid(T).name());
912+
return T();
913+
}
914+
915+
template <typename T = T>
916+
typename std::enable_if<(std::is_same<T, float>::value ||
917+
std::is_same<T, HLSLHalf_t>::value),
918+
T>::type
919+
ldexp(const T &A, const T &Exponent) const {
920+
return A * T(std::pow(2.0f, Exponent));
921+
}
867922
};
868923

869924
template <typename T> class TernaryMathOpTestConfig : public TestConfig<T> {

tools/clang/unittests/HLSLExec/ShaderOpArith.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3827,6 +3827,23 @@ void MSMain(uint GID : SV_GroupIndex,
38273827
}
38283828
#endif
38293829
3830+
#ifdef FUNC_FREXP
3831+
vector<OUT_TYPE, NUM> TestFrexp(vector<TYPE, NUM> Vector)
3832+
{
3833+
vector<OUT_TYPE, NUM> Mantissa;
3834+
vector<OUT_TYPE, NUM> Exponent;
3835+
3836+
Mantissa = frexp(Vector, Exponent);
3837+
3838+
// Store the exponent outputs in the second half of the output vector.
3839+
// Exponent values are always floats, so we can use 4 bytes per
3840+
// element for our offset.
3841+
g_OutputVector.Store< vector<OUT_TYPE, NUM> >(4 * NUM, Exponent);
3842+
3843+
return Mantissa;
3844+
}
3845+
#endif
3846+
38303847
[numthreads(1,1,1)]
38313848
void main(uint GI : SV_GroupIndex) {
38323849

0 commit comments

Comments
 (0)