feat(shader): implement SOP1 saveexec/wrexec family

This commit is contained in:
asimplestray 2026-10-01 16:18:08 +00:00
parent 709d7fe0fd
commit f2bfdd26f9
8 changed files with 151 additions and 8 deletions

View File

@ -615,6 +615,14 @@ if(BUILD_TESTING)
set_tests_properties(vector_min_max_med16 PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
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:$<TARGET_FILE_DIR:libc>")
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)

View File

@ -0,0 +1,36 @@
#include "Translation/TranslationContext.hpp"
#include "RdnaDecoder/RdnaScalarOpDecoder.hpp"
#include <array>
#include <stdexcept>
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<std::uint32_t, 1> 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);
}

View File

@ -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,

View File

@ -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:

View File

@ -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:

View File

@ -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);

View File

@ -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<IrU32, 2> oldExec{IrU32(ir.GetExecLo()), IrU32(ir.GetExecHi())};
if (writeFirst) writeU32Pair(inst.destination, oldExec);
const std::array<IrU32, 2> 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());

View File

@ -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;