Commit bf151b82 by Antonio Maiorano

ReactorUnitTests: add unit tests for intrinsics

Bug: b/130459196 Change-Id: I19ecfaa6350654b0c4808739a4dd680d2e5506f1 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38875 Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Tested-by: 's avatarAntonio Maiorano <amaiorano@google.com> Reviewed-by: 's avatarAlexis Hétu <sugoi@google.com>
parent e6ab4707
......@@ -4345,6 +4345,7 @@ namespace rr
Int CToReactor<int32_t>::cast(int32_t v) { return type(v); }
UInt CToReactor<uint32_t>::cast(uint32_t v) { return type(v); }
Float CToReactor<float>::cast(float v) { return type(v); }
Float4 CToReactor<float[4]>::cast(float v[4]) { return type(v[0], v[1], v[2], v[3]); }
// TODO: Long has no constructor that takes a uint64_t
// Long CToReactor<uint64_t>::cast(uint64_t v) { return type(v); }
......
......@@ -17,10 +17,61 @@
#include "gtest/gtest.h"
#include <cmath>
#include <tuple>
using namespace rr;
constexpr float PI = 3.141592653589793f;
using float4 = float[4];
// Value type wrapper around a float4
struct float4_value
{
float4_value() = default;
explicit float4_value(float rep) : v{ rep, rep, rep, rep } {}
float4_value(float x, float y, float z, float w) : v{ x, y, z, w } {}
bool operator==(const float4_value &rhs) const
{
return std::equal(std::begin(v), std::end(v), rhs.v);
}
// For gtest printing
friend std::ostream& operator<<(std::ostream& os, const float4_value& value) {
return os << "[" << value.v[0] << ", " << value.v[1] << ", " << value.v[2] << ", " << value.v[3] << "]";
}
float4 v;
};
// For gtest printing of pairs
namespace std
{
template <typename T, typename U>
std::ostream& operator<<(std::ostream& os, const std::pair<T, U>& value) {
return os << "{ " << value.first << ", " << value.second << " }";
}
}
// Invoke a void(float4*) routine on &v.v, returning wrapped result in v
template <typename RoutineType>
float4_value invokeRoutine(RoutineType& routine, float4_value v)
{
routine(&v.v);
return v;
}
// Invoke a void(float4*, float4*) routine on &v1.v, &v2.v returning wrapped result in v1
template <typename RoutineType>
float4_value invokeRoutine(RoutineType& routine, float4_value v1, float4_value v2)
{
routine(&v1.v, &v2.v);
return v1;
}
int reference(int *p, int y)
{
int x = p[-1];
......@@ -309,7 +360,7 @@ TEST(ReactorUnitTests, Swizzle)
}
for(int i = 0; i < 256; i++)
{
{
*Pointer<Float4>(out + 16 * (256 + i)) = ShuffleLowHigh(Float4(1.0f, 2.0f, 3.0f, 4.0f), Float4(5.0f, 6.0f, 7.0f, 8.0f), i);
}
......@@ -1732,6 +1783,322 @@ TEST(ReactorUnitTests, Coroutines_Parameters)
EXPECT_EQ(out, 99);
}
template <typename TestFuncType, typename RefFuncType, typename TestValueType>
struct IntrinsicTestParams
{
std::function<TestFuncType> testFunc; // Function we're testing (Reactor)
std::function<RefFuncType> refFunc; // Reference function to test against (C)
std::vector<TestValueType> testValues; // Values to input to functions
};
using IntrinsicTestParams_Float = IntrinsicTestParams<RValue<Float>(RValue<Float>), float(float), float>;
using IntrinsicTestParams_Float4 = IntrinsicTestParams<RValue<Float4>(RValue<Float4>), float(float), float>;
using IntrinsicTestParams_Float4_Float4 = IntrinsicTestParams<RValue<Float4>(RValue<Float4>, RValue<Float4>), float(float, float), std::pair<float, float>>;
struct IntrinsicTest_Float : public testing::TestWithParam<IntrinsicTestParams_Float>
{
void test()
{
FunctionT<float(float)> function;
{
Return(GetParam().testFunc((Float(function.Arg<0>()))));
}
auto routine = function("one");
for (auto&& v : GetParam().testValues)
{
SCOPED_TRACE(v);
EXPECT_FLOAT_EQ(routine(v), GetParam().refFunc(v));
}
}
};
struct IntrinsicTest_Float4 : public testing::TestWithParam<IntrinsicTestParams_Float4>
{
void test()
{
FunctionT<void(float4*)> function;
{
Pointer<Float4> a = function.Arg<0>();
*a = GetParam().testFunc(*a);
Return();
}
auto routine = function("one");
for (auto&& v : GetParam().testValues)
{
SCOPED_TRACE(v);
float4_value result = invokeRoutine(routine, float4_value{ v });
float4_value expected = float4_value{ GetParam().refFunc(v) };
EXPECT_FLOAT_EQ(result.v[0], expected.v[0]);
EXPECT_FLOAT_EQ(result.v[1], expected.v[1]);
EXPECT_FLOAT_EQ(result.v[2], expected.v[2]);
EXPECT_FLOAT_EQ(result.v[3], expected.v[3]);
}
}
};
struct IntrinsicTest_Float4_Float4 : public testing::TestWithParam<IntrinsicTestParams_Float4_Float4>
{
void test()
{
FunctionT<void(float4*, float4*)> function;
{
Pointer<Float4> a = function.Arg<0>();
Pointer<Float4> b = function.Arg<1>();
*a = GetParam().testFunc(*a, *b);
Return();
}
auto routine = function("one");
for (auto&& v : GetParam().testValues)
{
SCOPED_TRACE(v);
float4_value result = invokeRoutine(routine, float4_value{ v.first }, float4_value{ v.second });
float4_value expected = float4_value{ GetParam().refFunc(v.first, v.second) };
EXPECT_FLOAT_EQ(result.v[0], expected.v[0]);
EXPECT_FLOAT_EQ(result.v[1], expected.v[1]);
EXPECT_FLOAT_EQ(result.v[2], expected.v[2]);
EXPECT_FLOAT_EQ(result.v[3], expected.v[3]);
}
}
};
INSTANTIATE_TEST_SUITE_P(IntrinsicTestParams_Float, IntrinsicTest_Float, testing::Values(
IntrinsicTestParams_Float{ [](Float v) { return rr::Exp2(v); }, exp2f, {0.f, 1.f, 12345.f} },
IntrinsicTestParams_Float{ [](Float v) { return rr::Log2(v); }, log2f, {0.f, 1.f, 12345.f} },
IntrinsicTestParams_Float{ [](Float v) { return rr::Sqrt(v); }, sqrtf, {0.f, 1.f, 12345.f} }
));
INSTANTIATE_TEST_SUITE_P(IntrinsicTestParams_Float4, IntrinsicTest_Float4, testing::Values(
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Sin(v); }, sinf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Cos(v); }, cosf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Tan(v); }, tanf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Asin(v); }, asinf, {0.f, 1.f, -1.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Acos(v); }, acosf, {0.f, 1.f, -1.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Atan(v); }, atanf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Sinh(v); }, sinhf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Cosh(v); }, coshf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Tanh(v); }, tanhf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Asinh(v); }, asinhf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Acosh(v); }, acoshf, { 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Atanh(v); }, atanhf, {0.f, 1.f, -1.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Exp(v); }, expf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Log(v); }, logf, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Exp2(v); }, exp2f, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Log2(v); }, log2f, {0.f, 1.f, PI, 12345.f} },
IntrinsicTestParams_Float4{ [](RValue<Float4> v) { return rr::Sqrt(v); }, sqrtf, {0.f, 1.f, PI, 12345.f} }
));
INSTANTIATE_TEST_SUITE_P(IntrinsicTestParams_Float4_Float4, IntrinsicTest_Float4_Float4, testing::Values(
IntrinsicTestParams_Float4_Float4{ [](RValue<Float4> v1, RValue<Float4> v2) { return Atan2(v1, v2); }, atan2f, { {0.f, 0.f}, {0.f, -1.f}, {-1.f, 0.f}, {12345.f, 12345.f} } },
IntrinsicTestParams_Float4_Float4{ [](RValue<Float4> v1, RValue<Float4> v2) { return Pow(v1, v2); }, powf, { {0.f, 0.f}, {0.f, -1.f}, {-1.f, 0.f}, {12345.f, 12345.f} } }
));
TEST_P(IntrinsicTest_Float, Test) { test(); }
TEST_P(IntrinsicTest_Float4, Test) { test(); }
TEST_P(IntrinsicTest_Float4_Float4, Test) { test(); }
TEST(ReactorUnitTests, Intrinsics_Ctlz)
{
// ctlz: counts number of leading zeros
{
Function<UInt(UInt x)> function;
{
UInt x = function.Arg<0>();
Return(rr::Ctlz(x, false));
}
auto routine = function("one");
auto callable = (uint32_t(*)(uint32_t))routine->getEntry();
for (uint32_t i = 0; i < 31; ++i) {
uint32_t result = callable(1 << i);
EXPECT_EQ(result, 31 - i);
}
// Input 0 should return 32 for isZeroUndef == false
{
uint32_t result = callable(0);
EXPECT_EQ(result, 32u);
}
}
{
Function<Void(Pointer<UInt4>, UInt x)> function;
{
Pointer<UInt4> out = function.Arg<0>();
UInt x = function.Arg<1>();
*out = rr::Ctlz(UInt4(x), false);
}
auto routine = function("one");
auto callable = (void(*)(uint32_t*, uint32_t))routine->getEntry();
uint32_t x[4];
for (uint32_t i = 0; i < 31; ++i) {
callable(x, 1 << i);
EXPECT_EQ(x[0], 31 - i);
EXPECT_EQ(x[1], 31 - i);
EXPECT_EQ(x[2], 31 - i);
EXPECT_EQ(x[3], 31 - i);
}
// Input 0 should return 32 for isZeroUndef == false
{
callable(x, 0);
EXPECT_EQ(x[0], 32u);
EXPECT_EQ(x[1], 32u);
EXPECT_EQ(x[2], 32u);
EXPECT_EQ(x[3], 32u);
}
}
}
TEST(ReactorUnitTests, Intrinsics_Cttz)
{
// cttz: counts number of trailing zeros
{
Function<UInt(UInt x)> function;
{
UInt x = function.Arg<0>();
Return(rr::Cttz(x, false));
}
auto routine = function("one");
auto callable = (uint32_t(*)(uint32_t))routine->getEntry();
for (uint32_t i = 0; i < 31; ++i) {
uint32_t result = callable(1 << i);
EXPECT_EQ(result, i);
}
// Input 0 should return 32 for isZeroUndef == false
{
uint32_t result = callable(0);
EXPECT_EQ(result, 32u);
}
}
{
Function<Void(Pointer<UInt4>, UInt x)> function;
{
Pointer<UInt4> out = function.Arg<0>();
UInt x = function.Arg<1>();
*out = rr::Cttz(UInt4(x), false);
}
auto routine = function("one");
auto callable = (void(*)(uint32_t*, uint32_t))routine->getEntry();
uint32_t x[4];
for (uint32_t i = 0; i < 31; ++i) {
callable(x, 1 << i);
EXPECT_EQ(x[0], i);
EXPECT_EQ(x[1], i);
EXPECT_EQ(x[2], i);
EXPECT_EQ(x[3], i);
}
// Input 0 should return 32 for isZeroUndef == false
{
callable(x, 0);
EXPECT_EQ(x[0], 32u);
EXPECT_EQ(x[1], 32u);
EXPECT_EQ(x[2], 32u);
EXPECT_EQ(x[3], 32u);
}
}
}
TEST(ReactorUnitTests, Intrinsics_Scatter)
{
Function<Void(Pointer<Float> base, Pointer<Float4> val, Pointer<Int4> offsets)> function;
{
Pointer<Float> base = function.Arg<0>();
Pointer<Float4> val = function.Arg<1>();
Pointer<Int4> offsets = function.Arg<2>();
auto mask = Int4(~0, ~0, ~0, ~0);
unsigned int alignment = 1;
Scatter(base, *val, *offsets, mask, alignment);
}
float buffer[16] = {0};
constexpr auto elemSize = sizeof(buffer[0]);
int offsets[] =
{
1 *elemSize,
6 *elemSize,
11 *elemSize,
13 *elemSize
};
float val[4] = {10, 60, 110, 130};
auto routine = function("one");
auto entry = (void(*)(float*, float*, int*))routine->getEntry();
entry(buffer, val, offsets);
EXPECT_EQ(buffer[offsets[0] / sizeof(buffer[0])], 10);
EXPECT_EQ(buffer[offsets[1] / sizeof(buffer[0])], 60);
EXPECT_EQ(buffer[offsets[2] / sizeof(buffer[0])], 110);
EXPECT_EQ(buffer[offsets[3] / sizeof(buffer[0])], 130);
}
TEST(ReactorUnitTests, Intrinsics_Gather)
{
Function<Void(Pointer<Float> base, Pointer<Int4> offsets, Pointer<Float4> result)> function;
{
Pointer<Float> base = function.Arg<0>();
Pointer<Int4> offsets = function.Arg<1>();
Pointer<Float4> result = function.Arg<2>();
auto mask = Int4(~0, ~0, ~0, ~0);
unsigned int alignment = 1;
bool zeroMaskedLanes = true;
*result = Gather(base, *offsets, mask, alignment, zeroMaskedLanes);
}
float buffer[] =
{
0, 10, 20, 30,
40, 50, 60, 70,
80, 90, 100, 110,
120, 130, 140, 150
};
constexpr auto elemSize = sizeof(buffer[0]);
int offsets[] =
{
1 *elemSize,
6 *elemSize,
11 *elemSize,
13 *elemSize
};
auto routine = function("one");
auto entry = (void(*)(float*, int*, float*))routine->getEntry();
float result[4] = {};
entry(buffer, offsets, result);
EXPECT_EQ(result[0], 10);
EXPECT_EQ(result[1], 60);
EXPECT_EQ(result[2], 110);
EXPECT_EQ(result[3], 130);
}
TEST(ReactorUnitTests, ExtractFromRValue)
{
Function<Void(Pointer<Int4> values, Pointer<Int4> result)> function;
......
......@@ -38,6 +38,7 @@ namespace rr
class Long;
class Half;
class Float;
class Float4;
template<class T> class Pointer;
template<class T> class LValue;
......@@ -83,6 +84,7 @@ namespace rr
template<> struct CToReactor<int32_t> { using type = Int; static Int cast(int32_t); };
template<> struct CToReactor<uint32_t> { using type = UInt; static UInt cast(uint32_t); };
template<> struct CToReactor<float> { using type = Float; static Float cast(float); };
template<> struct CToReactor<float[4]> { using type = Float4; static Float4 cast(float[4]); };
// TODO: Long has no constructor that takes a uint64_t
template<> struct CToReactor<uint64_t> { using type = Long; /* static Long cast(uint64_t); */ };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment