// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #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 {} static void TearDownTestSuite() { gcn_test::Runner::DestroyInstance(); } }; struct F32x2 { float a; float b; }; struct FrontFaceSpirvInfo { bool has_front_facing{}; u32 select_count{}; u32 true_value{}; u32 false_value{}; }; FrontFaceSpirvInfo InspectFrontFaceSpirv(const std::vector& spirv) { FrontFaceSpirvInfo info{}; if (spirv.size() < 5U) { ADD_FAILURE() << "SPIR-V header is truncated"; return info; } std::unordered_map constants; u32 true_value_id{}; u32 false_value_id{}; for (size_t offset = 5; offset < spirv.size();) { const u32 instruction = spirv[offset]; const u32 word_count = instruction >> 16; const auto opcode = static_cast(instruction & 0xffffU); if (word_count == 0U || offset + word_count > spirv.size()) { ADD_FAILURE() << "Malformed SPIR-V instruction at word " << offset; return info; } if (opcode == spv::Op::OpDecorate && word_count >= 4U && spirv[offset + 2] == static_cast(spv::Decoration::BuiltIn) && spirv[offset + 3] == static_cast(spv::BuiltIn::FrontFacing)) { info.has_front_facing = true; } else if (opcode == spv::Op::OpConstant && word_count == 4U) { constants.emplace(spirv[offset + 2], spirv[offset + 3]); } else if (opcode == spv::Op::OpSelect && word_count == 6U) { ++info.select_count; true_value_id = spirv[offset + 4]; false_value_id = spirv[offset + 5]; } offset += word_count; } if (info.select_count == 1U && constants.contains(true_value_id) && constants.contains(false_value_id)) { info.true_value = constants.at(true_value_id); info.false_value = constants.at(false_value_id); } return info; } struct PullModelSpirvInfo { u32 bary_coord_khr_count{}; u32 frag_coord_count{}; u32 pull_model_amd_count{}; u32 fragment_barycentric_khr_count{}; u32 fmul_count{}; }; PullModelSpirvInfo InspectPullModelSpirv(const std::vector& spirv) { PullModelSpirvInfo info{}; if (spirv.size() < 5U) { ADD_FAILURE() << "SPIR-V header is truncated"; return info; } for (size_t offset = 5; offset < spirv.size();) { const u32 instruction = spirv[offset]; const u32 word_count = instruction >> 16; const auto opcode = static_cast(instruction & 0xffffU); if (word_count == 0U || offset + word_count > spirv.size()) { ADD_FAILURE() << "Malformed SPIR-V instruction at word " << offset; return info; } if (opcode == spv::Op::OpCapability && word_count >= 2U && spirv[offset + 1] == static_cast(spv::Capability::FragmentBarycentricKHR)) { ++info.fragment_barycentric_khr_count; } else if (opcode == spv::Op::OpDecorate && word_count >= 4U && spirv[offset + 2] == static_cast(spv::Decoration::BuiltIn)) { const auto builtin = static_cast(spirv[offset + 3]); switch (builtin) { case spv::BuiltIn::BaryCoordKHR: ++info.bary_coord_khr_count; break; case spv::BuiltIn::FragCoord: ++info.frag_coord_count; break; case spv::BuiltIn::BaryCoordPullModelAMD: ++info.pull_model_amd_count; break; default: break; } } else if (opcode == spv::Op::OpFMul) { ++info.fmul_count; } offset += word_count; } return info; } TEST_F(GcnTest, fragment_front_face_uses_float_sign_bits) { const auto info = InspectFrontFaceSpirv(TranslateFragmentFrontFaceToSpirv(false)); EXPECT_TRUE(info.has_front_facing); EXPECT_EQ(info.select_count, 1U); EXPECT_EQ(info.true_value, 0x3f800000U); EXPECT_EQ(info.false_value, 0xbf800000U); } TEST_F(GcnTest, fragment_front_face_uses_all_bits) { const auto info = InspectFrontFaceSpirv(TranslateFragmentFrontFaceToSpirv(true)); EXPECT_TRUE(info.has_front_facing); EXPECT_EQ(info.select_count, 1U); EXPECT_EQ(info.true_value, 1U); EXPECT_EQ(info.false_value, 0U); } TEST_F(GcnTest, khr_barycentrics_reconstruct_pull_model) { const auto info = InspectPullModelSpirv(TranslateFragmentPullModelToSpirv(false)); EXPECT_EQ(info.bary_coord_khr_count, 1U); EXPECT_EQ(info.frag_coord_count, 1U); EXPECT_EQ(info.fragment_barycentric_khr_count, 1U); EXPECT_EQ(info.pull_model_amd_count, 0U); EXPECT_EQ(info.fmul_count, 2U); } TEST_F(GcnTest, amd_barycentrics_use_native_pull_model) { const auto info = InspectPullModelSpirv(TranslateFragmentPullModelToSpirv(true)); EXPECT_EQ(info.pull_model_amd_count, 1U); EXPECT_EQ(info.frag_coord_count, 0U); EXPECT_EQ(info.bary_coord_khr_count, 0U); EXPECT_EQ(info.fmul_count, 0U); } TEST_F(GcnTest, interp_mov_selects_p10_p20_and_p0) { const auto p10 = TranslateFragmentInterpMovSelector(0, false, false); EXPECT_EQ(p10.attribute_indices, (std::vector{1U, 0U})); EXPECT_EQ(p10.fsub_count, 1U); const auto p20 = TranslateFragmentInterpMovSelector(1, false, false); EXPECT_EQ(p20.attribute_indices, (std::vector{2U, 0U})); EXPECT_EQ(p20.fsub_count, 1U); const auto p0 = TranslateFragmentInterpMovSelector(2, false, false); EXPECT_EQ(p0.attribute_indices, (std::vector{0U})); EXPECT_EQ(p0.fsub_count, 0U); } TEST_F(GcnTest, interp_mov_uses_vertex_values_only_for_passthrough_inputs) { for (const auto [flat_shade, offset5] : {std::pair{false, false}, std::pair{true, false}, std::pair{false, true}}) { const auto p10 = TranslateFragmentInterpMovSelector(0, flat_shade, offset5); EXPECT_EQ(p10.attribute_indices, (std::vector{1U, 0U})); EXPECT_EQ(p10.fsub_count, 1U); } const auto p10 = TranslateFragmentInterpMovSelector(0, true, true); EXPECT_EQ(p10.attribute_indices, (std::vector{1U})); EXPECT_EQ(p10.fsub_count, 0U); } // 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_i32_carry_feeds_addc_u32) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ VOP2(OpcodeVOP2::V_ADD_I32, VOperand8::V1, SOperand9::S0, VOperand8::V1).Get(), VOP2(OpcodeVOP2::V_ADDC_U32, VOperand8::V0, SOperand9::S2, VOperand8::V3).Get(), }; const auto spirv = TranslateToSpirv(instructions); const auto overflow = runner->run(spirv, std::array{0xffffffffU, 1U, 7U, 0U}); ASSERT_TRUE(overflow.has_value()); EXPECT_EQ(*overflow, 8U); const auto no_overflow = runner->run(spirv, std::array{2U, 1U, 7U, 0U}); ASSERT_TRUE(no_overflow.has_value()); EXPECT_EQ(*no_overflow, 7U); } 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); } TEST_F(GcnTest, mad_mix_f32_1) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_MAD_MIX_F32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOpSelHi({0}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, std::array{2.0f, 3.0f, 4.0f}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 10.0f); } TEST_F(GcnTest, mad_mix_f32_2) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_MAD_MIX_F32, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOpSelHi({1,1,0}).SetOpSel({1,0,0}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, std::array{ std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(4.0f)} ); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 26.0f); } TEST_F(GcnTest, mad_mixlo_f16_1) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_MAD_MIXLO_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOpSelHi({1,1,0}).SetOpSel({1,0,0}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, std::array{ std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(4.0f)} ); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(26.0f), half(0.5f)})); } TEST_F(GcnTest, mad_mixhi_f16_1) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_MAD_MIXHI_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1, SOperand9::V2).SetOpSelHi({1,1,0}).SetOpSel({1,0,0}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, std::array{ std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(F16x2{half(44.0f), half(0.5f)}), std::bit_cast(4.0f)} ); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(44.0f), half(26.0f)})); } TEST_F(GcnTest, lshrrev_b16_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_LSHRREV_B16, VOperand8::V0, SOperand9::V0, SOperand9::V1).Get()); auto result = runner->run(spirv, std::array{0xFFFFFFF2, 0x88881000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xFFFF0400); } TEST_F(GcnTest, lshrrev_b16_2) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_LSHRREV_B16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetOpSel({0,0,0,1}).Get()); auto result = runner->run(spirv, std::array{0xFFFFFFF2, 0x88881000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x0400FFF2); } TEST_F(GcnTest, lshrrev_b16_3) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_LSHRREV_B16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetOpSel({0,1,0,0}).Get()); auto result = runner->run(spirv, std::array{0xFFFFFFF2, 0x88881000}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xFFFF2222); } TEST_F(GcnTest, lshlrev_b16_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_LSHLREV_B16, VOperand8::V0, SOperand9::V0, SOperand9::V1).Get()); auto result = runner->run(spirv, std::array{0xFFFFFFF3, 0x88888888}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0xFFFF4440); } TEST_F(GcnTest, ashrrev_i16_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3A(OpcodeVOP3::V_ASHRREV_I16, VOperand8::V0, SOperand9::V0, SOperand9::V1).Get()); auto result = runner->run(spirv, std::array{0x1234FFF3, 0x88888888}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x1234F111); } TEST_F(GcnTest, pk_add_f16_1) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(3.0f), half(4.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(4.0f), half(6.0f)})); } TEST_F(GcnTest, pk_add_f16_2) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::Const0, SOperand9::ConstInv2Pi).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, 0U); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x00003118); } TEST_F(GcnTest, pk_add_f16_3) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::Const0, SOperand9::ConstInv2Pi).SetOpSel({0,1,1}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, 0U); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0); } TEST_F(GcnTest, pk_add_f16_4) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::Const0p5, SOperand9::Const0p5).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, 0U); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x3C00); } TEST_F(GcnTest, pk_add_f16_5) { auto runner = gcn_test::Runner::instance().value(); auto inst = VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::Const0, SOperand9::ConstInv2Pi).SetOpSelHi({0,0,0}).Get(); auto spirv = TranslateToSpirv(inst); auto result = runner->run(spirv, 0U); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, 0x31183118); } TEST_F(GcnTest, pk_add_f16_neg_lo) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetNeg({1,1,0}).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(3.0f), half(4.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(-4.0f), half(6.0f)})); } TEST_F(GcnTest, pk_add_f16_neg_hi) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetNegHi({1,1,0}).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(3.0f), half(4.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(4.0f), half(-6.0f)})); } TEST_F(GcnTest, pk_add_f16_op_sel_reversed) { auto runner = gcn_test::Runner::instance().value(); auto spirv = TranslateToSpirv(VOP3P(OpcodeVOP3P::V_PK_ADD_F16, VOperand8::V0, SOperand9::V0, SOperand9::V1).SetOpSel({1,1,1}).SetOpSelHi({0,0,0}).Get()); auto result = runner->run(spirv, std::array{F16x2{half(1.0f), half(2.0f)}, F16x2{half(3.0f), half(4.0f)}}); EXPECT_TRUE(result.has_value()); EXPECT_EQ(*result, (F16x2{half(6.0f), half(4.0f)})); } TEST_F(GcnTest, addk_i32_overflow_scc) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ SOPK(OpcodeSOPK::S_ADDK_I32, SOperand7::S0, 0xffff).Get(), SOP2(OpcodeSOP2::S_CSELECT_B32, SOperand7::S0, SOperand8::Const1, SOperand8::Const0).Get(), VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::S0).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto overflow = runner->run(spirv, std::array{0x80000000U, 0U, 0U, 0U}); ASSERT_TRUE(overflow.has_value()); EXPECT_EQ(*overflow, 1U); auto no_overflow = runner->run(spirv, std::array{5U, 0U, 0U, 0U}); ASSERT_TRUE(no_overflow.has_value()); EXPECT_EQ(*no_overflow, 0U); } TEST_F(GcnTest, bitcmp1_b64_bit32) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ SOPC(OpcodeSOPC::S_BITCMP1_B64, SOperand8::S0, SOperand8::S2).Get(), SOP2(OpcodeSOP2::S_CSELECT_B32, SOperand7::S0, SOperand8::Const1, SOperand8::Const0).Get(), VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::S0).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto result = runner->run(spirv, std::array{0U, 1U, 32U, 0U}); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 1U); } TEST_F(GcnTest, subb_u32_clears_vcc) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ VOP2(OpcodeVOP2::V_SUB_I32, VOperand8::V1, SOperand9::S0, VOperand8::V1).Get(), VOP2(OpcodeVOP2::V_SUBB_U32, VOperand8::V1, SOperand9::S2, VOperand8::V3).Get(), VOP2(OpcodeVOP2::V_ADDC_U32, VOperand8::V0, SOperand9::Const0, VOperand8::V3).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto result = runner->run(spirv, std::array{0U, 1U, 5U, 0U}); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 0U); } TEST_F(GcnTest, subb_u32_scc_wrap) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ SOP2(OpcodeSOP2::S_ADD_U32, SOperand7::S3, SOperand8::S0, SOperand8::S1).Get(), SOP2(OpcodeSOP2::S_SUBB_U32, SOperand7::S3, SOperand8::Const0, SOperand8::S2).Get(), SOP2(OpcodeSOP2::S_CSELECT_B32, SOperand7::S0, SOperand8::Const1, SOperand8::Const0).Get(), VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::S0).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto result = runner->run(spirv, std::array{0xffffffffU, 1U, 0xffffffffU, 0U}); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 1U); } TEST_F(GcnTest, addc_u32_clears_scc) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ SOP2(OpcodeSOP2::S_ADD_U32, SOperand7::S3, SOperand8::S0, SOperand8::S1).Get(), SOP2(OpcodeSOP2::S_ADDC_U32, SOperand7::S3, SOperand8::Const0, SOperand8::Const0).Get(), SOP2(OpcodeSOP2::S_CSELECT_B32, SOperand7::S0, SOperand8::Const1, SOperand8::Const0).Get(), VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::S0).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto result = runner->run(spirv, std::array{0xffffffffU, 1U, 0U, 0U}); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 0U); } TEST_F(GcnTest, addc_u32_result_uses_scc) { auto runner = gcn_test::Runner::instance().value(); const std::array instructions{ SOP2(OpcodeSOP2::S_ADD_U32, SOperand7::S1, SOperand8::S0, SOperand8::S1).Get(), SOP2(OpcodeSOP2::S_ADDC_U32, SOperand7::S0, SOperand8::S2, SOperand8::S3).Get(), VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::S0).Get(), }; const auto spirv = TranslateToSpirv(instructions); auto overflow = runner->run(spirv, std::array{0xffffffffU, 1U, 7U, 0U}); ASSERT_TRUE(overflow.has_value()); EXPECT_EQ(*overflow, 8U); auto no_overflow = runner->run(spirv, std::array{2U, 1U, 7U, 0U}); ASSERT_TRUE(no_overflow.has_value()); EXPECT_EQ(*no_overflow, 7U); } TEST_F(GcnTest, floor_f64_literal_is_high_dword) { auto runner = gcn_test::Runner::instance().value(); if (!runner->supports_float64()) { GTEST_SKIP() << "shaderFloat64 is not supported"; } const u64 floor_literal = VOP1(OpcodeVOP1::V_FLOOR_F64, VOperand8::V0, SOperand9::LiteralConstant).Get() | (u64{0xc0040000U} << 32); const std::array low{floor_literal}; const std::array high{ floor_literal, VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::V1).Get(), }; auto result_lo = runner->run(TranslateToSpirv(low), std::array{0U, 0U, 0U, 0U}); auto result_hi = runner->run(TranslateToSpirv(high), std::array{0U, 0U, 0U, 0U}); ASSERT_TRUE(result_lo.has_value()); ASSERT_TRUE(result_hi.has_value()); EXPECT_EQ(*result_lo, 0U); EXPECT_EQ(*result_hi, 0xc0080000U); }