From 3ea89674d2ef8818a14d5ea7576b4f1a2582635e Mon Sep 17 00:00:00 2001 From: AlxcBTW <187763517+AlxcBTW@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:13:21 +0200 Subject: [PATCH] refactor(shader): drop unused folded constants and unreachable blocks after specialization SpecializeSpirv hoists every instruction it folds into a module-level constant and keeps that constant after its last use has folded away too. It also leaves each block that a folded branch cuts off as OpLabel; OpUnreachable. A formatted buffer helper's 77-case format switch leaves about 1,800 such blocks. Emit only the folded constants that something still refers to, and remove OpLabel; OpUnreachable blocks that no instruction names. Merge and continue targets of live constructs are named, so they stay. The specialized modules lose words, not behaviour. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/SpirvModule.cpp | 52 +++++++++++++++++++ .../SpirvBackend/src/SpirvSpecialization.cpp | 46 ++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp index 98353c1399..fc5decffcc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SpirvModule.cpp @@ -223,7 +223,59 @@ void testStructExtractsAreNotShuffled() { } } +void testFoldingLeavesNoDeadBlocksOrConstants() { + const auto op = [](std::uint32_t count, spv::Op code) { return (count << 16u) | static_cast(code); }; + const std::vector words{ + spv::MagicNumber, 0x00010300u, 0u, 30u, 0u, + op(2, spv::OpCapability), spv::CapabilityShader, + op(3, spv::OpMemoryModel), spv::AddressingModelLogical, spv::MemoryModelGLSL450, + op(6, spv::OpEntryPoint), spv::ExecutionModelGLCompute, 11u, 0x6e69616du, 0u, 10u, + op(6, spv::OpExecutionMode), 11u, spv::ExecutionModeLocalSize, 1u, 1u, 1u, + op(2, spv::OpTypeVoid), 1u, + op(4, spv::OpTypeInt), 2u, 32u, 0u, + op(4, spv::OpTypePointer), 4u, spv::StorageClassPrivate, 2u, + op(3, spv::OpTypeFunction), 5u, 1u, + op(4, spv::OpConstant), 2u, 6u, 2u, + op(4, spv::OpConstant), 2u, 7u, 10u, + op(4, spv::OpConstant), 2u, 8u, 20u, + op(4, spv::OpVariable), 4u, 10u, spv::StorageClassPrivate, + op(5, spv::OpFunction), 1u, 11u, 0u, 5u, + op(2, spv::OpLabel), 12u, + op(5, spv::OpIAdd), 2u, 13u, 6u, 7u, + op(5, spv::OpIMul), 2u, 14u, 13u, 7u, + op(3, spv::OpSelectionMerge), 20u, spv::SelectionControlMaskNone, + op(7, spv::OpSwitch), 6u, 15u, 1u, 16u, 2u, 17u, + op(2, spv::OpLabel), 15u, + op(3, spv::OpStore), 10u, 7u, + op(2, spv::OpBranch), 20u, + op(2, spv::OpLabel), 16u, + op(3, spv::OpStore), 10u, 8u, + op(2, spv::OpBranch), 20u, + op(2, spv::OpLabel), 17u, + op(3, spv::OpStore), 10u, 14u, + op(2, spv::OpBranch), 20u, + op(2, spv::OpLabel), 20u, + op(1, spv::OpReturn), + op(1, spv::OpFunctionEnd), + }; + const auto specialized = ShaderRecompiler::SpecializeSpirv(words); + const auto counts = opcodeCounts(specialized); + std::set constants; + bool storesFolded = false; + for (std::size_t cursor = 5; cursor < specialized.size(); cursor += specialized[cursor] >> 16u) { + if ((specialized[cursor] >> 16u) == 0u) break; + const auto opcode = specialized[cursor] & 0xffffu; + if (opcode == spv::OpConstant) constants.insert(specialized[cursor + 2u]); + if (opcode == spv::OpStore && specialized[cursor + 2u] == 14u) storesFolded = true; + } + check(!counts.contains(spv::OpUnreachable), "blocks the folded switch left unreachable were kept"); + check(counts.at(spv::OpLabel) == 3u, "the specialized function does not have exactly its three live blocks"); + check(storesFolded && constants.contains(14u), "the folded product the live case stores is missing"); + check(!constants.contains(13u), "a folded constant without uses was kept"); +} + int main() { + testFoldingLeavesNoDeadBlocksOrConstants(); testStructExtractsAreNotShuffled(); testRepeatedTypesShareOneId(); testOperandShapesDoNotCollide(); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp index 6d8d414774..5a103ee158 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp @@ -257,6 +257,8 @@ public: changed |= propagateCopies(); } removeDeadComputations(); + removeUnreachableBlocks(); + removeUnusedConstants(); orderPhis(); std::vector words = header; bool inserted = false; @@ -505,6 +507,50 @@ private: return changed; } + std::set referencedIds() const { + std::set referenced; + for (const auto& instruction : instructions) { + if (instruction.empty()) continue; + const auto op = Opcode(instruction); + if (op == spv::OpName || op == spv::OpMemberName || op == spv::OpDecorate || op == spv::OpMemberDecorate || op == spv::OpLabel) continue; + for (std::size_t index = 1; index < instruction.size(); ++index) referenced.insert(instruction[index]); + } + return referenced; + } + + void removeUnreachableBlocks() { + const auto referenced = referencedIds(); + const auto next = [&](std::size_t index) { + while (++index < instructions.size() && instructions[index].empty()) {} + return index; + }; + std::size_t previous = instructions.size(); + for (std::size_t index = 0; index < instructions.size(); ++index) { + if (instructions[index].empty()) continue; + const auto op = Opcode(instructions[index]); + const auto following = next(index); + const bool entry = previous < instructions.size() && (Opcode(instructions[previous]) == spv::OpFunction || Opcode(instructions[previous]) == spv::OpFunctionParameter); + if (op == spv::OpLabel && !entry && following < instructions.size() && Opcode(instructions[following]) == spv::OpUnreachable && !referenced.contains(instructions[index].at(1))) { + removed.insert(instructions[index].at(1)); + instructions[index].clear(); + instructions[following].clear(); + index = following; + continue; + } + previous = index; + } + } + + void removeUnusedConstants() { + const auto referenced = referencedIds(); + std::erase_if(constants, [&](const Instruction& constant) { + const auto id = Result(constant); + if (referenced.contains(id)) return false; + removed.insert(id); + return true; + }); + } + void orderPhis() { std::vector ordered; std::vector copies;