From fcc426b6a2119056c8c171945e7fdab226089388 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 19:35:45 +0000 Subject: [PATCH 1/2] fix(shader): add the ordered count of a first active lane in the upper wave64 half The ordered count's amount was shuffled from the first half's value of the first active lane's host lane, so a wave64 program whose active lanes were all in lanes 32 to 63 added the inactive lane's value from the lower half. Read the amount through the context shuffle, which selects the half the first active guest lane lives in, as v_readfirstlane_b32 does. The GDS test now masks the ordered count to the upper part of each wave, including a wave64 whose first active lane is lane 40, prefills the result register to check that inactive lanes stay untouched, and keeps the M0 cases of the counter addressing. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019hfwvntiwabeqdZ6avJssk --- .../tests/WaveControlDecode.cpp | 5 +- .../tests/execution/GdsAppendConsume.cpp | 49 +++++++++++++------ .../SpirvMemory/SpirvMemoryInstructions.cpp | 6 +-- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp index 16faeba83fc..707159cd03a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WaveControlDecode.cpp @@ -62,7 +62,7 @@ void verifyDecode() { require(decoded[11].op == RdnaOpcode::SCodeEnd, "s_code_end"); } -std::string recompile(std::span code) { +std::string recompile(std::span code, std::uint32_t subgroupSize = 64u) { const std::array userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu}; const std::array capabilities{1u, 61u}; const std::array extensions{"SPV_KHR_storage_buffer_storage_class"}; @@ -74,7 +74,7 @@ std::string recompile(std::span code) { request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; request.target.vulkanVersion = 0x00403000u; request.target.spirvVersion = 0x00010600u; - request.target.subgroupSize = 64u; + request.target.subgroupSize = subgroupSize; request.target.bdaAbiVersion = 1; request.target.supportedCapabilities = capabilities; request.target.supportedExtensions = extensions; @@ -104,6 +104,7 @@ void verifyTranslation() { require(recompile(trailingLoop).empty(), "a program padded with s_code_end after its last branch is refused: " + recompile(trailingLoop)); const auto orderedAdd = withStore({0xd8fe0000u, 0x05000001u}); require(recompile(orderedAdd).empty(), "ds_ordered_count adding to a GDS counter is refused: " + recompile(orderedAdd)); + require(recompile(orderedAdd, 32u).empty(), "ds_ordered_count of a wave64 on 32-lane subgroups is refused: " + recompile(orderedAdd, 32u)); const std::vector, std::string_view>> refused{ {"s_sethalt 1", withStore({0xbf8d0001u}), "s_sethalt 1 at pc 0 halts the wave"}, diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GdsAppendConsume.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GdsAppendConsume.cpp index 9ac9376530a..3be5f5b44a9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/GdsAppendConsume.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GdsAppendConsume.cpp @@ -25,9 +25,11 @@ constexpr std::uint32_t CounterOffset = 0x10; constexpr std::array Starts{1000, 2000, 3000, 4000}; constexpr std::uint32_t UntouchedAppend = 0x5eed0001u; constexpr std::uint32_t UntouchedConsume = 0x5eed0002u; +constexpr std::uint32_t UntouchedOrdered = 0x5eed0003u; constexpr std::size_t ActiveLanesLiteral = 12; -constexpr std::size_t OrderedM0Literal = 3; -constexpr std::array OrderedM0{0u, 7u}; +constexpr std::size_t OrderedWaveMaskLiteral = 5; +constexpr std::size_t OrderedM0Literal = 7; +constexpr std::size_t OrderedFirstLaneLiteral = 9; alignas(256) std::array Output{}; alignas(16) std::array CounterReadback{}; @@ -49,13 +51,24 @@ alignas(256) auto Code = std::to_array({ alignas(256) auto OrderedCode = std::to_array({ 0x34060082, 0x4a020083, + 0x7e0a02ff, UntouchedOrdered, + 0x360400ff, 0x0000003f, 0xbefc03ff, 0x0000ffff, + 0x7da604ff, 0x00000000, 0xd8fe0310, 0x05000001, + 0xbefe04c1, 0xbf8cc07f, 0xe0702000, 0x80000503, 0xbf810000, }); +struct OrderedRun { + std::uint32_t m0; + std::uint32_t firstLaneEighths; +}; + +constexpr std::array OrderedRuns{{{0, 0}, {7, 0}, {7, 2}, {7, 5}}}; + struct Workgroup { std::uint32_t threads; std::uint32_t waveSize; @@ -144,24 +157,29 @@ void CheckWaves(const Workgroup& workgroup, std::uint32_t result, std::uint32_t } } -void CheckOrdered(const Workgroup& workgroup, std::uint32_t m0, const std::array& counters) { +void CheckOrdered(const Workgroup& workgroup, std::uint32_t m0, std::uint32_t firstLane, const std::array& counters) { const auto waves = workgroup.threads / workgroup.waveSize; - const auto what = workgroup.Name() + " ds_ordered_count with M0 " + std::to_string(m0); + const auto what = workgroup.Name() + " ds_ordered_count with M0 " + std::to_string(m0) + " from lane " + std::to_string(firstLane) + " of each wave"; + std::vector values(waves); std::vector order(waves); for (std::uint32_t wave = 0; wave < waves; ++wave) { order[wave] = wave; - const auto first = wave * workgroup.waveSize; - for (std::uint32_t lane = first; lane < first + workgroup.waveSize; ++lane) { + const auto first = wave * workgroup.waveSize + firstLane; + values[wave] = Output[first * Results]; + for (std::uint32_t lane = wave * workgroup.waveSize; lane < (wave + 1u) * workgroup.waveSize; ++lane) { const auto actual = Output[lane * Results]; - Require(actual == Output[first * Results], what + ": lane " + std::to_string(lane) + " returned " + std::to_string(actual) + ", its wave's first lane " + std::to_string(Output[first * Results])); + if (lane < first) { + Require(actual == UntouchedOrdered, what + ": inactive lane " + std::to_string(lane) + " was written " + std::to_string(actual)); + continue; + } + Require(actual == values[wave], what + ": lane " + std::to_string(lane) + " returned " + std::to_string(actual) + ", its wave's first active lane " + std::to_string(values[wave])); } } - std::sort(order.begin(), order.end(), [&](std::uint32_t left, std::uint32_t right) { return Output[left * workgroup.waveSize * Results] < Output[right * workgroup.waveSize * Results]; }); + std::sort(order.begin(), order.end(), [&](std::uint32_t left, std::uint32_t right) { return values[left] < values[right]; }); auto expected = Starts[0]; for (const auto wave : order) { - const auto first = wave * workgroup.waveSize; - Require(Output[first * Results] == expected, what + ": wave " + std::to_string(wave) + " returned " + std::to_string(Output[first * Results]) + ", expected " + std::to_string(expected)); - expected += first + 3u; + Require(values[wave] == expected, what + ": wave " + std::to_string(wave) + " returned " + std::to_string(values[wave]) + ", expected " + std::to_string(expected)); + expected += wave * workgroup.waveSize + firstLane + 3u; } Require(counters[0] == expected, what + ": GDS counter 0 is " + std::to_string(counters[0]) + ", expected " + std::to_string(expected)); } @@ -201,11 +219,14 @@ int main() { Run(*device, workgroup, Code); Check(workgroup, pass, ReadCounters(queue)); } - for (const auto m0 : OrderedM0) { + for (const auto& run : OrderedRuns) { + const auto firstLane = workgroup.waveSize * run.firstLaneEighths / 8u; SetCounters(queue); - OrderedCode[OrderedM0Literal] = m0; + OrderedCode[OrderedWaveMaskLiteral] = workgroup.waveSize - 1u; + OrderedCode[OrderedM0Literal] = run.m0; + OrderedCode[OrderedFirstLaneLiteral] = firstLane; Run(*device, workgroup, OrderedCode); - CheckOrdered(workgroup, m0, ReadCounters(queue)); + CheckOrdered(workgroup, run.m0, firstLane, ReadCounters(queue)); } } std::puts("gds append consume tests passed"); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index b7ee109d7f1..54d05114066 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -1283,11 +1283,7 @@ std::uint32_t AppendConsume(SpirvValueEmitContext& ctx, const IrValue& inst, boo const auto count = Binary(state, spv::OpIAdd, TypeU32(state), Unary(state, spv::OpBitCount, TypeU32(state), low), Unary(state, spv::OpBitCount, TypeU32(state), high)); const auto first = ctx.FirstLane(ballot); const auto sourceLane = wave64 ? Binary(state, spv::OpBitwiseAnd, TypeU32(state), first, ConstantU32(state, 31u)) : first; - auto amount = count; - if (ordered) { - amount = state.module.AllocateId(); - state.module.AddFunction(spv::OpGroupNonUniformShuffle, TypeU32(state), amount, ConstantU32(state, spv::ScopeSubgroup), ctx.Arg(inst, 4), EmitHostSubgroupLane(state, sourceLane)); - } + const auto amount = ordered ? ctx.Shuffle(inst, 4, first) : count; const auto isFirst = Binary(state, spv::OpIEqual, TypeBool(state), EmitSubgroupLocalInvocationId(state), sourceLane); const auto storageBounds = EmitMemoryElementInBounds(state, access, index); const auto bounds = m0Address ? AndCondition(state, storageBounds, m0Bounds) : storageBounds; From 411ce81a6e41da8bf63bbeeeebc39bd716e5ff24 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 19:35:45 +0000 Subject: [PATCH 2/2] test(agc): cover ordered-append modifiers on indirect dispatches DISPATCH_INDIRECT accepts ORDERED_APPEND_ENBL like DISPATCH_DIRECT and still refuses ORDERED_APPEND_MODE. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_019hfwvntiwabeqdZ6avJssk --- core/libs/prx/libSceAgcDriver/tests/Pm4.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 39131eb9cab..a2ebacd5725 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -210,7 +210,9 @@ void testContextAndBases() { expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x2063}, 2), 0); }, "dispatch modifiers"); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x41}, 4), 0); }, "header flags"); AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x49}), 0); + AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x49}), 0); expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x15, {1, 1, 1, 0x51}), 0); }, "dispatch modifiers"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x16, {0, 0x51}), 0); }, "indirect dispatch modifiers"); execute(state, makePacket(0x13, {32})); execute(state, makePacket(0x26, {0x1000, 1})); execute(state, makePacket(0x2a, {1}));