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: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Reactor/Reactor.cpp b/src/Reactor/Reactor.cpp
index 31ab59e..3d57b17 100644
--- a/src/Reactor/Reactor.cpp
+++ b/src/Reactor/Reactor.cpp
@@ -4345,6 +4345,7 @@
 	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); }
diff --git a/src/Reactor/ReactorUnitTests.cpp b/src/Reactor/ReactorUnitTests.cpp
index dcfe961..06dcf20 100644
--- a/src/Reactor/ReactorUnitTests.cpp
+++ b/src/Reactor/ReactorUnitTests.cpp
@@ -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 @@
 			}
 
 			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 @@
 	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;
diff --git a/src/Reactor/Traits.hpp b/src/Reactor/Traits.hpp
index ccef628..d26abc7 100644
--- a/src/Reactor/Traits.hpp
+++ b/src/Reactor/Traits.hpp
@@ -38,6 +38,7 @@
 	class Long;
 	class Half;
 	class Float;
+	class Float4;
 
 	template<class T> class Pointer;
 	template<class T> class LValue;
@@ -83,6 +84,7 @@
 	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); */ };