From e2974fc4dbc9d94231152213f81f085b520a06de Mon Sep 17 00:00:00 2001 From: Kikoarl <7354010+Kikoarl@users.noreply.github.com> Date: Tue, 6 Oct 2026 00:12:39 +0200 Subject: [PATCH 1/2] feat(shader): implement S_SCRATCH_LOAD_DWORD, S_SCRATCH_LOAD_DWORDX2 and S_SCRATCH_LOAD_DWORDX4 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 8 ++++ core/libs/tests/ScalarScratchLoad.cpp | 39 +++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 3 ++ .../RdnaDecoder/src/RdnaMemoryOpDecoder.cpp | 3 ++ .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 3 ++ .../Translation/TranslationContext.hpp | 1 + .../MemoryInstructionDispatch.cpp | 4 ++ .../ScalarBufferInstructions.cpp | 28 +++++++++++++ 8 files changed, 89 insertions(+) create mode 100644 core/libs/tests/ScalarScratchLoad.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index b373847f0..4cf067779 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -750,6 +750,14 @@ if(BUILD_TESTING) set_tests_properties(scalar_bitcount64 PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(scalar_scratch_load_tests ${LIBS_INCLUDE_DIR}/tests/ScalarScratchLoad.cpp) + target_link_libraries(scalar_scratch_load_tests PRIVATE shader_recompiler_agc_driver libc) + configure_windows_unwind(scalar_scratch_load_tests) + add_test(NAME scalar_scratch_load COMMAND scalar_scratch_load_tests) + if(WIN32) + set_tests_properties(scalar_scratch_load PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(image_sample_gather_tests ${LIBS_INCLUDE_DIR}/tests/ImageSampleGather.cpp) target_link_libraries(image_sample_gather_tests PRIVATE shader_recompiler_agc_driver libc) configure_windows_unwind(image_sample_gather_tests) diff --git a/core/libs/tests/ScalarScratchLoad.cpp b/core/libs/tests/ScalarScratchLoad.cpp new file mode 100644 index 000000000..5e1e4d8b7 --- /dev/null +++ b/core/libs/tests/ScalarScratchLoad.cpp @@ -0,0 +1,39 @@ +#include "Translation/TranslationContext.hpp" +#include "RdnaDecoder/RdnaMemoryOpDecoder.hpp" +#include +#include + +using namespace ShaderRecompiler; +static void Require(bool value) { if (!value) throw std::runtime_error("scalar scratch load regression"); } +static void Check(std::uint32_t opcode, RdnaOpcode expected, std::uint32_t dwords) { + const std::array code{ + (0x3du << 26u) | (opcode << 18u) | (2u << 6u) | 1u, + (125u << 25u) | 0x10u + }; + const RdnaInstruction instruction = DecodeRdnaSmem(0u, code, 0u); + Require(instruction.op == expected); + Require(instruction.family == RdnaInstructionFamily::SMEM); + Require(instruction.dataDwordCount == dwords); + Require(instruction.destination.kind == RdnaOperandKind::ScalarRegister); + Require(instruction.destination.reg == 2u); + Require(instruction.source0.kind == RdnaOperandKind::ScalarRegister); + Require(instruction.source0.reg == 2u); + Require(instruction.memoryOffset == 0x10); + IrProgram program; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + TranslationContext context(program, block, 256); + context.TranslateInstruction(instruction); + std::uint32_t loadCount = 0; + for (auto* value : block.Instructions()) { + if (value->Opcode() == IrOpcode::LoadAddressU32) { + ++loadCount; + } + } + Require(loadCount == dwords); +} +int main() { + Check(0x05u, RdnaOpcode::SScratchLoadDword, 1u); + Check(0x06u, RdnaOpcode::SScratchLoadDwordx2, 2u); + Check(0x07u, RdnaOpcode::SScratchLoadDwordx4, 4u); +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 23d5277fa..5986b1d72 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -697,6 +697,9 @@ enum class RdnaOpcode : std::uint16_t { SBufferLoadDwordx4, SBufferLoadDwordx8, SBufferLoadDwordx16, + SScratchLoadDword, + SScratchLoadDwordx2, + SScratchLoadDwordx4, SGetWaveidInWorkgroup, BufferStoreFormatXy, BufferStoreFormatXyz, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp index 2544e10eb..7500a8a17 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaMemoryOpDecoder.cpp @@ -59,6 +59,9 @@ constexpr MemoryOpcodeInfo smemOpcodes[] = { {0x02u, RdnaOpcode::SLoadDwordx4, 4, 32, false, false, false}, {0x03u, RdnaOpcode::SLoadDwordx8, 8, 32, false, false, false}, {0x04u, RdnaOpcode::SLoadDwordx16, 16, 32, false, false, false}, + {0x05u, RdnaOpcode::SScratchLoadDword, 1, 32, false, false, false}, + {0x06u, RdnaOpcode::SScratchLoadDwordx2, 2, 32, false, false, false}, + {0x07u, RdnaOpcode::SScratchLoadDwordx4, 4, 32, false, false, false}, {0x08u, RdnaOpcode::SBufferLoadDword, 1, 32, false, false, false}, {0x09u, RdnaOpcode::SBufferLoadDwordx2, 2, 32, false, false, false}, {0x0au, RdnaOpcode::SBufferLoadDwordx4, 4, 32, false, false, false}, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 7473a018d..e68c49ad1 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -646,6 +646,9 @@ bool IsScalarMemoryOpcode(RdnaOpcode opcode) { case RdnaOpcode::SBufferLoadDwordx4: case RdnaOpcode::SBufferLoadDwordx8: case RdnaOpcode::SBufferLoadDwordx16: + case RdnaOpcode::SScratchLoadDword: + case RdnaOpcode::SScratchLoadDwordx2: + case RdnaOpcode::SScratchLoadDwordx4: return true; default: return false; diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 3bf343733..1e8ce5069 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -86,6 +86,7 @@ private: IrU32 widenSubdword(IrValue* value, std::uint32_t bits, bool sign); IrValue* narrowSubdword(IrU32 value, std::uint32_t bits); bool sLoad(const RdnaInstruction& inst, bool raw); + bool sScratchLoad(const RdnaInstruction& inst); bool bufferLoad(const RdnaInstruction& inst); bool bufferLoadFormatD16(const RdnaInstruction& inst); bool bufferStoreFormatD16(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp index 66bc02885..24993bd96 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/MemoryInstructionDispatch.cpp @@ -40,6 +40,10 @@ bool TranslationContext::emitMemory(const RdnaInstruction& inst) { case RdnaOpcode::SBufferLoadDwordx8: case RdnaOpcode::SBufferLoadDwordx16: return sLoad(inst, false); + case RdnaOpcode::SScratchLoadDword: + case RdnaOpcode::SScratchLoadDwordx2: + case RdnaOpcode::SScratchLoadDwordx4: + return sScratchLoad(inst); case RdnaOpcode::SGetWaveidInWorkgroup: { if (program.Resources().stage != IrShaderStage::Compute) { throw std::runtime_error("s_get_waveid_in_workgroup is supported only in compute shaders, at pc " + std::to_string(inst.programCounter)); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp index d97478e6b..2ef8a7f7f 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp @@ -52,6 +52,34 @@ bool TranslationContext::sLoad(const RdnaInstruction& inst, bool raw) { return true; } +bool TranslationContext::sScratchLoad(const RdnaInstruction& inst) { + if (inst.family != RdnaInstructionFamily::SMEM) { + throw std::runtime_error("sScratchLoad requires an SMEM instruction"); + } + MemoryInfo memory; + memory.kind = ResourceKind::Scratch; + memory.offset = inst.memoryOffset; + memory.dataDwords = inst.dataDwordCount; + memory.componentCount = inst.dataDwordCount; + IrValue& resource = ir.Emit(IrOpcode::GetScratchResource, IrOpcodeType(IrOpcode::GetScratchResource), {}); + const IrU32 base = readU32(inst.source0); + const IrU32 soffset = readU32(inst.source1); + IrValue& byteOffset = ir.IAdd(base.Value(), soffset.Value()); + std::array loaded{}; + for (std::uint32_t component = 0u; component < memory.dataDwords; ++component) { + MemoryInfo scalar = memory; + scalar.offset += component * 4u; + scalar.dataDwords = 1u; + scalar.componentIndex = component; + const MemoryFlags flags = addMemoryInfo(scalar, inst.programCounter); + loaded[component] = &ir.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {&resource, &byteOffset, &ir.Constant(0u), &ir.ConstantBool(true)}, flags); + } + for (std::uint32_t component = 0u; component < memory.dataDwords; ++component) { + writeOperand(scalarDestinationOperand(inst.destination, component), loaded[component]); + } + return true; +} + void TranslationContext::TranslateCodeTableLoad(const RdnaInstruction& instruction, const ControlFlowGraph::CodeTableLoad& table) { if (table.values.empty()) throw std::runtime_error("empty shader code table"); const IrU32 index = readRawU32(instruction.source1); From 9ac108b7b562cbd8de960afea4d2959132a160b7 Mon Sep 17 00:00:00 2001 From: Kikoarl <7354010+Kikoarl@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:16:02 +0200 Subject: [PATCH 2/2] refactor(shader): throw on s_scratch_load until wave scratch layout is modelled --- core/libs/tests/ScalarScratchLoad.cpp | 13 ++++----- .../ScalarBufferInstructions.cpp | 28 ++----------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/core/libs/tests/ScalarScratchLoad.cpp b/core/libs/tests/ScalarScratchLoad.cpp index 5e1e4d8b7..7c2c770ad 100644 --- a/core/libs/tests/ScalarScratchLoad.cpp +++ b/core/libs/tests/ScalarScratchLoad.cpp @@ -23,14 +23,13 @@ static void Check(std::uint32_t opcode, RdnaOpcode expected, std::uint32_t dword auto& block = program.CreateBlock(); program.SetEntryBlock(block); TranslationContext context(program, block, 256); - context.TranslateInstruction(instruction); - std::uint32_t loadCount = 0; - for (auto* value : block.Instructions()) { - if (value->Opcode() == IrOpcode::LoadAddressU32) { - ++loadCount; - } + bool threw = false; + try { + context.TranslateInstruction(instruction); + } catch (const std::runtime_error&) { + threw = true; } - Require(loadCount == dwords); + Require(threw); } int main() { Check(0x05u, RdnaOpcode::SScratchLoadDword, 1u); diff --git a/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp b/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp index 2ef8a7f7f..ae275627b 100644 --- a/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp +++ b/core/shader/recompiler/Translation/src/MemoryInstructions/ScalarBufferInstructions.cpp @@ -52,32 +52,8 @@ bool TranslationContext::sLoad(const RdnaInstruction& inst, bool raw) { return true; } -bool TranslationContext::sScratchLoad(const RdnaInstruction& inst) { - if (inst.family != RdnaInstructionFamily::SMEM) { - throw std::runtime_error("sScratchLoad requires an SMEM instruction"); - } - MemoryInfo memory; - memory.kind = ResourceKind::Scratch; - memory.offset = inst.memoryOffset; - memory.dataDwords = inst.dataDwordCount; - memory.componentCount = inst.dataDwordCount; - IrValue& resource = ir.Emit(IrOpcode::GetScratchResource, IrOpcodeType(IrOpcode::GetScratchResource), {}); - const IrU32 base = readU32(inst.source0); - const IrU32 soffset = readU32(inst.source1); - IrValue& byteOffset = ir.IAdd(base.Value(), soffset.Value()); - std::array loaded{}; - for (std::uint32_t component = 0u; component < memory.dataDwords; ++component) { - MemoryInfo scalar = memory; - scalar.offset += component * 4u; - scalar.dataDwords = 1u; - scalar.componentIndex = component; - const MemoryFlags flags = addMemoryInfo(scalar, inst.programCounter); - loaded[component] = &ir.Emit(IrOpcode::LoadAddressU32, IrOpcodeType(IrOpcode::LoadAddressU32), {&resource, &byteOffset, &ir.Constant(0u), &ir.ConstantBool(true)}, flags); - } - for (std::uint32_t component = 0u; component < memory.dataDwords; ++component) { - writeOperand(scalarDestinationOperand(inst.destination, component), loaded[component]); - } - return true; +bool TranslationContext::sScratchLoad(const RdnaInstruction& /*inst*/) { + throw std::runtime_error("s_scratch_load not yet implemented: wave-uniform or swizzled scratch layout required"); } void TranslationContext::TranslateCodeTableLoad(const RdnaInstruction& instruction, const ControlFlowGraph::CodeTableLoad& table) {