compiler: Support the majority of the remaining VU VF instructions (#258)

* compiler: Support the majority of the remaining VU VF instructions

- VWAIT
- VMADD variants
- VMSUB variants
- VSQRT
- VDIV
- outer product (VOPMULA + VOPMSUB)

* compiler: Fix some bugs / optimize some instructions

* tests/compiler: Add test coverage for new instructions

* docs: Add documentation for new inline assembly functions

* lint: Formatting / fix failing test

* Remove my comment about ftf/fsf encoding, it's been fixed

* address review feedback

* correct VSQRTPS implementation
This commit is contained in:
Tyler Wilding
2021-02-16 18:41:33 -08:00
committed by GitHub
parent f1a93886e7
commit cdce4d9612
15 changed files with 1546 additions and 514 deletions
@@ -0,0 +1,21 @@
(defun test-vector-math ()
(let ((vector-in-1 (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 {{ v1x }} {{ v1y }} {{ v1z }} {{ v1w }})
(set-vector! vector-out {{ destx }} {{ desty }} {{ destz }} {{ destw }})
(rlet ((vf1 :class vf :reset-here #t)
(vf2 :class vf :reset-here #t))
(.lvf vf1 vector-in-1)
(.lvf vf2 vector-out)
({{ operation }} vf2 vf1{% if destinationMask %} :mask #b{{ destinationMask }}{% endif %})
(.wait.vf)
(.svf vector-out vf2))
(format #t "(~f, ~f, ~f, ~f)~%" (-> vector-out x) (-> vector-out y) (-> vector-out z) (-> vector-out w))))
(test-vector-math)
@@ -0,0 +1,29 @@
(defun test-vector-outer-product ()
(let ((vector-in-1 (new 'stack 'vector))
(vector-in-2 (new 'stack 'vector))
(vector-acc (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 {{ v1x }} {{ v1y }} {{ v1z }} {{ v1w }})
(set-vector! vector-in-2 {{ v2x }} {{ v2y }} {{ v2z }} {{ v2w }})
(set-vector! vector-acc {{ accx }} {{ accy }} {{ accz }} {{ accw }})
(set-vector! vector-out {{ destx }} {{ desty }} {{ destz }} {{ destw }})
(rlet ((vf1 :class vf :reset-here #t)
(vf2 :class vf :reset-here #t)
(vfd :class vf :reset-here #t)
(acc :class vf :reset-here #t))
(.lvf vfd vector-out)
(.lvf vf1 vector-in-1)
(.lvf vf2 vector-in-2)
(.lvf acc vector-acc)
({{ operation }} vfd vf1 vf2 acc{% if destinationMask %} :mask #b{{ destinationMask }}{% endif %})
(.wait.vf)
(.svf vector-out vfd))
(format #t "(~f, ~f, ~f, ~f)~%" (-> vector-out x) (-> vector-out y) (-> vector-out z) (-> vector-out w))))
(test-vector-outer-product)
@@ -1,23 +1,24 @@
(defun test-vector-math ()
(let ((vector-in-1 (new 'stack 'vector))
{% if twoOperands %}(vector-in-2 (new 'stack 'vector)){% endif %}
(vector-in-2 (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 {{ v1x }} {{ v1y }} {{ v1z }} {{ v1w }})
{% if twoOperands %}(set-vector! vector-in-2 {{ v2x }} {{ v2y }} {{ v2z }} {{ v2w }}){% endif %}
(set-vector! vector-in-2 {{ v2x }} {{ v2y }} {{ v2z }} {{ v2w }})
(set-vector! vector-out {{ destx }} {{ desty }} {{ destz }} {{ destw }})
(rlet ((vf1 :class vf :reset-here #t)
{% if twoOperands %}(vf2 :class vf :reset-here #t){% endif %}
(vf2 :class vf :reset-here #t)
(vf3 :class vf :reset-here #t))
(.lvf vf1 vector-in-1)
{% if twoOperands %}(.lvf vf2 vector-in-2){% endif %}
(.lvf vf2 vector-in-2)
(.lvf vf3 vector-out)
{% if twoOperands %}({{ operation }} vf3 vf1 vf2{% if destinationMask %} :mask #b{{ destinationMask }}{% endif %}){% else %}({{ operation }} vf3 vf1{% if destinationMask %} :mask #b{{ destinationMask }}{% endif %}){% endif %}
({{ operation }} vf3 vf1 vf2{% if destinationMask %} :mask #b{{ destinationMask }}{% endif %})
(.wait.vf)
(.svf vector-out vf3))
(format #t "(~f, ~f, ~f, ~f)~%" (-> vector-out x) (-> vector-out y) (-> vector-out z) (-> vector-out w))))
@@ -0,0 +1,25 @@
(defun test-vector-division ()
(let ((vector-in-1 (new 'stack 'vector))
(vector-in-2 (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 {{ v1x }} {{ v1y }} {{ v1z }} {{ v1w }})
(set-vector! vector-in-2 {{ v2x }} {{ v2y }} {{ v2z }} {{ v2w }})
(set-vector! vector-out {{ destx }} {{ desty }} {{ destz }} {{ destw }})
(rlet ((vf1 :class vf :reset-here #t)
(vf2 :class vf :reset-here #t)
(vf3 :class vf :reset-here #t))
(.lvf vf1 vector-in-1)
(.lvf vf2 vector-in-2)
(.lvf vf3 vector-out)
({{ operation }} vf3 vf1 vf2 :fsf #b{{ fsf }} :ftf #b{{ ftf }})
(.wait.vf)
(.svf vector-out vf3))
(format #t "~f~%" (-> vector-out x))))
(test-vector-division)
@@ -0,0 +1,21 @@
(defun test-vector-sqrt ()
(let ((vector-in-1 (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 {{ v1x }} {{ v1y }} {{ v1z }} {{ v1w }})
(set-vector! vector-out {{ destx }} {{ desty }} {{ destz }} {{ destw }})
(rlet ((vf1 :class vf :reset-here #t)
(vf2 :class vf :reset-here #t))
(.lvf vf1 vector-in-1)
(.lvf vf2 vector-out)
({{ operation }} vf2 vf1 :ftf #b{{ ftf }})
(.wait.vf)
(.svf vector-out vf2))
(format #t "~f~%" (-> vector-out x))))
(test-vector-sqrt)
@@ -0,0 +1,26 @@
(defun test-vector-outer-product ()
(let ((vector-in-1 (new 'stack 'vector))
(vector-in-2 (new 'stack 'vector))
(vector-out (new 'stack 'vector)))
(set-vector! vector-in-1 1.0 2.0 3.0 4.0)
(set-vector! vector-in-2 5.0 6.0 7.0 8.0)
(set-vector! vector-out 0.0 0.0 0.0 999.0)
(rlet ((vf1 :class vf :reset-here #t)
(vf2 :class vf :reset-here #t)
(vf3 :class vf :reset-here #t))
(.lvf vf1 vector-in-1)
(.lvf vf2 vector-in-2)
(.lvf vf3 vector-out)
(.outer.product.vf vf3 vf1 vf2)
(.wait.vf)
(.svf vector-out vf3))
(format #t "(~f, ~f, ~f, ~f)~%" (-> vector-out x) (-> vector-out y) (-> vector-out z) (-> vector-out w))))
(test-vector-outer-product)
+517 -213
View File
@@ -344,222 +344,13 @@ TEST_F(WithGameTests, StaticBoxedArray) {
// VECTOR FLOAT TESTS
struct VectorFloatRegister {
float x = 0;
float y = 0;
float z = 0;
float w = 0;
// ---- One off Tests
void setJson(nlohmann::json& data, std::string vectorKey) {
data[fmt::format("{}x", vectorKey)] = x;
data[fmt::format("{}y", vectorKey)] = y;
data[fmt::format("{}z", vectorKey)] = z;
data[fmt::format("{}w", vectorKey)] = w;
}
float getBroadcastElement(emitter::Register::VF_ELEMENT bc, float defValue) {
switch (bc) {
case emitter::Register::VF_ELEMENT::X:
return x;
case emitter::Register::VF_ELEMENT::Y:
return y;
case emitter::Register::VF_ELEMENT::Z:
return z;
case emitter::Register::VF_ELEMENT::W:
return w;
default:
return defValue;
}
}
std::string toGOALFormat() {
std::string answer = fmt::format("({:.4f}, {:.4f}, {:.4f}, {:.4f})", x, y, z, w);
// {fmt} formats negative 0 as "-0.000", just going to flip any negative zeros to positives as I
// don't think is an OpenGOAL issue
return std::regex_replace(answer, std::regex("-0.0000"), "0.0000");
}
};
struct VectorFloatTestCase {
VectorFloatRegister input1 = {1.5, -1.5, 0.0, 100.5};
VectorFloatRegister input2 = {-5.5, -0.0, 10.0, 7.5};
VectorFloatRegister dest = {11, 22, 33, 44};
int destinationMask = -1;
emitter::Register::VF_ELEMENT bc = emitter::Register::VF_ELEMENT::NONE;
std::function<float(float, float)> operation;
VectorFloatRegister getExpectedResult() {
VectorFloatRegister expectedResult;
expectedResult.x = destinationMask & 0b0001
? operation(input1.x, input2.getBroadcastElement(bc, input2.x))
: dest.x;
expectedResult.y = destinationMask & 0b0010
? operation(input1.y, input2.getBroadcastElement(bc, input2.y))
: dest.y;
expectedResult.z = destinationMask & 0b0100
? operation(input1.z, input2.getBroadcastElement(bc, input2.z))
: dest.z;
expectedResult.w = destinationMask & 0b1000
? operation(input1.w, input2.getBroadcastElement(bc, input2.w))
: dest.w;
return expectedResult;
}
std::string getOperationBroadcast() {
switch (bc) {
case emitter::Register::VF_ELEMENT::X:
return "x";
case emitter::Register::VF_ELEMENT::Y:
return "y";
case emitter::Register::VF_ELEMENT::Z:
return "z";
case emitter::Register::VF_ELEMENT::W:
return "w";
default:
return "";
}
}
void setJson(nlohmann::json& data, std::string func, bool twoOperands = true) {
input1.setJson(data, "v1");
data["twoOperands"] = twoOperands;
if (twoOperands) {
input2.setJson(data, "v2");
}
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
if (destinationMask == -1) {
data["destinationMask"] = false;
} else {
data["destinationMask"] = fmt::format("{:b}", destinationMask);
}
}
};
std::vector<VectorFloatTestCase> vectorMathTestCaseGen() {
std::string test = fmt::format("{:.4f}", -0.0);
std::vector<VectorFloatTestCase> cases = {};
for (int i = 0; i <= 15; i++) {
VectorFloatTestCase testCase = VectorFloatTestCase();
testCase.destinationMask = i;
cases.push_back(testCase);
// Re-add each case with each broadcast varient
for (int j = 0; j < 4; j++) {
VectorFloatTestCase testCaseBC = VectorFloatTestCase();
testCaseBC.destinationMask = i;
testCaseBC.bc = static_cast<emitter::Register::VF_ELEMENT>(j);
cases.push_back(testCaseBC);
}
}
return cases;
TEST_F(WithGameTests, VFOuterProduct) {
runner.run_static_test(env, testCategory, "test-vector-outer-product.gc",
{"(-4.0000, 8.0000, -4.0000, 999.0000)\n0\n"});
}
class VectorFloatParameterizedTestFixtureWithRunner
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase> {
protected:
std::string templateFile = "test-vector-math.template.gc";
};
// NOTE - an excellent article -
// https://www.sandordargo.com/blog/2019/04/24/parameterized-testing-with-gtest
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_ADD_XYZW_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) { return x + y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".add{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-add{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_SUB_XYZW_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) { return x - y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".sub{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-sub{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_MUL_XYZW_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) { return x * y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".mul{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-mul{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_MIN_XYZW_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) { return fmin(x, y); };
nlohmann::json data;
testCase.setJson(data, fmt::format(".min{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-min{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_MAX_XYZW_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) { return fmax(x, y); };
nlohmann::json data;
testCase.setJson(data, fmt::format(".max{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-max{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
// TODO - This test runs more often than the rest, should probably be split into it's own fixture
// (broadcasting ignored!)
TEST_P(VectorFloatParameterizedTestFixtureWithRunner, VF_ABS_DEST) {
VectorFloatTestCase testCase = GetParam();
testCase.operation = [](float x, float y) {
// Avoid compiler warnings for unused variable, making a varient that accepts a lambda with only
// 1 float is just unnecessary complexity
(void)y;
return fabs(x);
};
nlohmann::json data;
testCase.setJson(data, ".abs.vf", false);
std::string outFile = runner.test_file_name("vector-math-abs-{}.generated.gc");
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner,
::testing::ValuesIn(vectorMathTestCaseGen()));
TEST_F(WithGameTests, VFLoadAndStore) {
runner.run_static_test(env, testCategory, "test-vf-load-and-store.gc", {"2.0000\n0\n"});
}
@@ -596,3 +387,516 @@ TEST(TypeConsistency, TypeConsistency) {
compiler.run_test_no_load("test/goalc/source_templates/with_game/test-build-game.gc");
compiler.run_test_no_load("decompiler/config/all-types.gc");
}
struct VectorFloatRegister {
float x = 0;
float y = 0;
float z = 0;
float w = 0;
void setJson(nlohmann::json& data, std::string vectorKey) {
data[fmt::format("{}x", vectorKey)] = x;
data[fmt::format("{}y", vectorKey)] = y;
data[fmt::format("{}z", vectorKey)] = z;
data[fmt::format("{}w", vectorKey)] = w;
}
float getBroadcastElement(emitter::Register::VF_ELEMENT bc, float defValue) {
switch (bc) {
case emitter::Register::VF_ELEMENT::X:
return x;
case emitter::Register::VF_ELEMENT::Y:
return y;
case emitter::Register::VF_ELEMENT::Z:
return z;
case emitter::Register::VF_ELEMENT::W:
return w;
default:
return defValue;
}
}
std::string toGOALFormat() {
std::string answer = fmt::format("({:.4f}, {:.4f}, {:.4f}, {:.4f})", x, y, z, w);
// {fmt} formats negative 0 as "-0.000", just going to flip any negative zeros to positives as I
// don't think is an OpenGOAL issue
// Additionally, GOAL doesn't have -/+ Inf it seems, so replace with NaN. -nan is also just NaN
return std::regex_replace(std::regex_replace(answer, std::regex("-0.0000"), "0.0000"),
std::regex("nan|inf|-nan|-inf"), "NaN");
}
std::string toGOALFormat(float val) {
std::string answer = fmt::format("{:.4f}", x);
// {fmt} formats negative 0 as "-0.000", just going to flip any negative zeros to positives as I
// don't think is an OpenGOAL issue
// Additionally, GOAL doesn't have -/+ Inf it seems, so replace with NaN
return std::regex_replace(std::regex_replace(answer, std::regex("-0.0000"), "0.0000"),
std::regex("nan|inf|-nan|-inf"), "NaN");
}
};
struct VectorFloatTestCase {
VectorFloatRegister dest = {11, 22, 33, 44};
int destinationMask = -1;
emitter::Register::VF_ELEMENT bc = emitter::Register::VF_ELEMENT::NONE;
std::string getOperationBroadcast() {
switch (bc) {
case emitter::Register::VF_ELEMENT::X:
return ".x";
case emitter::Register::VF_ELEMENT::Y:
return ".y";
case emitter::Register::VF_ELEMENT::Z:
return ".z";
case emitter::Register::VF_ELEMENT::W:
return ".w";
default:
return "";
}
}
virtual VectorFloatRegister getExpectedResult() = 0;
virtual void setJson(nlohmann::json& data, std::string func) = 0;
};
struct VectorFloatTestCase_TwoOperand : VectorFloatTestCase {
VectorFloatRegister input1 = {1.5, -1.5, 0.0, 100.5};
VectorFloatRegister input2 = {-5.5, -0.0, 10.0, 7.5};
std::function<float(float, float)> operation;
VectorFloatRegister getExpectedResult() {
VectorFloatRegister expectedResult;
expectedResult.x = destinationMask & 0b0001
? operation(input1.x, input2.getBroadcastElement(bc, input2.x))
: dest.x;
expectedResult.y = destinationMask & 0b0010
? operation(input1.y, input2.getBroadcastElement(bc, input2.y))
: dest.y;
expectedResult.z = destinationMask & 0b0100
? operation(input1.z, input2.getBroadcastElement(bc, input2.z))
: dest.z;
expectedResult.w = destinationMask & 0b1000
? operation(input1.w, input2.getBroadcastElement(bc, input2.w))
: dest.w;
return expectedResult;
}
void setJson(nlohmann::json& data, std::string func) {
input1.setJson(data, "v1");
input2.setJson(data, "v2");
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
if (destinationMask == -1) {
data["destinationMask"] = false;
} else {
data["destinationMask"] = fmt::format("{:b}", destinationMask);
}
}
};
std::vector<VectorFloatTestCase_TwoOperand> vectorMathCaseGen_TwoOperand() {
std::vector<VectorFloatTestCase_TwoOperand> cases = {};
for (int i = 0; i <= 15; i++) {
VectorFloatTestCase_TwoOperand testCase = VectorFloatTestCase_TwoOperand();
testCase.destinationMask = i;
cases.push_back(testCase);
// Re-add each case with each broadcast variant
for (int j = 0; j < 4; j++) {
VectorFloatTestCase_TwoOperand testCaseBC = VectorFloatTestCase_TwoOperand();
testCaseBC.destinationMask = i;
testCaseBC.bc = static_cast<emitter::Register::VF_ELEMENT>(j);
cases.push_back(testCaseBC);
}
}
return cases;
}
class VectorFloatParameterizedTestFixtureWithRunner_TwoOperand
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase_TwoOperand> {
protected:
std::string templateFile = "test-vector-math-2-operand.template.gc";
};
// NOTE - an excellent article -
// https://www.sandordargo.com/blog/2019/04/24/parameterized-testing-with-gtest
// --- 2 Operand VF Operations
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperand, VF_ADD_XYZW_DEST) {
VectorFloatTestCase_TwoOperand testCase = GetParam();
testCase.operation = [](float x, float y) { return x + y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".add{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-add{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperand, VF_SUB_XYZW_DEST) {
VectorFloatTestCase_TwoOperand testCase = GetParam();
testCase.operation = [](float x, float y) { return x - y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".sub{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-sub{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperand, VF_MUL_XYZW_DEST) {
VectorFloatTestCase_TwoOperand testCase = GetParam();
testCase.operation = [](float x, float y) { return x * y; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".mul{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-mul{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperand, VF_MIN_XYZW_DEST) {
VectorFloatTestCase_TwoOperand testCase = GetParam();
testCase.operation = [](float x, float y) { return fmin(x, y); };
nlohmann::json data;
testCase.setJson(data, fmt::format(".min{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-min{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperand, VF_MAX_XYZW_DEST) {
VectorFloatTestCase_TwoOperand testCase = GetParam();
testCase.operation = [](float x, float y) { return fmax(x, y); };
nlohmann::json data;
testCase.setJson(data, fmt::format(".max{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-max{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner_TwoOperand,
::testing::ValuesIn(vectorMathCaseGen_TwoOperand()));
// --- 1 Operand VF Operations
struct VectorFloatTestCase_SingleOperand : VectorFloatTestCase {
VectorFloatRegister input1 = {1.5, -1.5, 0.0, 100.5};
std::function<float(float)> operation;
VectorFloatRegister getExpectedResult() {
VectorFloatRegister expectedResult;
expectedResult.x =
destinationMask & 0b0001 ? operation(input1.getBroadcastElement(bc, input1.x)) : dest.x;
expectedResult.y =
destinationMask & 0b0010 ? operation(input1.getBroadcastElement(bc, input1.y)) : dest.y;
expectedResult.z =
destinationMask & 0b0100 ? operation(input1.getBroadcastElement(bc, input1.z)) : dest.z;
expectedResult.w =
destinationMask & 0b1000 ? operation(input1.getBroadcastElement(bc, input1.w)) : dest.w;
return expectedResult;
}
void setJson(nlohmann::json& data, std::string func) {
input1.setJson(data, "v1");
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
if (destinationMask == -1) {
data["destinationMask"] = false;
} else {
data["destinationMask"] = fmt::format("{:b}", destinationMask);
}
}
};
std::vector<VectorFloatTestCase_SingleOperand> vectorMathCaseGen_SingleOperand_NoBroadcast() {
std::vector<VectorFloatTestCase_SingleOperand> cases = {};
for (int i = 0; i <= 15; i++) {
VectorFloatTestCase_SingleOperand testCase = VectorFloatTestCase_SingleOperand();
testCase.destinationMask = i;
cases.push_back(testCase);
}
return cases;
}
class VectorFloatParameterizedTestFixtureWithRunner_SingleOperand
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase_SingleOperand> {
protected:
std::string templateFile = "test-vector-math-1-operand.template.gc";
};
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_SingleOperand, VF_ABS_DEST) {
VectorFloatTestCase_SingleOperand testCase = GetParam();
testCase.operation = [](float x) { return fabs(x); };
nlohmann::json data;
testCase.setJson(data, ".abs.vf");
std::string outFile = runner.test_file_name("vector-math-abs-{}.generated.gc");
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner_SingleOperand,
::testing::ValuesIn(vectorMathCaseGen_SingleOperand_NoBroadcast()));
// --- 2 Operand With ACC VF Operations
// TODO - these pollute tests, it would be nicer long-term to move these into the framework
// namespace
struct VectorFloatTestCase_TwoOperandACC : VectorFloatTestCase {
VectorFloatRegister input1 = {1.5, -1.5, 0.0, 100.5};
VectorFloatRegister input2 = {-5.5, -0.0, 10.0, 7.5};
VectorFloatRegister acc = {-15.5, -0.0, 20.0, 70.5};
std::function<float(float, float, float)> operation;
VectorFloatRegister getExpectedResult() {
VectorFloatRegister expectedResult;
expectedResult.x = destinationMask & 0b0001
? operation(input1.x, input2.getBroadcastElement(bc, input2.x), acc.x)
: dest.x;
expectedResult.y = destinationMask & 0b0010
? operation(input1.y, input2.getBroadcastElement(bc, input2.y), acc.y)
: dest.y;
expectedResult.z = destinationMask & 0b0100
? operation(input1.z, input2.getBroadcastElement(bc, input2.z), acc.z)
: dest.z;
expectedResult.w = destinationMask & 0b1000
? operation(input1.w, input2.getBroadcastElement(bc, input2.w), acc.w)
: dest.w;
return expectedResult;
}
void setJson(nlohmann::json& data, std::string func) {
input1.setJson(data, "v1");
input2.setJson(data, "v2");
acc.setJson(data, "acc");
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
if (destinationMask == -1) {
data["destinationMask"] = false;
} else {
data["destinationMask"] = fmt::format("{:b}", destinationMask);
}
}
};
// TODO - unnecessary duplication for these generation methods, use some templates (only the type
// changes)
std::vector<VectorFloatTestCase_TwoOperandACC> vectorMathCaseGen_TwoOperandACC() {
std::vector<VectorFloatTestCase_TwoOperandACC> cases = {};
for (int i = 0; i <= 15; i++) {
VectorFloatTestCase_TwoOperandACC testCase = VectorFloatTestCase_TwoOperandACC();
testCase.destinationMask = i;
cases.push_back(testCase);
// Re-add each case with each broadcast variant
for (int j = 0; j < 4; j++) {
VectorFloatTestCase_TwoOperandACC testCaseBC = VectorFloatTestCase_TwoOperandACC();
testCaseBC.destinationMask = i;
testCaseBC.bc = static_cast<emitter::Register::VF_ELEMENT>(j);
cases.push_back(testCaseBC);
}
}
return cases;
}
class VectorFloatParameterizedTestFixtureWithRunner_TwoOperandACC
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase_TwoOperandACC> {
protected:
std::string templateFile = "test-vector-math-2-operand-acc.template.gc";
};
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperandACC, VF_MUL_ADD_XYZW_DEST) {
VectorFloatTestCase_TwoOperandACC testCase = GetParam();
testCase.operation = [](float x, float y, float acc) { return (x * y) + acc; };
nlohmann::json data;
testCase.setJson(data, fmt::format(".add.mul{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-add-mul{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperandACC, VF_MUL_SUB_XYZW_DEST) {
VectorFloatTestCase_TwoOperandACC testCase = GetParam();
testCase.operation = [](float x, float y, float acc) { return acc - (x * y); };
nlohmann::json data;
testCase.setJson(data, fmt::format(".sub.mul{}.vf", testCase.getOperationBroadcast()));
std::string outFile = runner.test_file_name(
fmt::format("vector-math-sub-mul{}-{{}}.generated.gc", testCase.getOperationBroadcast()));
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat())});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner_TwoOperandACC,
::testing::ValuesIn(vectorMathCaseGen_TwoOperandACC()));
// ---- Two Operand Quotient Register Operations
struct VectorFloatTestCase_TwoOperandQuotient : VectorFloatTestCase {
VectorFloatRegister input1 = {1.5, -1.5, 0.0, 100.5};
VectorFloatRegister input2 = {-5.5, -0.0, 10.0, 10.0};
int fsf = 0;
int ftf = 0;
std::function<float(float, float)> operation;
VectorFloatRegister getExpectedResult() {
float operand1 =
input1.getBroadcastElement(static_cast<emitter::Register::VF_ELEMENT>(fsf), input1.x);
float operand2 =
input2.getBroadcastElement(static_cast<emitter::Register::VF_ELEMENT>(ftf), input2.x);
float result = operation(operand1, operand2);
VectorFloatRegister expectedResult;
expectedResult.x = result;
expectedResult.y = result;
expectedResult.z = result;
expectedResult.w = result;
return expectedResult;
}
void setJson(nlohmann::json& data, std::string func) {
input1.setJson(data, "v1");
input2.setJson(data, "v2");
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
data["ftf"] = fmt::format("{:b}", ftf);
data["fsf"] = fmt::format("{:b}", fsf);
}
};
std::vector<VectorFloatTestCase_TwoOperandQuotient> vectorMathCaseGen_TwoOperandQuotient() {
std::vector<VectorFloatTestCase_TwoOperandQuotient> cases = {};
for (int i = 0; i <= 3; i++) {
VectorFloatTestCase_TwoOperandQuotient testCase = VectorFloatTestCase_TwoOperandQuotient();
testCase.fsf = i;
for (int j = 0; j <= 3; j++) {
testCase.ftf = j;
cases.push_back(testCase);
}
}
return cases;
}
class VectorFloatParameterizedTestFixtureWithRunner_TwoOperandQuotient
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase_TwoOperandQuotient> {
protected:
std::string templateFile = "test-vector-math-division.template.gc";
};
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_TwoOperandQuotient, VF_DIV_FTF_FSF) {
VectorFloatTestCase_TwoOperandQuotient testCase = GetParam();
testCase.operation = [](float x, float y) { return x / y; };
nlohmann::json data;
testCase.setJson(data, ".div.vf");
std::string outFile = runner.test_file_name("vector-math-div-{}.generated.gc");
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat(
testCase.getExpectedResult().x))});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner_TwoOperandQuotient,
::testing::ValuesIn(vectorMathCaseGen_TwoOperandQuotient()));
// ---- Single Operand Quotient Register Operations
struct VectorFloatTestCase_OneOperandQuotient : VectorFloatTestCase {
VectorFloatRegister input1 = {2, -2, 0.0, 100};
int ftf = 0;
std::function<float(float)> operation;
VectorFloatRegister getExpectedResult() {
float operand1 =
input1.getBroadcastElement(static_cast<emitter::Register::VF_ELEMENT>(ftf), input1.x);
float result = operation(operand1);
VectorFloatRegister expectedResult;
expectedResult.x = result;
expectedResult.y = result;
expectedResult.z = result;
expectedResult.w = result;
return expectedResult;
}
void setJson(nlohmann::json& data, std::string func) {
input1.setJson(data, "v1");
dest.setJson(data, "dest");
data["operation"] = fmt::format(func);
data["ftf"] = fmt::format("{:b}", ftf);
}
};
std::vector<VectorFloatTestCase_OneOperandQuotient> vectorMathCaseGen_OneOperandQuotient() {
std::vector<VectorFloatTestCase_OneOperandQuotient> cases = {};
for (int i = 0; i <= 3; i++) {
VectorFloatTestCase_OneOperandQuotient testCase = VectorFloatTestCase_OneOperandQuotient();
testCase.ftf = i;
cases.push_back(testCase);
}
return cases;
}
class VectorFloatParameterizedTestFixtureWithRunner_OneOperandQuotient
: public WithGameTests,
public ::testing::WithParamInterface<VectorFloatTestCase_OneOperandQuotient> {
protected:
std::string templateFile = "test-vector-math-sqrt.template.gc";
};
TEST_P(VectorFloatParameterizedTestFixtureWithRunner_OneOperandQuotient, VF_SQRT_FTF) {
VectorFloatTestCase_OneOperandQuotient testCase = GetParam();
testCase.operation = [](float x) { return sqrt(x); };
nlohmann::json data;
testCase.setJson(data, ".sqrt.vf");
std::string outFile = runner.test_file_name("vector-math-sqrt-{}.generated.gc");
env.write(templateFile, data, outFile);
runner.run_test(testCategory, outFile,
{fmt::format("{}\n0\n", testCase.getExpectedResult().toGOALFormat(
testCase.getExpectedResult().x))});
}
INSTANTIATE_TEST_SUITE_P(WithGameTests_VectorFloatTests,
VectorFloatParameterizedTestFixtureWithRunner_OneOperandQuotient,
::testing::ValuesIn(vectorMathCaseGen_OneOperandQuotient()));
+34
View File
@@ -11,6 +11,13 @@ TEST(EmitterAVX, VF_NOP) {
EXPECT_EQ(tester.dump_to_hex_string(true), "D9D0");
}
TEST(EmitterAVX, WAIT_VF) {
CodeTester tester;
tester.init_code_buffer(1024);
tester.emit(IGen::wait_vf());
EXPECT_EQ(tester.dump_to_hex_string(true), "9B");
}
TEST(EmitterAVX, MOV_VF) {
CodeTester tester;
tester.init_code_buffer(10000);
@@ -281,6 +288,33 @@ TEST(EmitterAVX, BlendVF) {
"43110CED03");
}
TEST(EmitterAVX, DivVF) {
CodeTester tester;
tester.init_code_buffer(1024);
tester.emit(IGen::div_vf(XMM0 + 3, XMM0 + 3, XMM0 + 3));
tester.emit(IGen::div_vf(XMM0 + 3, XMM0 + 3, XMM0 + 13));
tester.emit(IGen::div_vf(XMM0 + 3, XMM0 + 13, XMM0 + 3));
tester.emit(IGen::div_vf(XMM0 + 3, XMM0 + 13, XMM0 + 13));
tester.emit(IGen::div_vf(XMM0 + 13, XMM0 + 3, XMM0 + 3));
tester.emit(IGen::div_vf(XMM0 + 13, XMM0 + 3, XMM0 + 13));
tester.emit(IGen::div_vf(XMM0 + 13, XMM0 + 13, XMM0 + 3));
tester.emit(IGen::div_vf(XMM0 + 13, XMM0 + 13, XMM0 + 13));
EXPECT_EQ(tester.dump_to_hex_string(true),
"C5E05EDBC4C1605EDDC5905EDBC4C1105EDDC5605EEBC441605EEDC5105EEBC441105EED");
}
TEST(EmitterAVX, SqrtVF) {
CodeTester tester;
tester.init_code_buffer(1024);
tester.emit(IGen::sqrt_vf(XMM0 + 3, XMM0 + 4));
tester.emit(IGen::sqrt_vf(XMM0 + 3, XMM0 + 14));
tester.emit(IGen::sqrt_vf(XMM0 + 13, XMM0 + 4));
tester.emit(IGen::sqrt_vf(XMM0 + 13, XMM0 + 14));
EXPECT_EQ(tester.dump_to_hex_string(true), "C5F851DCC4C17851DEC57851ECC4417851EE");
}
TEST(EmitterAVX, RIP) {
CodeTester tester;
tester.init_code_buffer(1024);