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 <noreply@anthropic.com>
This commit is contained in:
AlxcBTWandClaude Opus 5.5 committed 2026-10-12 12:17:25 +02:00
1 parent e7c70a2df9
commit 3ea89674d2
2 files changed
+98

No files matched your search

@@ -223,7 +223,59 @@ void testStructExtractsAreNotShuffled() {
}
}
void testFoldingLeavesNoDeadBlocksOrConstants() {
const auto op = [](std::uint32_t count, spv::Op code) { return (count << 16u) | static_cast<std::uint32_t>(code); };
const std::vector<std::uint32_t> 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<std::uint32_t> 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();
@@ -257,6 +257,8 @@ public:
changed |= propagateCopies();
}
removeDeadComputations();
removeUnreachableBlocks();
removeUnusedConstants();
orderPhis();
std::vector<std::uint32_t> words = header;
bool inserted = false;
@@ -505,6 +507,50 @@ private:
return changed;
}
std::set<std::uint32_t> referencedIds() const {
std::set<std::uint32_t> 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<Instruction> ordered;
std::vector<Instruction> copies;