rpcs3/rpcs3/tests/test_rsx_cfg.cpp

231 lines
6.4 KiB
C++

#include <gtest/gtest.h>
#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
{
static const BasicBlock* get_graph_block_by_id(const FlowGraph& graph, u32 id)
{
auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id));
return &(*found);
}
TEST(CFG, FpToCFG_IF)
{
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
{ 3, 1 }, // Branch
{ 4, 1 }, // Merge
};
RSXFragmentProgram program{};
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
ASSERT_EQ(graph.blocks.size(), 3);
int i = 0;
for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it)
{
const auto& expected = expected_block_data[i++];
EXPECT_EQ(it->id, expected.first);
EXPECT_EQ(it->instructions.size(), expected.second);
}
// Check edges
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)
{
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
{ 3, 2 }, // Branch 1
{ 5, 1 }, // Branch 2
{ 6, 2 }, // Merge 1
{ 8, 1 }, // Merge 2
};
RSXFragmentProgram program{};
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
ASSERT_EQ(graph.blocks.size(), 5);
int i = 0;
for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it)
{
const auto& expected = expected_block_data[i++];
EXPECT_EQ(it->id, expected.first);
EXPECT_EQ(it->instructions.size(), expected.second);
}
}
TEST(CFG, FpToCFG_NestedIF_MultiplePred)
{
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
{ 3, 2 }, // Branch 1
{ 5, 1 }, // Branch 2
{ 6, 3 }, // Merge
};
RSXFragmentProgram program{};
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
ASSERT_EQ(graph.blocks.size(), 4);
int i = 0;
for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it)
{
const auto& expected = expected_block_data[i++];
EXPECT_EQ(it->id, expected.first);
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(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(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)
{
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
{ 3, 1 }, // Branch positive
{ 4, 2 }, // Branch negative
{ 6, 1 }, // Merge
};
RSXFragmentProgram program{};
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
ASSERT_EQ(graph.blocks.size(), 4);
int i = 0;
for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it)
{
const auto& expected = expected_block_data[i++];
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);
}
TEST(CFG, FpToCFG_EmptyIF)
{
auto ir = FPIR::from_source(
"IF.LT;" // Empty branch
"ENDIF;"
"MOV R0, R1;" // False merge block.
);
RSXFragmentProgram program{};
auto bytecode = ir.compile();
program.data = bytecode.data();
FlowGraph graph = deconstruct_fragment_program(program);
ASSERT_EQ(graph.blocks.size(), 1);
EXPECT_EQ(graph.blocks.front().instructions.size(), 1);
}
}