From 364d7cf29f4c5f5875785ff82b129d87ebc5ecfe Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 12 Oct 2026 02:02:21 +0200 Subject: [PATCH] fix(shader): match half-wave reductions over relatively addressed vector registers A program that uses v_movrels_b32 or v_movreld_b32 keeps every vector register from the lowest relatively addressed one up in a per-invocation window array, so its plain register accesses become LoadVectorWindow and StoreVectorWindow. The half-wave reduction matcher followed only SSA values, so a wave64 scan, v_permlanex16_b32 join and lane 31/63 read through that array threw that lane 63 is outside the 32-lane host subgroup. The matcher now reads a window load at a constant slot as the value of the last store to that slot earlier in its block, unless a store at a runtime slot lies in between; each invocation owns its array, so that is the value the load returns. A store in another block, or a v_movreld_b32 at a runtime M0 in between, still throws. This was the only refused lane 32-63 read left among Ghost of Yotei's wave64 graphics programs (one pixel program). --- .../libSceAgcDriver/tests/ShaderMemory.cpp | 7 +++++ .../tests/execution/PixelWaveReduction.cpp | 15 ++++++----- .../src/SpirvAlu/SpirvAluEmitterMath.cpp | 26 ++++++++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 2a86162fa..21db2c34f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1341,6 +1341,13 @@ void verifyHalfWaveReduction() { divergent.insert(divergent.begin() + 3, 0x88fe1a7eu); divergent.insert(divergent.begin() + 2, {0xbe860480u, 0x7d821688u, 0xbe9a246au, 0xbf880001u}); require(reductions(pixel(divergent, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: keys masked inside a divergent branch did not read lane 31 as a subgroup UMin"); + auto windowed = umin; + windowed.insert(windowed.begin() + 23, 0x7e008514u); + require(reductions(pixel(windowed, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a UMin scan of registers in a v_movreld_b32 window did not read lane 31 as a subgroup UMin on 32 lanes"); + require(halves(pixel(windowed, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a UMin scan of registers in a v_movreld_b32 window did not read lanes 31 and 63 as reductions of host invocations 0-31 and 32-63 on 64 lanes"); + auto overwritten = umin; + overwritten.insert(overwritten.begin() + 17, {0x7ef80500u, 0x7e008514u}); + expectFailure([&] { static_cast(pixel(overwritten, 32u, true)); }, lane63, "half-wave reduction: a scan of window registers read lane 63 after a v_movreld_b32 at a runtime M0 that may overwrite them"); const char* arithmetic = "v_readlane_b32 of lane 31 of a wave64 half-wave reduction scan needs subgroup arithmetic"; expectFailure([&] { static_cast(pixel(umin, 32u, false)); }, arithmetic, "half-wave reduction: a device without subgroup arithmetic read lane 31 on 32 lanes"); expectFailure([&] { static_cast(pixel(umin, 64u, false)); }, arithmetic, "half-wave reduction: a device without subgroup arithmetic read lane 31 on 64 lanes"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp index 83f21e2b2..7c007900a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp @@ -194,13 +194,16 @@ int main() { }}; for (const auto& layout : layouts) { for (const auto& reduction : reductions) { - const auto code = PixelCode(reduction); - std::uint32_t partial = 0; - for (std::uint32_t pass = 0; pass < 4; ++pass) { - Draw(*device, code, layout); - partial += Check(reduction, target.subgroupSize); + for (const bool windowed : {false, true}) { + auto code = PixelCode(reduction); + if (windowed) code.insert(std::find(code.begin(), code.end(), 0x7e0e02f2u), 0x7e008514u); + std::uint32_t partial = 0; + for (std::uint32_t pass = 0; pass < 4; ++pass) { + Draw(*device, code, layout); + partial += Check(reduction, target.subgroupSize); + } + std::printf("%s%s, laid out for %u lanes on %u-lane subgroups: %u partial waves over 4 draws\n", reduction.name, windowed ? " in a v_movreld_b32 window" : "", layout.subgroupSize, target.subgroupSize, partial); } - std::printf("%s, laid out for %u lanes on %u-lane subgroups: %u partial waves over 4 draws\n", reduction.name, layout.subgroupSize, target.subgroupSize, partial); } } std::puts("pixel wave reduction tests passed"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index ec55881e3..217c580b0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -1,5 +1,6 @@ #include "SpirvBackend/SpirvEmitterInstructions.hpp" #include "SpirvBackend/SpirvMemory/SpirvSubgroup.hpp" +#include "IntermediateRepresentation/IrBlock.hpp" #include "IntermediateRepresentation/IrValue.hpp" #include #include @@ -227,8 +228,31 @@ struct HalfWaveScan { const IrValue* source = nullptr; }; +const IrValue* StoredWindowValue(const IrValue* load) { + const auto* slot = load->Argument(0)->Resolve(); + const auto* block = load->Parent(); + if (slot == nullptr || !slot->HasImmediate() || block == nullptr) return nullptr; + const auto& instructions = block->Instructions(); + auto at = std::find(instructions.begin(), instructions.end(), load); + if (at == instructions.end()) return nullptr; + while (at != instructions.begin()) { + const auto* previous = *--at; + if (previous->HasImmediate() || previous->Opcode() != IrOpcode::StoreVectorWindow) continue; + const auto* stored = previous->Argument(0)->Resolve(); + if (stored == nullptr || !stored->HasImmediate()) return nullptr; + if (stored->ImmediateU32() == slot->ImmediateU32()) return previous->Argument(1)->Resolve(); + } + return nullptr; +} + const IrValue* Resolved(const IrValue* value) { - return value != nullptr ? value->Resolve() : nullptr; + value = value != nullptr ? value->Resolve() : nullptr; + while (value != nullptr && !value->HasImmediate() && value->Opcode() == IrOpcode::LoadVectorWindow) { + const auto* stored = StoredWindowValue(value); + if (stored == nullptr) break; + value = stored; + } + return value; } bool Is(const IrValue* value, IrOpcode opcode) {