rsx: Add UTs for register annotation pass and fix uncovered bugs

This commit is contained in:
kd-11 2025-11-30 21:46:46 +03:00 committed by kd-11
parent 88a54e2d98
commit aaaa6feb5a
5 changed files with 123 additions and 10 deletions

View File

@ -223,21 +223,48 @@ namespace rsx::assembler
auto encode_opcode = [](const std::string& op, Instruction* inst)
{
OPDEST d0 { .HEX = inst->bytecode[0] };
SRC0 s0 { .HEX = inst->bytecode[1] };
#define SET_OPCODE(code) \
do { \
inst->opcode = d0.opcode = code; \
s0.exec_if_eq = s0.exec_if_gr = s0.exec_if_lt = 1; \
inst->bytecode[0] = d0.HEX; \
inst->bytecode[1] = s0.HEX; \
} while (0)
if (op == "MOV")
{
inst->opcode = d0.opcode = RSX_FP_OPCODE_MOV;
inst->bytecode[0] = d0.HEX;
SET_OPCODE(RSX_FP_OPCODE_MOV);
return;
}
if (op == "ADD")
{
inst->opcode = d0.opcode = RSX_FP_OPCODE_ADD;
inst->bytecode[0] = d0.HEX;
SET_OPCODE(RSX_FP_OPCODE_ADD);
return;
}
if (op == "MAD" || op == "FMA")
{
SET_OPCODE(RSX_FP_OPCODE_MAD);
return;
}
if (op == "UP4S")
{
SET_OPCODE(RSX_FP_OPCODE_UP4);
return;
}
if (op == "PK4S")
{
SET_OPCODE(RSX_FP_OPCODE_PK4);
return;
}
#undef SET_OPCODE
fmt::throw_exception("Unhandled instruction '%s'", op);
};
@ -262,6 +289,7 @@ namespace rsx::assembler
Instruction* target = &ir.m_instructions.back();
encode_opcode(op, target);
ensure(sources.size() == FP::get_operand_count(static_cast<FP_opcode>(target->opcode)), "Invalid operand count for opcode");
if (dst.empty())
{

View File

@ -15,6 +15,7 @@ namespace rsx::assembler::FP
case RSX_FP_OPCODE_NOP:
return 0;
case RSX_FP_OPCODE_MOV:
return 1;
case RSX_FP_OPCODE_MUL:
case RSX_FP_OPCODE_ADD:
return 2;
@ -290,8 +291,8 @@ namespace rsx::assembler::FP
u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
{
// Brute-force this. There's only 16 permutations.
constexpr u32 x = 0b0000;
constexpr u32 y = 0b0000;
constexpr u32 x = 0b0001;
constexpr u32 y = 0b0010;
constexpr u32 z = 0b0100;
constexpr u32 w = 0b1000;

View File

@ -10,6 +10,16 @@ namespace rsx::assembler
{
int id = 0;
bool f16 = false;
bool operator == (const Register& other) const
{
return id == other.id && f16 == other.f16;
}
std::string to_string() const
{
return std::string(f16 ? "H" : "R") + std::to_string(id);
}
};
struct RegisterRef
@ -34,6 +44,11 @@ namespace rsx::assembler
{
return !!mask;
}
bool operator == (const RegisterRef& other) const
{
return reg == other.reg && mask == other.mask;
}
};
struct Instruction

View File

@ -9,7 +9,7 @@ namespace rsx::assembler::FP
{
static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers
static constexpr char content_unknown = 0;
static constexpr char content_float32 = 'F';
static constexpr char content_float32 = 'R';
static constexpr char content_float16 = 'H';
static constexpr char content_dual = 'D';
@ -125,7 +125,7 @@ namespace rsx::assembler::FP
for (const auto& src : instruction.srcs)
{
const auto read_bytes = get_register_file_range(src);
const char expected_type = src.reg.f16 ? content_float16 : content_float16;
const char expected_type = src.reg.f16 ? content_float16 : content_float32;
for (const auto& index : read_bytes)
{
if (output_register_file[index] != content_unknown)
@ -156,7 +156,7 @@ namespace rsx::assembler::FP
{
const auto& dst = instruction.dsts.front();
const auto write_bytes = get_register_file_range(dst);
const char expected_type = dst.reg.f16 ? content_float16 : content_float16;
const char expected_type = dst.reg.f16 ? content_float16 : content_float32;
for (const auto& index : write_bytes)
{

View File

@ -2,15 +2,56 @@
#include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/Assembler/FPASM.h"
#include "Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
namespace rsx::assembler
{
#define DECLARE_REG32(num)\
Register R##num{ .id = num, .f16 = false }
#define DECLARE_REG16(num)\
Register H##num{ .id = num, .f16 = true }
DECLARE_REG32(0);
DECLARE_REG32(1);
DECLARE_REG32(2);
DECLARE_REG32(3);
DECLARE_REG32(4);
DECLARE_REG32(5);
DECLARE_REG32(6);
DECLARE_REG32(7);
DECLARE_REG32(8);
DECLARE_REG16(0);
DECLARE_REG16(1);
DECLARE_REG16(2);
DECLARE_REG16(3);
DECLARE_REG16(4);
DECLARE_REG16(5);
DECLARE_REG16(6);
DECLARE_REG16(7);
DECLARE_REG16(8);
#undef DECLARE_REG32
#undef DECLARE_REG16
static FlowGraph CFG_from_source(const std::string& asm_)
{
auto ir = FPIR::from_source(asm_);
FlowGraph graph{};
graph.blocks.push_back({});
auto& bb = graph.blocks.back();
bb.instructions = ir.build();
return graph;
}
TEST(TestFPIR, FromSource)
{
auto ir = FPIR::from_source(R"(
MOV R0, #{ 0.125 };
ADD R1, R0;
ADD R1, R0, R0;
)");
const auto instructions = ir.build();
@ -30,4 +71,32 @@ namespace rsx::assembler
EXPECT_EQ(SRC0{ .HEX = instructions[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP);
EXPECT_EQ(instructions[1].length, 4);
}
TEST(TestFPIR, RegisterAnnotationPass)
{
// Code snippet reads from R0 and R2, clobbers R0, R1, H0
auto graph = CFG_from_source(R"(
ADD R1, R0, R1;
MOV H0, H4;
)");
ASSERT_EQ(graph.blocks.size(), 1);
ASSERT_EQ(graph.blocks.front().instructions.size(), 2);
auto& block = graph.blocks.front();
RSXFragmentProgram prog{};
FP::RegisterAnnotationPass annotation_pass(prog);
annotation_pass.run(graph);
ASSERT_EQ(block.clobber_list.size(), 2);
ASSERT_EQ(block.input_list.size(), 3);
EXPECT_EQ(block.clobber_list[0].reg, H0);
EXPECT_EQ(block.clobber_list[1].reg, R1);
EXPECT_EQ(block.input_list[0].reg, H4);
EXPECT_EQ(block.input_list[1].reg, R0);
EXPECT_EQ(block.input_list[2].reg, R1);
}
}