From 67f69bb4b3243a3e89c7fb0a89e5da8dc1c41d4c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Mar 2026 01:06:42 +0300 Subject: [PATCH] rsx/cfg: Fix handling of instructions with literal input - The hardware is pretty dumb about it, it just unconditionally skips the next instruction --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 8 +++++++ rpcs3/Emu/RSX/Program/ProgramStateCache.cpp | 3 +-- rpcs3/tests/test_rsx_cfg.cpp | 25 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index c6d092c92a..0178ba5efb 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -165,6 +165,14 @@ namespace rsx::assembler if (opcode == RSX_FP_OPCODE_NOP) { + if (includes_literal_constant()) + { + // Verified behavior on real hardware + // If any input on a non-flow-control instruction is of literal type the next instruction is assumed to be data + // You can actually use this behavior to mask off instructions completely + pc++; + } + pc++; continue; } diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index 528b5c6316..f1c2d49097 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -679,8 +679,7 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys break; } - if (rsx::assembler::FP::get_operand_count(opcode) > 0 && - is_any_src_constant(inst)) + if (is_any_src_constant(inst)) { // Instruction references constant, skip one slot occupied by data index++; diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index a20b3a7d0a..81027e61db 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -251,4 +251,29 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_gr, 1); EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_eq, 1); } + + TEST(CFG, FpToCFG_SkipOverImmediateOperand) + { + auto ir = FPIR::from_source( + "MOV R0, #{ 0.25 };" // NOP with real dst and one literal input + "MOV R0, R1;" // False merge block. + ); + + RSXFragmentProgram program{}; + auto bytecode = ir.compile(); + program.data = bytecode.data(); + + ASSERT_EQ(bytecode.size(), 12); + + // Patch the first instruction to be a NOP with a literal as input + const u32 decoded_d0 = ((bytecode[0] & 0xFF00FF00u) >> 16u) | ((bytecode[0] & 0x00FF00FFu) << 16u); + OPDEST d0{ .HEX = decoded_d0 }; + d0.opcode = RSX_FP_OPCODE_NOP; + bytecode[0] = ((d0.HEX & 0xFF00FF00u) >> 16u) | ((d0.HEX & 0x00FF00FFu) << 16u); + + FlowGraph graph = deconstruct_fragment_program(program); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 1); + } }