From f2bfdd26f9e2b0f727d86129412b398d9fdfe54f Mon Sep 17 00:00:00 2001 From: asimplestray <304690168+asimplestray@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:18:08 +0000 Subject: [PATCH] feat(shader): implement SOP1 saveexec/wrexec family --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 8 ++++ core/libs/tests/ScalarSaveexec.cpp | 36 +++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 15 +++++++ .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 15 +++++++ .../RdnaDecoder/src/RdnaScalarOpDecoder.cpp | 21 +++++++++ .../Translation/TranslationContext.hpp | 2 +- .../src/ControlFlowInstructions.cpp | 17 ++++--- .../Translation/src/ScalarInstructions.cpp | 45 +++++++++++++++++++ 8 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 core/libs/tests/ScalarSaveexec.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 38e55c2e2..a88802e2c 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -615,6 +615,14 @@ if(BUILD_TESTING) set_tests_properties(vector_min_max_med16 PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(scalar_saveexec_tests ${LIBS_INCLUDE_DIR}/tests/ScalarSaveexec.cpp) + target_link_libraries(scalar_saveexec_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(scalar_saveexec_tests) + add_test(NAME scalar_saveexec COMMAND scalar_saveexec_tests) + if(WIN32) + set_tests_properties(scalar_saveexec PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_indexed_draw_tests ${LIBS_INCLUDE_DIR}/tests/GuestIndexedDraw.cpp) target_include_directories(guest_indexed_draw_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_indexed_draw_tests PRIVATE libSceAgc libSceAgcDriver libc) diff --git a/core/libs/tests/ScalarSaveexec.cpp b/core/libs/tests/ScalarSaveexec.cpp new file mode 100644 index 000000000..f73c93e79 --- /dev/null +++ b/core/libs/tests/ScalarSaveexec.cpp @@ -0,0 +1,36 @@ +#include "Translation/TranslationContext.hpp" +#include "RdnaDecoder/RdnaScalarOpDecoder.hpp" +#include +#include + +using namespace ShaderRecompiler; +static void Require(bool value) { if (!value) throw std::runtime_error("scalar saveexec regression"); } +static void Check(std::uint32_t encoding, RdnaOpcode opcode) { + const std::array code{0x80000000u | (106u << 16u) | (encoding << 8u) | 10u}; + const RdnaInstruction instruction = DecodeRdnaSop1(0u, code, 0u); + Require(instruction.op == opcode); + Require(instruction.family == RdnaInstructionFamily::SOP1); + Require(IsScalarAluOpcode(instruction.op)); + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); +} +int main() { + Check(0x3du, RdnaOpcode::SOrSaveexecB32); + Check(0x3eu, RdnaOpcode::SXorSaveexecB32); + Check(0x3fu, RdnaOpcode::SAndn2SaveexecB32); + Check(0x41u, RdnaOpcode::SNandSaveexecB32); + Check(0x42u, RdnaOpcode::SNorSaveexecB32); + Check(0x43u, RdnaOpcode::SXnorSaveexecB32); + Check(0x45u, RdnaOpcode::SOrn1SaveexecB32); + Check(0x46u, RdnaOpcode::SAndn1WrexecB32); + Check(0x47u, RdnaOpcode::SAndn2WrexecB32); + Check(0x29u, RdnaOpcode::SNandSaveexecB64); + Check(0x2au, RdnaOpcode::SNorSaveexecB64); + Check(0x2bu, RdnaOpcode::SXnorSaveexecB64); + Check(0x38u, RdnaOpcode::SOrn1SaveexecB64); + Check(0x39u, RdnaOpcode::SAndn1WrexecB64); + Check(0x3au, RdnaOpcode::SAndn2WrexecB64); +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 52440fadb..f825c41ff 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -137,14 +137,29 @@ enum class RdnaOpcode : std::uint16_t { SSubvectorLoopBegin, SSubvectorLoopEnd, SAndSaveexecB32, + SOrSaveexecB32, + SXorSaveexecB32, + SAndn2SaveexecB32, SOrn2SaveexecB32, + SNandSaveexecB32, + SNorSaveexecB32, + SXnorSaveexecB32, SAndn1SaveexecB32, + SOrn1SaveexecB32, + SAndn1WrexecB32, + SAndn2WrexecB32, SAndSaveexecB64, SOrSaveexecB64, SXorSaveexecB64, SAndn2SaveexecB64, SOrn2SaveexecB64, + SNandSaveexecB64, + SNorSaveexecB64, + SXnorSaveexecB64, SAndn1SaveexecB64, + SOrn1SaveexecB64, + SAndn1WrexecB64, + SAndn2WrexecB64, SNotB32, SNotB64, SWqmB32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 032c76070..e491e36af 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -56,14 +56,29 @@ bool IsScalarAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::SBitreplicateB64B32: case RdnaOpcode::SQuadmaskB64: case RdnaOpcode::SAndSaveexecB32: + case RdnaOpcode::SOrSaveexecB32: + case RdnaOpcode::SXorSaveexecB32: + case RdnaOpcode::SAndn2SaveexecB32: case RdnaOpcode::SOrn2SaveexecB32: + case RdnaOpcode::SNandSaveexecB32: + case RdnaOpcode::SNorSaveexecB32: + case RdnaOpcode::SXnorSaveexecB32: case RdnaOpcode::SAndn1SaveexecB32: + case RdnaOpcode::SOrn1SaveexecB32: + case RdnaOpcode::SAndn1WrexecB32: + case RdnaOpcode::SAndn2WrexecB32: case RdnaOpcode::SAndSaveexecB64: case RdnaOpcode::SOrSaveexecB64: case RdnaOpcode::SXorSaveexecB64: case RdnaOpcode::SAndn2SaveexecB64: case RdnaOpcode::SOrn2SaveexecB64: + case RdnaOpcode::SNandSaveexecB64: + case RdnaOpcode::SNorSaveexecB64: + case RdnaOpcode::SXnorSaveexecB64: case RdnaOpcode::SAndn1SaveexecB64: + case RdnaOpcode::SOrn1SaveexecB64: + case RdnaOpcode::SAndn1WrexecB64: + case RdnaOpcode::SAndn2WrexecB64: case RdnaOpcode::SNotB32: case RdnaOpcode::SNotB64: case RdnaOpcode::SWqmB32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp index 4d9491482..df65c3e89 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaScalarOpDecoder.cpp @@ -40,13 +40,28 @@ RdnaOpcode decodeSop1Opcode(std::uint32_t opcode) { case 0x26u: return RdnaOpcode::SXorSaveexecB64; case 0x27u: return RdnaOpcode::SAndn2SaveexecB64; case 0x28u: return RdnaOpcode::SOrn2SaveexecB64; + case 0x29u: return RdnaOpcode::SNandSaveexecB64; + case 0x2au: return RdnaOpcode::SNorSaveexecB64; + case 0x2bu: return RdnaOpcode::SXnorSaveexecB64; case 0x2du: return RdnaOpcode::SQuadmaskB64; case 0x34u: return RdnaOpcode::SAbsI32; case 0x37u: return RdnaOpcode::SAndn1SaveexecB64; + case 0x38u: return RdnaOpcode::SOrn1SaveexecB64; + case 0x39u: return RdnaOpcode::SAndn1WrexecB64; + case 0x3au: return RdnaOpcode::SAndn2WrexecB64; case 0x3bu: return RdnaOpcode::SBitreplicateB64B32; case 0x3cu: return RdnaOpcode::SAndSaveexecB32; + case 0x3du: return RdnaOpcode::SOrSaveexecB32; + case 0x3eu: return RdnaOpcode::SXorSaveexecB32; + case 0x3fu: return RdnaOpcode::SAndn2SaveexecB32; case 0x40u: return RdnaOpcode::SOrn2SaveexecB32; + case 0x41u: return RdnaOpcode::SNandSaveexecB32; + case 0x42u: return RdnaOpcode::SNorSaveexecB32; + case 0x43u: return RdnaOpcode::SXnorSaveexecB32; case 0x44u: return RdnaOpcode::SAndn1SaveexecB32; + case 0x45u: return RdnaOpcode::SOrn1SaveexecB32; + case 0x46u: return RdnaOpcode::SAndn1WrexecB32; + case 0x47u: return RdnaOpcode::SAndn2WrexecB32; default: throw std::invalid_argument("unsupported SOP1 opcode " + std::to_string(opcode)); } } @@ -218,6 +233,12 @@ std::uint32_t scalarDestinationDwordCount(RdnaOpcode opcode) { case RdnaOpcode::SXorSaveexecB64: case RdnaOpcode::SAndn2SaveexecB64: case RdnaOpcode::SOrn2SaveexecB64: + case RdnaOpcode::SNandSaveexecB64: + case RdnaOpcode::SNorSaveexecB64: + case RdnaOpcode::SXnorSaveexecB64: + case RdnaOpcode::SOrn1SaveexecB64: + case RdnaOpcode::SAndn1WrexecB64: + case RdnaOpcode::SAndn2WrexecB64: case RdnaOpcode::SQuadmaskB64: case RdnaOpcode::SAndn1SaveexecB64: case RdnaOpcode::SBitreplicateB64B32: diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index f9b643bf7..dc25013df 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -199,7 +199,7 @@ private: bool vCndmaskB32(const RdnaInstruction& inst); bool packB16(const RdnaInstruction& inst, bool high0, bool high1); void sSubvectorLoop(const RdnaInstruction& inst, bool begin); - void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64); + void sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult = false, bool writeDestination = true); void addU32(const RdnaInstruction& inst, bool vector, bool useCarryIn); void subU32(const RdnaInstruction& inst, bool vector, bool reverse); void subbU32(const RdnaInstruction& inst, bool vector, bool reverse); diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 4159f2b23..b867014ef 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -24,23 +24,25 @@ bool SaveexecWritesDestinationFirst() { return writeFirst; } -void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64) { +void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operation, bool negateExec, bool negateSource, bool write64, bool negateResult, bool writeDestination) { // Callers name the lane-wise operation; the exec mask words themselves combine bitwise. if (operation == IrOpcode::LogicalAnd) operation = IrOpcode::BitwiseAnd32; else if (operation == IrOpcode::LogicalOr) operation = IrOpcode::BitwiseOr32; else if (operation == IrOpcode::LogicalXor) operation = IrOpcode::BitwiseXor32; - const bool writeFirst = SaveexecWritesDestinationFirst(); + const bool writeFirst = SaveexecWritesDestinationFirst() && writeDestination; if (write64) { const std::array oldExec{IrU32(ir.GetExecLo()), IrU32(ir.GetExecHi())}; if (writeFirst) writeU32Pair(inst.destination, oldExec); const std::array source = readU32Pair(sourceAt(inst, 0u)); - if (!writeFirst) writeU32Pair(inst.destination, oldExec); + if (!writeFirst && writeDestination) writeU32Pair(inst.destination, oldExec); IrValue& lowExecOperand = negateExec ? ir.BitwiseNot(oldExec[0].Value()) : oldExec[0].Value(); IrValue& lowSourceOperand = negateSource ? ir.BitwiseNot(source[0].Value()) : source[0].Value(); IrValue& highExecOperand = negateExec ? ir.BitwiseNot(oldExec[1].Value()) : oldExec[1].Value(); IrValue& highSourceOperand = negateSource ? ir.BitwiseNot(source[1].Value()) : source[1].Value(); - const IrU32 newExecLo(ir.Emit(operation, IrType::U32, {&lowExecOperand, &lowSourceOperand})); - const IrU32 newExecHi(ir.Emit(operation, IrType::U32, {&highExecOperand, &highSourceOperand})); + IrValue& lowCombined = ir.Emit(operation, IrType::U32, {&lowExecOperand, &lowSourceOperand}); + IrValue& highCombined = ir.Emit(operation, IrType::U32, {&highExecOperand, &highSourceOperand}); + const IrU32 newExecLo(negateResult ? ir.BitwiseNot(lowCombined) : lowCombined); + const IrU32 newExecHi(negateResult ? ir.BitwiseNot(highCombined) : highCombined); ir.SetExecLo(newExecLo.Value()); ir.SetExecHi(newExecHi.Value()); const IrU1 nonZero(ir.LogicalOr(ir.INotEqual(newExecLo.Value(), ir.Constant(0u)), ir.INotEqual(newExecHi.Value(), ir.Constant(0u)))); @@ -51,10 +53,11 @@ void TranslationContext::sSaveexec(const RdnaInstruction& inst, IrOpcode operati const IrU32 oldExec(ir.GetExecLo()); if (writeFirst) writeRawU32(inst.destination, oldExec); const IrU32 source = readU32(sourceAt(inst, 0u)); - if (!writeFirst) writeRawU32(inst.destination, oldExec); + if (!writeFirst && writeDestination) writeRawU32(inst.destination, oldExec); IrValue& execOperand = negateExec ? ir.BitwiseNot(oldExec.Value()) : oldExec.Value(); IrValue& sourceOperand = negateSource ? ir.BitwiseNot(source.Value()) : source.Value(); - const IrU32 newExec(ir.Emit(operation, IrType::U32, {&execOperand, &sourceOperand})); + IrValue& combined = ir.Emit(operation, IrType::U32, {&execOperand, &sourceOperand}); + const IrU32 newExec(negateResult ? ir.BitwiseNot(combined) : combined); ir.SetExecLo(newExec.Value()); const IrU1 nonZero(ir.INotEqual(newExec.Value(), ir.Constant(0u))); ir.SetScc(nonZero.Value()); diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 1eaf01984..69d5eddb6 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -59,12 +59,39 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { case RdnaOpcode::SAndSaveexecB32: sSaveexec(inst, IrOpcode::LogicalAnd, false, false, false); return true; + case RdnaOpcode::SOrSaveexecB32: + sSaveexec(inst, IrOpcode::LogicalOr, false, false, false); + return true; + case RdnaOpcode::SXorSaveexecB32: + sSaveexec(inst, IrOpcode::LogicalXor, false, false, false); + return true; case RdnaOpcode::SAndn1SaveexecB32: sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false); return true; + case RdnaOpcode::SAndn2SaveexecB32: + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false); + return true; + case RdnaOpcode::SOrn1SaveexecB32: + sSaveexec(inst, IrOpcode::LogicalOr, false, true, false); + return true; case RdnaOpcode::SOrn2SaveexecB32: sSaveexec(inst, IrOpcode::LogicalOr, true, false, false); return true; + case RdnaOpcode::SNandSaveexecB32: + sSaveexec(inst, IrOpcode::LogicalAnd, false, false, false, true); + return true; + case RdnaOpcode::SNorSaveexecB32: + sSaveexec(inst, IrOpcode::LogicalOr, false, false, false, true); + return true; + case RdnaOpcode::SXnorSaveexecB32: + sSaveexec(inst, IrOpcode::LogicalXor, false, false, false, true); + return true; + case RdnaOpcode::SAndn1WrexecB32: + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, false, false, false); + return true; + case RdnaOpcode::SAndn2WrexecB32: + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, false, false, false); + return true; case RdnaOpcode::SAndSaveexecB64: sSaveexec(inst, IrOpcode::LogicalAnd, false, false, true); return true; @@ -83,6 +110,24 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { case RdnaOpcode::SOrn2SaveexecB64: sSaveexec(inst, IrOpcode::LogicalOr, true, false, true); return true; + case RdnaOpcode::SNandSaveexecB64: + sSaveexec(inst, IrOpcode::LogicalAnd, false, false, true, true); + return true; + case RdnaOpcode::SNorSaveexecB64: + sSaveexec(inst, IrOpcode::LogicalOr, false, false, true, true); + return true; + case RdnaOpcode::SXnorSaveexecB64: + sSaveexec(inst, IrOpcode::LogicalXor, false, false, true, true); + return true; + case RdnaOpcode::SOrn1SaveexecB64: + sSaveexec(inst, IrOpcode::LogicalOr, false, true, true); + return true; + case RdnaOpcode::SAndn1WrexecB64: + sSaveexec(inst, IrOpcode::LogicalAnd, false, true, true, false, false); + return true; + case RdnaOpcode::SAndn2WrexecB64: + sSaveexec(inst, IrOpcode::LogicalAnd, true, false, true, false, false); + return true; case RdnaOpcode::SAddU32: addU32(inst, false, false); return true;