rsx/cfg: Add support for multi-slot instructions with literals

- Also fix pred edge direction bug
This commit is contained in:
kd-11 2025-11-23 21:11:05 +03:00 committed by kd-11
parent 42d9065c11
commit 9d92e190eb
3 changed files with 52 additions and 10 deletions

View file

@ -1,17 +1,14 @@
#include "stdafx.h" #include "stdafx.h"
#pragma optimize("", off)
#include "CFG.h" #include "CFG.h"
#include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Common/simple_array.hpp"
#include "Emu/RSX/Program/RSXFragmentProgram.h" #include "Emu/RSX/Program/RSXFragmentProgram.h"
#include "Emu/RSX/Program/ProgramStateCache.h"
#include <util/asm.hpp> #include <util/asm.hpp>
#include <util/v128.hpp>
#include <span> #include <span>
using namespace program_hash_util;
namespace rsx::assembler namespace rsx::assembler
{ {
@ -75,6 +72,13 @@ namespace rsx::assembler
return graph.push(parent, id, edge_type); return graph.push(parent, id, edge_type);
}; };
auto includes_literal_constant = [&]()
{
return src0.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT ||
src1.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT ||
src2.reg_type == RSX_FP_REGISTER_TYPE_CONSTANT;
};
while (!end) while (!end)
{ {
BasicBlock** found = end_blocks.find_if(FN(x->id == pc)); BasicBlock** found = end_blocks.find_if(FN(x->id == pc));
@ -110,13 +114,21 @@ namespace rsx::assembler
bb->instructions.push_back({}); bb->instructions.push_back({});
auto& ir_inst = bb->instructions.back(); auto& ir_inst = bb->instructions.back();
std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16); std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16);
ir_inst.length = 4;
ir_inst.addr = pc * 16;
switch (opcode) switch (opcode)
{ {
case RSX_FP_OPCODE_BRK:
break;
case RSX_FP_OPCODE_CAL: case RSX_FP_OPCODE_CAL:
// Unimplemented. Also unused by the RSX compiler // Unimplemented. Also unused by the RSX compiler
fmt::throw_exception("Unimplemented FP CAL instruction."); fmt::throw_exception("Unimplemented FP CAL instruction.");
break; break;
case RSX_FP_OPCODE_FENCT:
break;
case RSX_FP_OPCODE_FENCB:
break;
case RSX_FP_OPCODE_RET: case RSX_FP_OPCODE_RET:
// Outside a subroutine, this doesn't mean much. The main block can conditionally return to stop execution early. // Outside a subroutine, this doesn't mean much. The main block can conditionally return to stop execution early.
// This will not alter flow control. // This will not alter flow control.
@ -143,8 +155,13 @@ namespace rsx::assembler
break; break;
} }
default: default:
if (fragment_program_utils::is_any_src_constant(decoded)) if (includes_literal_constant())
{ {
const v128 constant_literal = v128::loadu(data, pc);
v128 decoded_literal = decode_instruction(constant_literal);
std::memcpy(ir_inst.bytecode + 4, &decoded_literal._u32[0], 16);
ir_inst.length += 4;
pc++; pc++;
} }
} }
@ -152,6 +169,14 @@ namespace rsx::assembler
pc++; pc++;
} }
// Sort edges for each block by distance
for (auto& block : graph.blocks)
{
std::sort(block.pred.begin(), block.pred.end(), FN(x.from->id > y.from->id));
std::sort(block.succ.begin(), block.succ.end(), FN(x.to->id < y.to->id));
}
// Sort block nodes by distance
graph.blocks.sort(FN(x.id < y.id)); graph.blocks.sort(FN(x.id < y.id));
return graph; return graph;
} }

View file

@ -33,11 +33,20 @@ namespace rsx::assembler
struct Instruction struct Instruction
{ {
// Raw data. Every instruction is max 128 bits // Raw data. Every instruction is max 128 bits.
u32 bytecode[4]; // Each instruction can also have 128 bits of literal/embedded data.
u32 bytecode[8]{ {} };
u32 addr = 0;
// Decoded // Decoded
u32 opcode = 0; u32 opcode = 0;
u8 length = 4; // Length in dwords
// Padding
u8 reserved0 = 0;
u16 reserved1 = 0;
// References
std::vector<RegisterRef> srcs; std::vector<RegisterRef> srcs;
std::vector<RegisterRef> dsts; std::vector<RegisterRef> dsts;
}; };
@ -49,7 +58,7 @@ namespace rsx::assembler
ELSE, ELSE,
ENDIF, ENDIF,
LOOP, LOOP,
ENDLOOP ENDLOOP,
}; };
struct FlowEdge struct FlowEdge
@ -78,7 +87,7 @@ namespace rsx::assembler
FlowEdge* insert_pred(BasicBlock* b, EdgeType type = EdgeType::NONE) FlowEdge* insert_pred(BasicBlock* b, EdgeType type = EdgeType::NONE)
{ {
FlowEdge e{ .type = type, .from = this, .to = b }; FlowEdge e{ .type = type, .from = b, .to = this };
pred.push_back(e); pred.push_back(e);
return &pred.back(); return &pred.back();
} }

View file

@ -1,4 +1,3 @@
#pragma optimize("", off)
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Common/simple_array.hpp"
@ -72,6 +71,8 @@ namespace rsx::assembler
EXPECT_EQ(graph.blocks.size(), 1); EXPECT_EQ(graph.blocks.size(), 1);
EXPECT_EQ(graph.blocks.front().instructions.size(), 2); EXPECT_EQ(graph.blocks.front().instructions.size(), 2);
EXPECT_EQ(graph.blocks.front().instructions.front().length, 4);
EXPECT_NE(graph.blocks.front().instructions.front().addr, 0);
} }
TEST(CFG, FpToCFG_IF) TEST(CFG, FpToCFG_IF)
@ -184,6 +185,13 @@ 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);
} }
// 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);
} }
TEST(CFG, FpToCFG_IF_ELSE) TEST(CFG, FpToCFG_IF_ELSE)