From f8ed012e3e30e7b331c410c11ec0b880b69fac0f Mon Sep 17 00:00:00 2001 From: Mouad-Scriptz-0 Date: Sun, 11 Oct 2026 17:26:18 +0000 Subject: [PATCH] perf(recompiler): keep relatively addressed vector registers in a window array `v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32` and `v_swaprel_b32` lowered to a compare-and-select chain over every register from the base to the program's last allocated register, so a program addressing 154 registers 104 times turned 3176 ISA words into 649,515 SPIR-V words that Intel's compiler did not finish. The registers from the lowest relative base up now live in a function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`), so an indexed access is one load or store; plain accesses to those registers go through the same array, DPP-modified relative writes keep their `DppUpdateU32` merge, an index past the window reads 0 and drops its write, and programs without relative addressing are unchanged. The same program recompiles to 392,333 words with 4,992 selects instead of 32,383. Co-Authored-By: Claude Fable 5.1 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 7 +- .../libSceAgcDriver/tests/VectorWindow.cpp | 123 ++++++++++++++++++ .../IntermediateRepresentation/IrBuilder.hpp | 1 + .../IrMetadata/ProgramMetadata.hpp | 2 + .../IntermediateRepresentation/IrOpcode.hpp | 2 + .../src/IrBuilder/IrBuilderRegisters.cpp | 14 ++ .../src/IrOpcode.cpp | 3 + .../SpirvBackend/SpirvEmitterInstructions.hpp | 2 + .../SpirvBackend/SpirvEmitterState.hpp | 1 + .../src/SpirvAlu/SpirvAluEmitterRegisters.cpp | 18 +++ .../SpirvBackend/src/SpirvFlowEmitter.cpp | 2 + .../src/SpirvMemory/SpirvModuleSetup.cpp | 10 ++ .../SpirvBackend/src/SpirvModuleEmitter.cpp | 5 + .../src/ControlFlowInstructions.cpp | 31 +++++ .../Translation/src/InstructionTranslator.cpp | 26 ++++ docs/dev/TechnicalDebt.md | 1 + 16 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index bef499a10..a17954a78 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -485,6 +485,11 @@ target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driv set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_masked_select_tests) +add_test_executable(agc_vector_window_tests EXCLUDE_FROM_ALL tests/VectorWindow.cpp) +target_link_libraries(agc_vector_window_tests PRIVATE shader_recompiler_agc_driver libc) +set_target_properties(agc_vector_window_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_vector_window_tests) + add_test_executable(agc_constant_bit_test_tests EXCLUDE_FROM_ALL tests/ConstantBitTest.cpp) target_link_libraries(agc_constant_bit_test_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_constant_bit_test_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -1001,7 +1006,7 @@ if(BUILD_TESTING) if(WIN32) set_tests_properties(agc_shader_bindless agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() - foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests}) + foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_vector_window_tests agc_constant_bit_test_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests}) if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests") continue() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp b/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp new file mode 100644 index 000000000..3e852184b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/VectorWindow.cpp @@ -0,0 +1,123 @@ +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace ShaderRecompiler; + +constexpr std::uint32_t MoveM0Two = 0xbefc0382u; +constexpr std::uint32_t MoveV4One = 0x7e0802f2u; +constexpr std::uint32_t MoveV5Two = 0x7e0a02f4u; +constexpr std::uint32_t MoveV6Four = 0x7e0c02f6u; +constexpr std::uint32_t MovrelsV1V4 = 0x7e028704u; +constexpr std::uint32_t MovreldV4V1 = 0x7e088501u; +constexpr std::uint32_t MoveV1V5 = 0x7e020305u; +constexpr std::uint32_t EndProgram = 0xbf810000u; + +struct Shape { + std::size_t functionArrays = 0; + std::uint32_t arrayLength = 0; + std::size_t accessChains = 0; + std::size_t selects = 0; +}; + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +std::vector RecompileProgram(std::span code) { + const std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformVote, spv::CapabilityGroupNonUniformShuffle, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses}; + const std::array userData{}; + const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, 0x20000u, code, 0, {}}; + request.context.waveSize = 32; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 4u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00403000u; + request.target.spirvVersion = 0x00010600u; + request.target.subgroupSize = 32; + request.target.bdaAbiVersion = 1; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; + request.target.maxWorkgroupInvocations = 1024; + request.target.maxWorkgroupSharedMemoryBytes = 49152; + request.layout = {0, 0, 0, 128}; + request.useCache = false; + auto result = Recompile(request); + Require(!result.spirv.empty(), "the relative-addressing program did not recompile"); + return std::vector(result.spirv.Words().begin(), result.spirv.Words().end()); +} + +Shape Scan(std::span words) { + Shape shape; + std::vector> constants; + std::vector> arrays; + std::vector> functionPointers; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> spv::WordCountShift; + const auto op = static_cast(words[cursor] & spv::OpCodeMask); + Require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction"); + if (op == spv::OpConstant && count == 4) constants.emplace_back(words[cursor + 2], words[cursor + 3]); + if (op == spv::OpTypeArray) arrays.emplace_back(words[cursor + 1], words[cursor + 3]); + if (op == spv::OpTypePointer && words[cursor + 2] == spv::StorageClassFunction) functionPointers.emplace_back(words[cursor + 1], words[cursor + 3]); + if (op == spv::OpVariable && words[cursor + 3] == spv::StorageClassFunction) { + for (const auto& [pointer, pointee] : functionPointers) { + if (pointer != words[cursor + 1]) continue; + for (const auto& [array, length] : arrays) { + if (array != pointee) continue; + ++shape.functionArrays; + for (const auto& [constant, value] : constants) { + if (constant == length) shape.arrayLength = value; + } + } + } + } + if (op == spv::OpAccessChain) ++shape.accessChains; + if (op == spv::OpSelect) ++shape.selects; + cursor += count; + } + return shape; +} + +bool run(const char* name, bool (*test)()) { + try { + if (test()) return true; + std::fprintf(stderr, "%s\n", name); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s: %s\n", name, error.what()); + } + return false; +} + +} + +int main() { + bool passed = true; + + passed &= run("a relative read and write index a function-scope window instead of selecting over the registers", [] { + const std::array code{MoveM0Two, MoveV4One, MoveV5Two, MoveV6Four, MovrelsV1V4, MovreldV4V1, MoveV1V5, EndProgram}; + const auto shape = Scan(RecompileProgram(code)); + if (shape.functionArrays != 1 || shape.arrayLength != 3 || shape.accessChains < 2 || shape.selects > 4) std::fprintf(stderr, " window shape: arrays %zu length %u access chains %zu selects %zu\n", shape.functionArrays, shape.arrayLength, shape.accessChains, shape.selects); + return shape.functionArrays == 1 && shape.arrayLength == 3 && shape.accessChains >= 2 && shape.selects <= 4; + }); + + passed &= run("a program without relative addressing keeps its registers in SSA values", [] { + const std::array code{MoveV4One, MoveV5Two, MoveV6Four, MoveV1V5, EndProgram}; + const auto shape = Scan(RecompileProgram(code)); + return shape.functionArrays == 0 && shape.accessChains == 0; + }); + + return passed ? 0 : 1; +} diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrBuilder.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrBuilder.hpp index 99b5ef6fa..39c7c07c3 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrBuilder.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrBuilder.hpp @@ -88,6 +88,7 @@ public: } private: + IrValue* vectorWindowIndex(VectorReg reg); IrProgram& program; IrBlock* insertionPoint; }; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp index 9a6b86e8b..67eedc48c 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp @@ -26,6 +26,8 @@ struct IrProgramMetadata { IrBindingLayout bindings; bool bindingLayoutComplete = false; std::uint32_t tessellationLocalStride = 0; + std::uint32_t vectorWindowBase = 0; + std::uint32_t vectorWindowSize = 0; std::uint32_t tessellationControlStride = 0; }; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index e580b1118..6f0d51c54 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -93,6 +93,8 @@ enum class IrOpcode : std::uint16_t { SetScalarRegister, GetVectorRegister, SetVectorRegister, + LoadVectorWindow, + StoreVectorWindow, GetGotoVariable, SetGotoVariable, GetScc, diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrBuilder/IrBuilderRegisters.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrBuilder/IrBuilderRegisters.cpp index 2a81dd39b..3f45934d4 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrBuilder/IrBuilderRegisters.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrBuilder/IrBuilderRegisters.cpp @@ -1,6 +1,7 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "IntermediateRepresentation/IrBuilderInternal.hpp" #include +#include namespace ShaderRecompiler { @@ -106,12 +107,25 @@ void IrBuilder::SetScalarMaskTag(ScalarReg reg, IrValue& value) { (void)Emit(IrOpcode::SetScalarMaskTag, IrType::Void, {&operand, &value}); } +IrValue* IrBuilder::vectorWindowIndex(VectorReg reg) { + const auto& metadata = program.Metadata(); + const auto index = static_cast(RegIndex(reg)); + if (metadata.vectorWindowSize == 0u || index < metadata.vectorWindowBase) return nullptr; + if (index - metadata.vectorWindowBase >= metadata.vectorWindowSize) throw std::runtime_error("vector register " + std::to_string(index) + " lies past the relative-addressing window"); + return &Constant(index - metadata.vectorWindowBase); +} + IrValue& IrBuilder::GetVectorReg(VectorReg reg) { + if (IrValue* index = vectorWindowIndex(reg)) return Emit(IrOpcode::LoadVectorWindow, IrType::U32, {index}); IrValue& operand = createRegisterOperand(program, RegisterBank::Vector, RegIndex(reg), IrType::VectorReg); return Emit(IrOpcode::GetVectorRegister, IrType::U32, {&operand}); } void IrBuilder::SetVectorReg(VectorReg reg, IrValue& value) { + if (IrValue* index = vectorWindowIndex(reg)) { + (void)Emit(IrOpcode::StoreVectorWindow, IrType::Void, {index, &value}); + return; + } IrValue& operand = createRegisterOperand(program, RegisterBank::Vector, RegIndex(reg), IrType::VectorReg); (void)Emit(IrOpcode::SetVectorRegister, IrType::Void, {&operand, &value}); } diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp index 7abf9208a..67f8993f6 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrOpcode.cpp @@ -134,6 +134,8 @@ constexpr std::array(IrOpcode::Count)> Meta makeMeta("SetScalarRegister", Void, ScalarReg, U32), makeMeta("GetVectorRegister", U32, VectorReg), makeMeta("SetVectorRegister", Void, VectorReg, U32), + makeMeta("LoadVectorWindow", U32, U32), + makeMeta("StoreVectorWindow", Void, U32, U32), makeMeta("GetGotoVariable", U1, U32), makeMeta("SetGotoVariable", Void, U32, U1), makeMeta("GetScc", U1), @@ -890,6 +892,7 @@ bool IrOpcodeHasSideEffects(IrOpcode opcode) { case IrOpcode::SetScalarMaskTag: case IrOpcode::SetScalarRegister: case IrOpcode::SetVectorRegister: + case IrOpcode::StoreVectorWindow: case IrOpcode::SetGotoVariable: case IrOpcode::SetScc: case IrOpcode::SetExec: diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index 97071e7ca..7cdaf8d9c 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -388,6 +388,8 @@ void EmitGetScalarRegister(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitSetScalarRegister(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitGetVectorRegister(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitSetVectorRegister(SpirvValueEmitContext& ctx, const IrValue& inst); +void EmitLoadVectorWindow(SpirvValueEmitContext& ctx, const IrValue& inst); +void EmitStoreVectorWindow(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitGetGotoVariable(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitSetGotoVariable(SpirvValueEmitContext& ctx, const IrValue& inst); void EmitGetScc(SpirvValueEmitContext& ctx, const IrValue& inst); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 23288d4c7..a876a4062 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -147,6 +147,7 @@ struct SpirvEmitterState { std::uint32_t ldsBufferBase = 0; std::uint32_t numWorkgroupsVariable = 0; std::array scratchVariable {}; + std::array vectorWindowVariable {}; std::array imageVariables {}; std::uint32_t samplerVariable = 0; std::uint32_t mainFunc = 0; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterRegisters.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterRegisters.cpp index d46db4461..f24270d40 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterRegisters.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterRegisters.cpp @@ -1,4 +1,5 @@ #include "SpirvBackend/SpirvAluEmitter.hpp" +#include "SpirvBackend/SpirvEmitterHelpers.hpp" #include "SpirvBackend/SpirvEmitterInstructions.hpp" #include @@ -44,6 +45,23 @@ void EmitSetVectorRegister(SpirvValueEmitContext& ctx, const IrValue& inst) { EmitUnreachable(ctx, inst); } +std::uint32_t VectorWindowElement(SpirvValueEmitContext& ctx, const IrValue& inst) { + auto& state = ctx.state; + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), pointer, state.vectorWindowVariable.at(state.laneHalf), ctx.Arg(inst, 0u)); + return pointer; +} + +void EmitLoadVectorWindow(SpirvValueEmitContext& ctx, const IrValue& inst) { + const auto value = ctx.state.module.AllocateId(); + ctx.state.module.AddFunction(spv::OpLoad, TypeU32(ctx.state), value, VectorWindowElement(ctx, inst)); + ctx.Define(inst, value); +} + +void EmitStoreVectorWindow(SpirvValueEmitContext& ctx, const IrValue& inst) { + ctx.state.module.AddFunction(spv::OpStore, VectorWindowElement(ctx, inst), ctx.Arg(inst, 1u)); +} + void EmitGetGotoVariable(SpirvValueEmitContext& ctx, const IrValue& inst) { EmitUnreachable(ctx, inst); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index 59a389eb2..7471dfbfb 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -308,6 +308,8 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::SetScalarRegister: return Invoke(EmitSetScalarRegister, ctx, inst); case IrOpcode::GetVectorRegister: return Invoke(EmitGetVectorRegister, ctx, inst); case IrOpcode::SetVectorRegister: return Invoke(EmitSetVectorRegister, ctx, inst); + case IrOpcode::LoadVectorWindow: return Invoke(EmitLoadVectorWindow, ctx, inst); + case IrOpcode::StoreVectorWindow: return Invoke(EmitStoreVectorWindow, ctx, inst); case IrOpcode::GetGotoVariable: return Invoke(EmitGetGotoVariable, ctx, inst); case IrOpcode::SetGotoVariable: return Invoke(EmitSetGotoVariable, ctx, inst); case IrOpcode::GetScc: return Invoke(EmitGetScc, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index db279fdf5..699290232 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -55,6 +55,11 @@ void DefineModule(SpirvEmitterState& state) { state.scratchVariable.at(half) = state.module.AllocateId(); } } + if (state.program.Metadata().vectorWindowSize != 0u) { + for (std::uint32_t half = 0; half < state.laneCount; half++) { + state.vectorWindowVariable.at(half) = state.module.AllocateId(); + } + } state.mainFunc = state.module.AllocateId(); if (StageOf(state) == IrShaderStage::Mesh) { const auto& mesh = VertexInfo(state).mesh; @@ -204,6 +209,11 @@ void DefineModule(SpirvEmitterState& state) { state.module.AddName(state.scratchVariable.at(half), "scratch_dwords"); } } + if (state.program.Metadata().vectorWindowSize != 0u) { + for (std::uint32_t half = 0; half < state.laneCount; half++) { + state.module.AddName(state.vectorWindowVariable.at(half), "vector_window"); + } + } } } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 5884af305..f2bd0fde9 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -999,6 +999,11 @@ void EmitProgram(SpirvEmitterState& state) { state.module.AddFunction(spv::OpVariable, TypeU32ArrayPointer(state, spv::StorageClassFunction, state.program.Info().scratchDwords), state.scratchVariable.at(half), spv::StorageClassFunction); } } + if (const auto window = state.program.Metadata().vectorWindowSize; window != 0u) { + for (std::uint32_t half = 0; half < state.laneCount; half++) { + state.module.AddFunction(spv::OpVariable, TypeU32ArrayPointer(state, spv::StorageClassFunction, window), state.vectorWindowVariable.at(half), spv::StorageClassFunction); + } + } if (state.pixelValidMaskVariable != 0u) { state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), state.pixelValidMaskVariable, spv::StorageClassFunction); } diff --git a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp index 838346dc4..8bf064fb1 100644 --- a/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp @@ -295,10 +295,28 @@ void TranslationContext::sWqm(const RdnaInstruction& inst, bool wide) { // M0 holds a uniform register offset. Vector registers are SSA values with fixed indices, so an indexed // access becomes a select over every register the program references at or past the base. +struct RelativeWindowAccess { + IrValue* index = nullptr; + IrValue* inRange = nullptr; +}; + +RelativeWindowAccess relativeWindowAccess(IrBuilder& ir, const IrProgram& program, std::uint32_t reg, IrValue& offset) { + const auto& metadata = program.Metadata(); + if (metadata.vectorWindowSize == 0u) return {}; + if (reg < metadata.vectorWindowBase) throw std::runtime_error("relative vector access below the relative-addressing window"); + IrValue& raw = ir.IAdd(ir.Constant(reg - metadata.vectorWindowBase), offset); + IrValue& index = ir.Emit(IrOpcode::UMin32, IrType::U32, {&raw, &ir.Constant(metadata.vectorWindowSize - 1u)}); + return {&index, &ir.ULessThan(raw, ir.Constant(metadata.vectorWindowSize))}; +} + IrU32 TranslationContext::readRelativeVector(const RdnaOperand& base, IrValue& offset, const char* name) { if (base.kind != RdnaOperandKind::VectorRegister) { throw std::runtime_error(std::string(name) + " source is not a vector register"); } + if (const auto access = relativeWindowAccess(ir, program, base.reg, offset); access.index != nullptr) { + IrValue& loaded = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {access.index}); + return applyBitSourceModifiers(base, IrU32(ir.Select(*access.inRange, loaded, ir.Constant(0u)))); + } IrU32 result(ir.GetVectorReg(static_cast(base.reg))); for (std::uint32_t reg = base.reg + 1u; reg < currentVectorLimit; ++reg) { IrValue& hit = ir.IEqual(offset, ir.Constant(reg - base.reg)); @@ -312,6 +330,19 @@ void TranslationContext::writeRelativeVector(const RdnaOperand& base, IrValue& o if (destination.kind != RdnaOperandKind::VectorRegister) { throw std::runtime_error(std::string(name) + " destination is not a vector register"); } + if (const auto access = relativeWindowAccess(ir, program, destination.reg, offset); access.index != nullptr) { + IrValue* index = access.index; + IrValue& previous = ir.Emit(IrOpcode::LoadVectorWindow, IrType::U32, {index}); + IrValue* merged = nullptr; + if (destination.dpp) { + const DppMoveFlags flags{destination.dpp8 ? DppMoveFlags::Lanes8 | destination.dppCtrl : destination.dppCtrl, static_cast(destination.dppRowMask), static_cast(destination.dppBankMask), destination.dppFetchInactive, destination.dppBoundCtrl}; + merged = &ir.Emit(IrOpcode::DppUpdateU32, IrType::U32, {&value.Value(), &previous, &ir.GetExec()}, flags); + } else { + merged = &ir.Select(ir.GetExec(), value.Value(), previous); + } + (void)ir.Emit(IrOpcode::StoreVectorWindow, IrType::Void, {index, &ir.Select(*access.inRange, *merged, previous)}); + return; + } for (std::uint32_t reg = destination.reg; reg < currentVectorLimit; ++reg) { RdnaOperand target = destination; target.reg = reg; diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 421ea460d..9df2181a8 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -396,6 +396,32 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con IrProgram program; program.SetWaveSize(options.waveSize); + std::uint32_t windowBase = NumVectorRegs; + for (const auto& cfgBlock : cfg.blocks) { + for (std::uint32_t index = cfgBlock.instructionBegin; index < cfgBlock.instructionEnd; index++) { + const auto& instruction = decoded.instructions[index]; + if (isCodeTableLoad(cfg, instruction.programCounter)) continue; + switch (instruction.op) { + case RdnaOpcode::VMovrelsB32: + windowBase = std::min(windowBase, instruction.source0.reg); + break; + case RdnaOpcode::VMovreldB32: + windowBase = std::min(windowBase, instruction.destination.reg); + break; + case RdnaOpcode::VMovrelsdB32: + case RdnaOpcode::VMovrelsd2B32: + case RdnaOpcode::VSwaprelB32: + windowBase = std::min({windowBase, instruction.source0.reg, instruction.destination.reg}); + break; + default: + break; + } + } + } + if (windowBase < vectorLimit) { + program.Metadata().vectorWindowBase = windowBase; + program.Metadata().vectorWindowSize = vectorLimit - windowBase; + } program.Resources().stage = toIrShaderStage(options.stage); program.Resources().shaderHash = options.shaderHash; program.Resources().userDataBase = options.userDataBaseRegister; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..719999491 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -577,6 +577,7 @@ Throughout the project, every function at every stage either **does exactly what - [LINE_STIPPLE_TEX](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) reads as 0: line stipple is not emulated. RADV enables the input only as the required pixel input of a shader that reads no interpolants, because it initializes the fewest VGPRs ([Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_shader.c) `radv_compute_spi_ps_input`), and never reads it - [Draw snapshot buffer descriptors](../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) rewrite each affected array from element zero, preserving unchanged descriptors. MoltenVK 1.4.2 writes auxiliary buffer lengths at index `i` instead of `start + i` for partial array updates ([MVKDescriptorSet.mm, BufferAuxSize](https://github.com/KhronosGroup/MoltenVK/blob/v1.4.2/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm#L1051)). Per-element writes therefore let one buffer length replace another even when Vulkan buffer handles and ranges are correct. - [Static scalar calls](../../core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp) (`s_call_b64` and `s_swappc_b64`) require an ordinary even-aligned SGPR link pair (s0:s1 through s104:s105) and a later `s_setpc_b64` return reading that exact pair. A pair serves one call, or several calls whose regions are disjoint, each closed by the first return reading the pair after its target. Outside every region of its calls the pair may only be overwritten whole, by `s_mov_b32`, `s_mov_b64`, an SOP2 result or a scalar load, as the caller of a user-data callee does before each call. Regions must nest or be disjoint; recursion, other shared links, escaping link values (including overlapping tuples and resource descriptors), invalid targets, interleaved regions, cross-region branches or fall-through, relative SGPR reads and dynamic `s_setpc_b64` in a program with calls throw. The analysis checks all decoded instructions, including unreachable code. Arbitrary indirect calls and general call frames are not implemented. +- [Relative vector addressing](../../core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp) (`v_movrels_b32`, `v_movreld_b32`, `v_movrelsd_b32`, `v_movrelsd_2_b32`, `v_swaprel_b32`) keeps every vector register from the lowest base register the program addresses relatively up to its last allocated register in a function-scope array per invocation (`LoadVectorWindow`/`StoreVectorWindow`, one element per register), so an indexed access is one load or store instead of a compare-and-select chain over the window; the host compiler decides whether that array stays in registers or spills to scratch, which is not measured. An index past the window reads 0 and its write is dropped, as the RDNA ISA describes for out-of-range VGPR indexing, where the earlier select chain read the base register and dropped the write; neither is measured on the console. Killzone: Liberation's PSP vertex program (PPSA11142) went from 649,515 to the recompiled size noted in its pull request with this. - [User-data callees](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a compute shader's `s_swappc_b64` whose source pair is a user-data pair, or a copy of one made with `s_mov_b32` or `s_mov_b64` in the same block, calls the code at the address that user data holds. At dispatch the callee is read from guest memory up to its `s_setpc_b64` on the link pair, one copy of it is appended per call after the caller's last instruction, and the call is rewritten to `s_call_b64` to that copy. The words after the caller's last instruction are dropped; in the shaders seen they are metadata, and a caller that reads its program counter, which could address them, throws. A copy runs at another program counter than the callee, so a callee that reads its program counter, makes a call, ends the program, jumps through another pair, returns before code it branches to, spans more than 64 KiB or is not readable throws. Callees are read again whenever a dispatch misses the dispatch cache; a dispatch that hits it reuses the linked program, so a title that rewrote a callee in place at the same address would keep running the old code. - [DB_RENDER_CONTROL resummarize draws](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - HTILE is not modeled, so a RESUMMARIZE_ENABLE draw without depth or stencil tests and without color writes changes no modeled state and draws nothing; any other resummarize, copy or decompress draw throws - [s_call_b64](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp) saves `PC + 4` over `GetShaderBase` and branches to `PC + 4 + signext(SIMM16) * 4`, following [AMD RDNA2 ISA](https://gpuopen.com/download/rdna2-shader-instruction-set-architecture.pdf) sections 3.6.3, 12.2 and 12.3. Forward callees after `s_endpgm` are decoded. Measured on RDNA2 (gfx1036) in wave32 and wave64 with SIMM16 = 3: the link pair receives `PC + 4`, the branch goes to `PC + 4 + signext(SIMM16) * 4`, and `s_setpc_b64` of the link returns to the instruction after the call. Execution on the PS5 (gfx1013) and title compatibility for these bounded scalar calls remain unverified; `s_rfe_b64` still throws in translation.