rsx/cfg/gtest: Rewrite CFG tests using the assembler

- Also extend IF-ELSE test to catch a broken succession chain
This commit is contained in:
kd-11 2025-12-06 03:34:46 +03:00 committed by kd-11
parent b244c0fa0f
commit 91e19652de

View File

@ -2,89 +2,28 @@
#include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/Assembler/CFG.h"
#include "Emu/RSX/Program/Assembler/FPASM.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <util/v128.hpp>
namespace rsx::assembler
{
auto swap_bytes16 = [](u32 dword) -> u32
static const BasicBlock* get_graph_block_by_id(const FlowGraph& graph, u32 id)
{
// Lazy encode, but good enough for what we need here.
union v32
{
u32 HEX;
u8 _v[4];
};
u8* src_bytes = reinterpret_cast<u8*>(&dword);
v32 dst_bytes;
dst_bytes._v[0] = src_bytes[1];
dst_bytes._v[1] = src_bytes[0];
dst_bytes._v[2] = src_bytes[3];
dst_bytes._v[3] = src_bytes[2];
return dst_bytes.HEX;
};
// Instruction mocks because we don't have a working assember (yet)
auto encode_instruction = [](u32 opcode, bool end = false) -> v128
{
OPDEST dst{};
dst.opcode = opcode;
if (end)
{
dst.end = 1;
auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id));
return &(*found);
}
return v128::from32(swap_bytes16(dst.HEX), 0, 0, 0);
};
auto create_if(u32 end, u32 _else = 0)
{
OPDEST dst{};
dst.opcode = RSX_FP_OPCODE_IFE & 0x3Fu;
SRC1 src1{};
src1.else_offset = (_else ? _else : end) << 2;
src1.opcode_is_branch = 1;
SRC2 src2{};
src2.end_offset = end << 2;
return v128::from32(swap_bytes16(dst.HEX), 0, swap_bytes16(src1.HEX), swap_bytes16(src2.HEX));
};
TEST(CFG, FpToCFG_Basic)
{
rsx::simple_array<v128> buffer = {
encode_instruction(RSX_FP_OPCODE_ADD),
encode_instruction(RSX_FP_OPCODE_MOV, true)
};
RSXFragmentProgram program{};
program.data = buffer.data();
FlowGraph graph = deconstruct_fragment_program(program);
EXPECT_EQ(graph.blocks.size(), 1);
EXPECT_EQ(graph.blocks.front().instructions.size(), 2);
EXPECT_EQ(graph.blocks.front().instructions.front().length, 4);
EXPECT_EQ(graph.blocks.front().instructions[0].addr, 0);
EXPECT_EQ(graph.blocks.front().instructions[1].addr, 16);
}
TEST(CFG, FpToCFG_IF)
{
rsx::simple_array<v128> buffer = {
encode_instruction(RSX_FP_OPCODE_ADD), // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1
create_if(4), // 2 (BR, 4)
encode_instruction(RSX_FP_OPCODE_ADD), // 3
encode_instruction(RSX_FP_OPCODE_MOV, true), // 4 (Merge block)
};
auto ir = FPIR::from_source(R"(
ADD R0, R0, R0;
MOV R1, R0;
IF.LT;
ADD R1, R1, R0;
ENDIF;
MOV R0, R1;
)");
const std::pair<int, size_t> expected_block_data[3] = {
{ 0, 3 }, // Head
@ -93,7 +32,8 @@ namespace rsx::assembler
};
RSXFragmentProgram program{};
program.data = buffer.data();
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
@ -108,24 +48,26 @@ namespace rsx::assembler
}
// Check edges
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 3))->pred[0].type, EdgeType::IF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 4))->pred[0].type, EdgeType::ENDIF);
EXPECT_EQ(get_graph_block_by_id(graph, 3)->pred[0].type, EdgeType::IF);
EXPECT_EQ(get_graph_block_by_id(graph, 0)->succ[0].type, EdgeType::IF);
EXPECT_EQ(get_graph_block_by_id(graph, 4)->pred[0].type, EdgeType::ENDIF);
}
TEST(CFG, FpToCFG_NestedIF)
{
rsx::simple_array<v128> buffer = {
encode_instruction(RSX_FP_OPCODE_ADD), // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1
create_if(8), // 2 (BR, 8)
encode_instruction(RSX_FP_OPCODE_ADD), // 3
create_if(6), // 4 (BR, 6)
encode_instruction(RSX_FP_OPCODE_MOV), // 5
encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block 1)
encode_instruction(RSX_FP_OPCODE_ADD), // 7
encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 (merge block 2
};
auto ir = FPIR::from_source(
"ADD R0, R0, R0;" // 0
"MOV R1, R0;" // 1
"IF.LT;" // 2 (BR, 8)
" ADD R1, R1, R0;" // 3
" IF.GT;" // 4 (BR, 6)
" MOV R3, R0;" // 5
" ENDIF;"
" MOV R2, R3;" // 6 (merge block 1)
" ADD R1, R2, R1;" // 7
"ENDIF;"
"MOV R0, R1;" // 8 (merge block 2
);
const std::pair<int, size_t> expected_block_data[5] = {
{ 0, 3 }, // Head
@ -136,7 +78,8 @@ namespace rsx::assembler
};
RSXFragmentProgram program{};
program.data = buffer.data();
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
@ -153,17 +96,19 @@ namespace rsx::assembler
TEST(CFG, FpToCFG_NestedIF_MultiplePred)
{
rsx::simple_array<v128> buffer = {
encode_instruction(RSX_FP_OPCODE_ADD), // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1
create_if(6), // 2 (BR, 6)
encode_instruction(RSX_FP_OPCODE_ADD), // 3
create_if(6), // 4 (BR, 6)
encode_instruction(RSX_FP_OPCODE_MOV), // 5
encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block)
encode_instruction(RSX_FP_OPCODE_ADD), // 7
encode_instruction(RSX_FP_OPCODE_MOV, true) // 8
};
auto ir = FPIR::from_source(
"ADD R0, R0, R0;" // 0
"MOV R1, R0;" // 1
"IF.LT;" // 2 (BR, 6)
" ADD R1, R1, R0;" // 3
" IF.GT;" // 4 (BR, 6)
" MOV R3, R0;" // 5
" ENDIF;" // ENDIF (4)
"ENDIF;" // ENDIF (2)
"MOV R2, R3;" // 6 (merge block, unified)
"ADD R1, R2, R1;" // 7
"MOV R0, R1;" // 8
);
const std::pair<int, size_t> expected_block_data[4] = {
{ 0, 3 }, // Head
@ -173,7 +118,8 @@ namespace rsx::assembler
};
RSXFragmentProgram program{};
program.data = buffer.data();
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
@ -187,32 +133,40 @@ namespace rsx::assembler
EXPECT_EQ(it->instructions.size(), expected.second);
}
const BasicBlock
*bb0 = get_graph_block_by_id(graph, 0),
*bb6 = get_graph_block_by_id(graph, 6);
// Predecessors must be ordered, closest first
ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred.size(), 2);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].type, EdgeType::ENDIF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].from->id, 3);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].type, EdgeType::ENDIF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].from->id, 0);
ASSERT_EQ(bb6->pred.size(), 3);
EXPECT_EQ(bb6->pred[0].type, EdgeType::ENDIF);
EXPECT_EQ(bb6->pred[0].from->id, 5);
EXPECT_EQ(bb6->pred[1].type, EdgeType::ENDIF);
EXPECT_EQ(bb6->pred[1].from->id, 3);
EXPECT_EQ(bb6->pred[2].type, EdgeType::ENDIF);
EXPECT_EQ(bb6->pred[2].from->id, 0);
// Successors must also be ordered, closest first
ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ.size(), 2);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].to->id, 3);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].type, EdgeType::ENDIF);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].to->id, 6);
ASSERT_EQ(bb0->succ.size(), 2);
EXPECT_EQ(bb0->succ[0].type, EdgeType::IF);
EXPECT_EQ(bb0->succ[0].to->id, 3);
EXPECT_EQ(bb0->succ[1].type, EdgeType::ENDIF);
EXPECT_EQ(bb0->succ[1].to->id, 6);
}
TEST(CFG, FpToCFG_IF_ELSE)
{
rsx::simple_array<v128> buffer = {
encode_instruction(RSX_FP_OPCODE_ADD), // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1
create_if(6, 4), // 2 (BR, 6)
encode_instruction(RSX_FP_OPCODE_ADD), // 3
encode_instruction(RSX_FP_OPCODE_MOV), // 4 (Else)
encode_instruction(RSX_FP_OPCODE_ADD), // 5
encode_instruction(RSX_FP_OPCODE_MOV, true), // 6 (Merge)
};
auto ir = FPIR::from_source(
"ADD R0, R0, R0;" // 0
"MOV R1, R0;" // 1
"IF.LT;" // 2 (BR, 6)
" ADD R1, R1, R0;" // 3
"ELSE;" // ELSE (2)
" MOV R2, R3;" // 4
" ADD R1, R2, R1;" // 5
"ENDIF;" // ENDIF (2)
"MOV R0, R1;" // 6 (merge)
);
const std::pair<int, size_t> expected_block_data[4] = {
{ 0, 3 }, // Head
@ -222,7 +176,8 @@ namespace rsx::assembler
};
RSXFragmentProgram program{};
program.data = buffer.data();
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
@ -235,5 +190,24 @@ namespace rsx::assembler
EXPECT_EQ(it->id, expected.first);
EXPECT_EQ(it->instructions.size(), expected.second);
}
// The IF and ELSE branches don't link to each other directly. Their predecessor should point to both and they both point to the merge.
const BasicBlock
*bb0 = get_graph_block_by_id(graph, 0),
*bb3 = get_graph_block_by_id(graph, 3),
*bb4 = get_graph_block_by_id(graph, 4),
*bb6 = get_graph_block_by_id(graph, 6);
EXPECT_EQ(bb0->succ.size(), 3);
EXPECT_EQ(bb3->succ.size(), 1);
EXPECT_EQ(bb4->succ.size(), 1);
EXPECT_EQ(bb3->succ.front().to, bb6);
EXPECT_EQ(bb4->succ.front().to, bb6);
EXPECT_EQ(bb6->pred.size(), 3);
EXPECT_EQ(bb6->pred[0].from, bb4);
EXPECT_EQ(bb6->pred[1].from, bb3);
EXPECT_EQ(bb6->pred[2].from, bb0);
}
}