// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "gcn_test_runner.hpp" #include "instructions.hpp" #include "translator.hpp" class GcnTest : public ::testing::Test { protected: void SetUp() override {} void TearDown() override {} }; struct F32x2 { float a; float b; }; // Example // TEST_F(GcnTest, test_name) { // // Runner sets the vulkan context // auto runner = gcn_test::Runner::instance().value(); // // // v_add_f32 v0, v0, v1 // auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); // // // run tells how to interpret the result (only 32bit as of now) // // the second argument is templated, it can be at most 4 u32s // // the data is accessible by the instruction in v0-4 and s0-4 (mirrored) // // the result has to be placed in v0 // auto result = runner->run(spirv, F32x2{1.5f, 6.0f}); // // EXPECT_TRUE(result.has_value()); // EXPECT_EQ(*result, 7.5f); // } TEST_F(GcnTest, add_f32) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, F32x2{1.5f, 6.0f}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 7.5f); } TEST_F(GcnTest, add_nan) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, F32x2{1.0f, std::numeric_limits::quiet_NaN()}); EXPECT_TRUE(result.has_value()); EXPECT_TRUE(std::isnan(*result)); } using half = half_float::half; struct F16x2 { half a; half b = half(0.0f); bool operator==(const F16x2& rhs) const = default; }; static_assert(sizeof(F16x2) == sizeof(float)); TEST_F(GcnTest, add_f16) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_ADD_F16, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, F16x2{half(2.0f)}); } TEST_F(GcnTest, add_f16_clamp) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetClamp(true).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, F16x2{half(1.0f)}); //confirmed with neo } TEST_F(GcnTest, add_f16_neg) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetNeg({true, true, false}).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f)}, F16x2{half(1.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ((*result).a, half(-2.0f)); //confirmed with neo } TEST_F(GcnTest, add_f16_opsel_hi) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetOpSel({true, true, false, true}).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(1.0f), half(2.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ((*result).a, half(1.0f)); EXPECT_EQ((*result).b, half(4.0f)); } TEST_F(GcnTest, sub_f16) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_SUB_F16, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{F16x2{half(0.0f)}, F16x2{half(1.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, F16x2{half(-1.0f)}); //confirmed with neo } TEST_F(GcnTest, mul_legacy_nan) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0); } TEST_F(GcnTest, mul_nan) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MUL_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); EXPECT_TRUE(result.has_value()); EXPECT_TRUE(std::isnan(*result)); } TEST_F(GcnTest, min_legacy_nan) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_LEGACY_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x7fc00000); } TEST_F(GcnTest, min_nan) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP2(OpcodeVOP2::V_MIN_F32, VOperand8::V0, SOperand9::V0, VOperand8::V1).Get()); auto result = runner->run(spirv, std::array{u32(0), u32(0x7fc00000)}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0); } TEST_F(GcnTest, add3_u32_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get()); auto result = runner->run(spirv, std::array{0, 1, 2}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 3); } TEST_F(GcnTest, add3_u32_2) { auto runner = gcn_test::Runner::instance().value(); auto big = 2000000000; auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get()); auto result = runner->run(spirv, std::array{big, big, big}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x65A0BC00); } TEST_F(GcnTest, add3_u32_3) { auto runner = gcn_test::Runner::instance().value(); auto big = 2000000000; auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get()); auto result = runner->run(spirv, std::array{big, big, big}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x65A0BC00); } TEST_F(GcnTest, add3_u32_4) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ADD3_U32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).Get()); auto result = runner->run(spirv, std::array{0, 1, 2}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x80000003); } TEST_F(GcnTest, or3_u32_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get()); auto result = runner->run(spirv, std::array{0xF0F0F0F0, 0x07070707, 0x11111111}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xF7F7F7F7); } TEST_F(GcnTest, or3_u32_2) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get()); auto result = runner->run(spirv, std::array{0x07070707, 0x11111111, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x57575757); } TEST_F(GcnTest, or3_u32_3) { auto runner = gcn_test::Runner::instance().value(); auto big = 2000000000; auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get()); auto result = runner->run(spirv, std::array{0x07070707, 0x11111111, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x57575757); } TEST_F(GcnTest, or3_u32_4) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_OR3_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({0,0,1}).Get()); auto result = runner->run(spirv, std::array{0x07070707, 0x11111111, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xD7575757); } TEST_F(GcnTest, and_or_b32_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).Get()); auto result = runner->run(spirv, std::array{0xF0F0F0F0, 0x07070707, 0x11111111}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x11111111); } TEST_F(GcnTest, and_or_b32_2) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Mul2).Get()); auto result = runner->run(spirv, std::array{0x40404040, 0x40404040, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x40404040); } TEST_F(GcnTest, and_or_b32_3) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetClamp(true).Get()); auto result = runner->run(spirv, std::array{0x40404040, 0x40404040, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x40404040); } TEST_F(GcnTest, and_or_b32_4) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).Get()); auto result = runner->run(spirv, std::array{0x07070707, 0x11111111, 0xF0F0F0F0}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xF1F1F1F1); } TEST_F(GcnTest, and_or_b32_5) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetNeg({1,0,0}).SetAbs({1,0,0}).Get()); auto result = runner->run(spirv, std::array{0x77777777, 0xB0B0B0B0, 0x11111111}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xB1313131); } TEST_F(GcnTest, and_or_b32_6) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Mul2).Get()); auto result = runner->run(spirv, std::array{0x40404040, 0xB0B0B0B0, 0x11111111}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x11111111); } TEST_F(GcnTest, and_or_b32_7) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOmod(Omod::Div2).Get()); auto result = runner->run(spirv, std::array{0xB0B0B0B0, 0x77777777, 0x40404040}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x70707070); } TEST_F(GcnTest, and_or_b32_8) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_AND_OR_B32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetAbs({1,1,0}).Get()); auto result = runner->run(spirv, std::array{0xB0B0B0B0, 0x11111111, 0x11111111}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x11111111); }