diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index 1708774d76..5e22311ac3 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -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 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(&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 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); + auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id)); + return &(*found); } - TEST(CFG, FpToCFG_IF) { - rsx::simple_array 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 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 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 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 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 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 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 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); } }