From 95c89229ada73c408153ea7ba4ed3930e2347587 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 2 Oct 2026 15:49:43 +0400 Subject: [PATCH] Remove dead shader instruction cycles with linear liveness traversal --- src/graphics/shader/recompiler/ir/Value.h | 3 ++ .../ir/passes/DeadCodeElimination.cpp | 41 ++++++++++++------- tests/ScalarProvenanceTests.cpp | 34 +++++++++++++++ 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/graphics/shader/recompiler/ir/Value.h b/src/graphics/shader/recompiler/ir/Value.h index b03475149..43d89ac6c 100644 --- a/src/graphics/shader/recompiler/ir/Value.h +++ b/src/graphics/shader/recompiler/ir/Value.h @@ -152,6 +152,8 @@ public: } private: + friend void EliminateDeadCode(const std::vector& blocks); + void AddUse(Inst* used, size_t operand); void RemoveUse(Inst* used, size_t operand); void ClearArgs(); @@ -161,6 +163,7 @@ private: ValueOpcode opcode; uint8_t num_args; + bool live = false; mutable uint32_t evaluation_index = UINT32_MAX; uint64_t flags; Block* parent = nullptr; diff --git a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp index 3ef1f3589..6bae6cb9e 100644 --- a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp +++ b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp @@ -1,5 +1,7 @@ #include "graphics/shader/recompiler/ir/passes/DeadCodeElimination.h" +#include + namespace Libs::Graphics::ShaderRecompiler::IR { void RemoveIdentities(const BlockList& blocks) { @@ -18,23 +20,32 @@ void RemoveIdentities(const BlockList& blocks) { } void EliminateDeadCode(const BlockList& blocks) { - bool changed; - do { - changed = false; - for (auto block = blocks.rbegin(); block != blocks.rend(); block++) { - auto& instructions = (*block)->Instructions(); - auto inst = instructions.end(); - while (inst != instructions.begin()) { - --inst; - if (inst->HasUses() || inst->MayHaveSideEffects()) { - continue; - } - inst->Invalidate(); - inst = instructions.erase(inst); - changed = true; + std::vector pending; + for (auto* block: blocks) { + for (auto& inst: *block) { + // Retained resource-planning instructions live outside the block list. + inst.live = inst.MayHaveSideEffects() || std::ranges::any_of(inst.Uses(), + [](const Use& use) { return use.user->Parent() == nullptr; }); + if (inst.live) pending.push_back(&inst); + } + } + while (!pending.empty()) { + const auto* inst = pending.back(); + pending.pop_back(); + for (size_t arg = 0; arg < inst->NumArgs(); ++arg) { + auto* input = inst->Arg(arg).TryInstruction(); + if (input != nullptr && input->Parent() != nullptr && !input->live) { + input->live = true; + pending.push_back(input); } } - } while (changed); + } + // Unlink every dead operand before freeing nodes in a dead Phi cycle. + for (auto* block: blocks) + for (auto& inst: *block) + if (!inst.live) inst.Invalidate(); + for (auto* block: blocks) + std::erase_if(block->Instructions(), [](const Inst& inst) { return !inst.live; }); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/tests/ScalarProvenanceTests.cpp b/tests/ScalarProvenanceTests.cpp index 19d384bb3..946ec2b4f 100644 --- a/tests/ScalarProvenanceTests.cpp +++ b/tests/ScalarProvenanceTests.cpp @@ -650,6 +650,39 @@ void TestControlFlowValueSurvivesReadLaneFolding() { ValidateProgram(fixture.program, true); } +void TestDeadPhiCyclesAndPlanningRoots() { + Fixture fixture(2); + auto &entry = fixture.BlockAt(0); + auto &loop = fixture.BlockAt(1); + entry.AddBranch(&loop); + loop.AddBranch(&loop); + const auto source = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast(2))}); + const auto identity = fixture.Emit(ValueOpcode::Identity, {source}); + auto &live = loop.AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32)); + live.AddPhiOperand(&entry, identity); + live.AddPhiOperand(&loop, Value(&live)); + const auto reference = fixture.Emit(ValueOpcode::ReferenceU32, {Value(&live)}, 0, 1); + auto &dead = loop.AppendNewInst(ValueOpcode::Phi, {}, uint64_t(Type::U32)); + const auto increment = fixture.Emit(ValueOpcode::IAdd32, {Value(&dead), Value(1u)}, 0, 1); + dead.AddPhiOperand(&entry, source); + dead.AddPhiOperand(&loop, increment); + const auto retained = fixture.Emit(ValueOpcode::GetUserData, {Value(static_cast(3))}); + auto &planning = fixture.program.value_storage.emplace_back(ValueOpcode::Identity); + planning.SetArg(0, retained); + + EliminateDeadCode(fixture.program.blocks); + Check(entry.Instructions().size() == 3 && loop.Instructions().size() == 2, + "dead Phi cycle survived or a live/planning dependency was removed"); + Check(identity.Instruction()->Arg(0) == source && live.Arg(0) == identity && + live.Arg(1) == Value(&live) && planning.Arg(0) == retained, + "DCE did not preserve direct Identity operands or live Phi recurrence"); + // Dropping roots must clear prior marks and remove the newly dead SCC safely. + reference.Instruction()->Invalidate(); + planning.Invalidate(); + EliminateDeadCode(fixture.program.blocks); + Check(entry.empty() && loop.empty(), "DCE reused stale marks after removing its roots"); +} + void TestUndefinedRuntimeValueFails() { Fixture fixture; const auto undef = fixture.Emit(ValueOpcode::UndefU32); @@ -699,6 +732,7 @@ int main() { TestReadLaneElimination(); TestOptimizationPipeline(); TestControlFlowValueSurvivesReadLaneFolding(); + TestDeadPhiCyclesAndPlanningRoots(); TestUndefinedRuntimeValueFails(); std::cout << "TypedValuePlanningTests: all cases passed\n"; return 0;