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/Common/simple_array.hpp"
#include "Emu/RSX/Program/Assembler/CFG.h" #include "Emu/RSX/Program/Assembler/CFG.h"
#include "Emu/RSX/Program/Assembler/FPASM.h"
#include "Emu/RSX/Program/RSXFragmentProgram.h" #include "Emu/RSX/Program/RSXFragmentProgram.h"
#include <util/v128.hpp> #include <util/v128.hpp>
namespace rsx::assembler 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. auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id));
union v32 return &(*found);
{
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;
}
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) TEST(CFG, FpToCFG_IF)
{ {
rsx::simple_array<v128> buffer = { auto ir = FPIR::from_source(R"(
encode_instruction(RSX_FP_OPCODE_ADD), // 0 ADD R0, R0, R0;
encode_instruction(RSX_FP_OPCODE_MOV), // 1 MOV R1, R0;
create_if(4), // 2 (BR, 4) IF.LT;
encode_instruction(RSX_FP_OPCODE_ADD), // 3 ADD R1, R1, R0;
encode_instruction(RSX_FP_OPCODE_MOV, true), // 4 (Merge block) ENDIF;
}; MOV R0, R1;
)");
const std::pair<int, size_t> expected_block_data[3] = { const std::pair<int, size_t> expected_block_data[3] = {
{ 0, 3 }, // Head { 0, 3 }, // Head
@ -93,7 +32,8 @@ namespace rsx::assembler
}; };
RSXFragmentProgram program{}; RSXFragmentProgram program{};
program.data = buffer.data(); auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program); FlowGraph graph = deconstruct_fragment_program(program);
@ -108,24 +48,26 @@ namespace rsx::assembler
} }
// Check edges // Check edges
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 3))->pred[0].type, EdgeType::IF); EXPECT_EQ(get_graph_block_by_id(graph, 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(get_graph_block_by_id(graph, 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, 4)->pred[0].type, EdgeType::ENDIF);
} }
TEST(CFG, FpToCFG_NestedIF) TEST(CFG, FpToCFG_NestedIF)
{ {
rsx::simple_array<v128> buffer = { auto ir = FPIR::from_source(
encode_instruction(RSX_FP_OPCODE_ADD), // 0 "ADD R0, R0, R0;" // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1 "MOV R1, R0;" // 1
create_if(8), // 2 (BR, 8) "IF.LT;" // 2 (BR, 8)
encode_instruction(RSX_FP_OPCODE_ADD), // 3 " ADD R1, R1, R0;" // 3
create_if(6), // 4 (BR, 6) " IF.GT;" // 4 (BR, 6)
encode_instruction(RSX_FP_OPCODE_MOV), // 5 " MOV R3, R0;" // 5
encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block 1) " ENDIF;"
encode_instruction(RSX_FP_OPCODE_ADD), // 7 " MOV R2, R3;" // 6 (merge block 1)
encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 (merge block 2 " ADD R1, R2, R1;" // 7
}; "ENDIF;"
"MOV R0, R1;" // 8 (merge block 2
);
const std::pair<int, size_t> expected_block_data[5] = { const std::pair<int, size_t> expected_block_data[5] = {
{ 0, 3 }, // Head { 0, 3 }, // Head
@ -136,7 +78,8 @@ namespace rsx::assembler
}; };
RSXFragmentProgram program{}; RSXFragmentProgram program{};
program.data = buffer.data(); auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program); FlowGraph graph = deconstruct_fragment_program(program);
@ -153,17 +96,19 @@ namespace rsx::assembler
TEST(CFG, FpToCFG_NestedIF_MultiplePred) TEST(CFG, FpToCFG_NestedIF_MultiplePred)
{ {
rsx::simple_array<v128> buffer = { auto ir = FPIR::from_source(
encode_instruction(RSX_FP_OPCODE_ADD), // 0 "ADD R0, R0, R0;" // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1 "MOV R1, R0;" // 1
create_if(6), // 2 (BR, 6) "IF.LT;" // 2 (BR, 6)
encode_instruction(RSX_FP_OPCODE_ADD), // 3 " ADD R1, R1, R0;" // 3
create_if(6), // 4 (BR, 6) " IF.GT;" // 4 (BR, 6)
encode_instruction(RSX_FP_OPCODE_MOV), // 5 " MOV R3, R0;" // 5
encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block) " ENDIF;" // ENDIF (4)
encode_instruction(RSX_FP_OPCODE_ADD), // 7 "ENDIF;" // ENDIF (2)
encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 "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] = { const std::pair<int, size_t> expected_block_data[4] = {
{ 0, 3 }, // Head { 0, 3 }, // Head
@ -173,7 +118,8 @@ namespace rsx::assembler
}; };
RSXFragmentProgram program{}; RSXFragmentProgram program{};
program.data = buffer.data(); auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program); FlowGraph graph = deconstruct_fragment_program(program);
@ -187,32 +133,40 @@ namespace rsx::assembler
EXPECT_EQ(it->instructions.size(), expected.second); 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 // Predecessors must be ordered, closest first
ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred.size(), 2); ASSERT_EQ(bb6->pred.size(), 3);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].type, EdgeType::ENDIF); EXPECT_EQ(bb6->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(bb6->pred[0].from->id, 5);
EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].type, EdgeType::ENDIF); EXPECT_EQ(bb6->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); 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 // 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); ASSERT_EQ(bb0->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(bb0->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(bb0->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(bb0->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); EXPECT_EQ(bb0->succ[1].to->id, 6);
} }
TEST(CFG, FpToCFG_IF_ELSE) TEST(CFG, FpToCFG_IF_ELSE)
{ {
rsx::simple_array<v128> buffer = { auto ir = FPIR::from_source(
encode_instruction(RSX_FP_OPCODE_ADD), // 0 "ADD R0, R0, R0;" // 0
encode_instruction(RSX_FP_OPCODE_MOV), // 1 "MOV R1, R0;" // 1
create_if(6, 4), // 2 (BR, 6) "IF.LT;" // 2 (BR, 6)
encode_instruction(RSX_FP_OPCODE_ADD), // 3 " ADD R1, R1, R0;" // 3
encode_instruction(RSX_FP_OPCODE_MOV), // 4 (Else) "ELSE;" // ELSE (2)
encode_instruction(RSX_FP_OPCODE_ADD), // 5 " MOV R2, R3;" // 4
encode_instruction(RSX_FP_OPCODE_MOV, true), // 6 (Merge) " ADD R1, R2, R1;" // 5
}; "ENDIF;" // ENDIF (2)
"MOV R0, R1;" // 6 (merge)
);
const std::pair<int, size_t> expected_block_data[4] = { const std::pair<int, size_t> expected_block_data[4] = {
{ 0, 3 }, // Head { 0, 3 }, // Head
@ -222,7 +176,8 @@ namespace rsx::assembler
}; };
RSXFragmentProgram program{}; RSXFragmentProgram program{};
program.data = buffer.data(); auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program); FlowGraph graph = deconstruct_fragment_program(program);
@ -235,5 +190,24 @@ namespace rsx::assembler
EXPECT_EQ(it->id, expected.first); EXPECT_EQ(it->id, expected.first);
EXPECT_EQ(it->instructions.size(), expected.second); 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);
} }
} }