Files
shadPS4/tests/gcn/test_gcn_instructions.cpp
T

813 lines
31 KiB
C++

// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include <unordered_map>
#include <utility>
#include <gtest/gtest.h>
#include <half.hpp>
#include <spirv/unified1/spirv.hpp11>
#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<u32>& spirv) {
FrontFaceSpirvInfo info{};
if (spirv.size() < 5U) {
ADD_FAILURE() << "SPIR-V header is truncated";
return info;
}
std::unordered_map<u32, u32> 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<spv::Op>(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<u32>(spv::Decoration::BuiltIn) &&
spirv[offset + 3] == static_cast<u32>(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<u32>& 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<spv::Op>(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<u32>(spv::Capability::FragmentBarycentricKHR)) {
++info.fragment_barycentric_khr_count;
} else if (opcode == spv::Op::OpDecorate && word_count >= 4U &&
spirv[offset + 2] == static_cast<u32>(spv::Decoration::BuiltIn)) {
const auto builtin = static_cast<spv::BuiltIn>(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<u32>{1U, 0U}));
EXPECT_EQ(p10.fsub_count, 1U);
const auto p20 = TranslateFragmentInterpMovSelector(1, false, false);
EXPECT_EQ(p20.attribute_indices, (std::vector<u32>{2U, 0U}));
EXPECT_EQ(p20.fsub_count, 1U);
const auto p0 = TranslateFragmentInterpMovSelector(2, false, false);
EXPECT_EQ(p0.attribute_indices, (std::vector<u32>{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<u32>{1U, 0U}));
EXPECT_EQ(p10.fsub_count, 1U);
}
const auto p10 = TranslateFragmentInterpMovSelector(0, true, true);
EXPECT_EQ(p10.attribute_indices, (std::vector<u32>{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<T> 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<float>(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<float>(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<u64, 2> 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<u32>(spirv, std::array{0xffffffffU, 1U, 7U, 0U});
ASSERT_TRUE(overflow.has_value());
EXPECT_EQ(*overflow, 8U);
const auto no_overflow = runner->run<u32>(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<float>(spirv, F32x2{1.0f, std::numeric_limits<float>::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<F16x2>(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<F16x2>(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<F16x2>(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<F16x2>(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<F16x2>(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<u32>(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<float>(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<u32>(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<float>(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<u32>(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<u32>(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<u32>(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<u32>(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<u32>(spirv, std::array<u32,3>{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<u32>(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<u32>(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<u32>(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<u32>(spirv, std::array<u32,3>{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<u32>(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<u32>(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<u32>(spirv, std::array<u32,3>{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<u32>(spirv, std::array<u32,3>{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<u32>(spirv, std::array<u32,3>{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<u32>(spirv, std::array<u32,3>{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<u32>(spirv, std::array<u32,3>{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<float>(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<float>(spirv, std::array<u32,3>{
std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(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<F16x2>(spirv, std::array<u32,3>{
std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(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<F16x2>(spirv, std::array<u32,3>{
std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(F16x2{half(44.0f), half(0.5f)}), std::bit_cast<u32>(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<u32>(spirv, std::array<u32,2>{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<u32>(spirv, std::array<u32,2>{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<u32>(spirv, std::array<u32,2>{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<u32>(spirv, std::array<u32,2>{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<u32>(spirv, std::array<u32,2>{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<F16x2>(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<u32>(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<u32>(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<u32>(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<u32>(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<F16x2>(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<F16x2>(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<F16x2>(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<u64, 3> 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<u32>(spirv, std::array{0x80000000U, 0U, 0U, 0U});
ASSERT_TRUE(overflow.has_value());
EXPECT_EQ(*overflow, 1U);
auto no_overflow = runner->run<u32>(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<u64, 3> 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<u32>(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<u64, 3> 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<u32>(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<u64, 4> 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<u32>(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<u64, 4> 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<u32>(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<u64, 3> 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<u32>(spirv, std::array{0xffffffffU, 1U, 7U, 0U});
ASSERT_TRUE(overflow.has_value());
EXPECT_EQ(*overflow, 8U);
auto no_overflow = runner->run<u32>(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<u64, 1> low{floor_literal};
const std::array<u64, 2> high{
floor_literal,
VOP1(OpcodeVOP1::V_MOV_B32, VOperand8::V0, SOperand9::V1).Get(),
};
auto result_lo = runner->run<u32>(TranslateToSpirv(low), std::array{0U, 0U, 0U, 0U});
auto result_hi = runner->run<u32>(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);
}