From 8b51370ca1834b2c422b1b088b01822487d5d1e1 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 18 Oct 2026 20:37:39 +0200 Subject: [PATCH] shader: eliminate predicates for empty branch detours --- .../ir/passes/DeadCodeElimination.cpp | 78 +++++++++++++------ tests/shaderCfgTests.cpp | 53 +++++++++++++ 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp index c6b775a06..602931465 100644 --- a/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp +++ b/src/graphics/shader/recompiler/ir/passes/DeadCodeElimination.cpp @@ -30,32 +30,62 @@ void RemoveIdentities(Program& program) { } void EliminateDeadCode(const BlockList& blocks) { - 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); + bool changed; + do { + 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); } } - } - // 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; }); + 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); + } + } + } + // 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; }); + + changed = false; + for (auto* block: blocks) { + const auto& term = block->terminator; + if (term.kind != CFG::TerminatorKind::ConditionalBranch || term.loop_header || + block->condition.IsImmediate()) continue; + for (const bool take_true: {false, true}) { + const auto* empty = take_true ? term.false_block : term.true_block; + const auto* merge = take_true ? term.true_block : term.false_block; + if (empty == nullptr || merge == nullptr || !empty->empty() || + empty->terminator.loop_header || merge->terminator.loop_header || + empty->terminator.kind != CFG::TerminatorKind::Branch || + empty->terminator.true_block != merge || + std::ranges::any_of(*merge, [](const Inst& inst) { + return inst.GetOpcode() == ValueOpcode::Phi; + })) continue; + // Keep the CFG and SSA edges intact; only the empty detour's predicate is dead. + for (auto& inst: *block) { + if (inst.GetOpcode() == ValueOpcode::Reference && + inst.Arg(0).Resolve() == block->condition.Resolve()) + inst.SetArg(0, Value(take_true)); + } + block->condition = Value(take_true); + changed = true; + break; + } + } + } while (changed); } } // namespace Libs::Graphics::ShaderRecompiler::IR diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index bd6aa3b2f..30f770ef5 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -15353,6 +15353,58 @@ void TestScalarMemoryLoadCrossesIntoVcc() { } } +void TestEmptyBranchScalarReadDce() { + using namespace ShaderRecompiler::IR; + using ShaderRecompiler::CFG::TerminatorKind; + for (const bool reverse : {false, true}) { + for (uint32_t mode = 0; mode < 4; ++mode) { + Block entry, detour, merge; + const BlockList blocks{&entry, &detour, &merge}; + entry.AddBranch(&detour); + entry.AddBranch(&merge); + detour.AddBranch(&merge); + entry.terminator.kind = TerminatorKind::ConditionalBranch; + entry.terminator.true_block = reverse ? &merge : &detour; + entry.terminator.false_block = reverse ? &detour : &merge; + entry.terminator.merge_block = &merge; + detour.terminator.kind = TerminatorKind::Branch; + detour.terminator.true_block = &merge; + IREmitter ir(&entry); + const auto address = ir.Emit(ValueOpcode::GetAddressResource, + {Value(0x1000u), Value(0u)}); + const auto read = ir.Emit(ValueOpcode::LoadAddressU32, + {address, Value(24u), Value(0u), Value(true)}); + const auto predicate = ir.INotEqual(U32(read), U32(Value(0u))); + entry.condition = ir.Emit(ValueOpcode::ConditionRef, {predicate}); + ir.Emit(ValueOpcode::Reference, {entry.condition}); + ir.Emit(ValueOpcode::Barrier); + detour.AppendNewInst(ValueOpcode::ControlNop, {}); + if (mode == 1) detour.AppendNewInst(ValueOpcode::Barrier, {}); + if (mode == 2) { + auto& phi = merge.AppendNewInst(ValueOpcode::Phi, {}); + phi.SetFlags(Type::U32); + phi.AddPhiOperand(&entry, Value(1u)); + phi.AddPhiOperand(&detour, Value(2u)); + merge.AppendNewInst(ValueOpcode::ReferenceU32, {Value(&phi)}); + } + if (mode == 3) merge.AppendNewInst(ValueOpcode::ReferenceU32, {read}); + EliminateDeadCode(blocks); + Check(entry.condition.IsImmediate() == (mode == 0 || mode == 3), + "empty branch cleanup changed side effects or a live Phi"); + const auto contains = [&](ValueOpcode op) { + return std::ranges::any_of(entry, [=](const Inst& inst) { + return inst.GetOpcode() == op; + }); + }; + Check(contains(ValueOpcode::LoadAddressU32) == (mode != 0), + "empty branch cleanup lost a shared read or retained a dead read"); + Check(contains(ValueOpcode::Barrier) && entry.ImmSuccessors().size() == 2 && + merge.ImmPredecessors().size() == 2, + "empty branch cleanup changed earlier side effects or CFG edges"); + } + } +} + void TestScalarMemoryUnusedTailDce() { const uint32_t shader[] = { EncodeSmem0(0x0c, 16, 0), @@ -16701,6 +16753,7 @@ int main() { TestSrtWalkerRealSBufferTranslation(); TestScalarMemorySourcesCapturedBeforeWrites(); TestScalarMemoryLoadCrossesIntoVcc(); + TestEmptyBranchScalarReadDce(); TestScalarMemoryUnusedTailDce(); TestResourceTrackingRealDensePatching(); TestDirectTranslationResetsAnalysisState();