diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp index 631446cf7..4a2564f47 100644 --- a/src/shader_recompiler/frontend/ir/microinstruction.cpp +++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp @@ -326,6 +326,11 @@ void Inst::AddPhiOperand(Block* predecessor, const Value& value) { phi_args.emplace_back(predecessor, value); } +void Inst::ErasePhiOperand(size_t index) { + const auto operand_it{phi_args.begin() + static_cast(index)}; + phi_args.erase(operand_it); +} + void Inst::OrderPhiArgs() { if (op != Opcode::Phi) { throw LogicError("{} is not a Phi instruction", op); diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h index 947579852..14f6e55bc 100644 --- a/src/shader_recompiler/frontend/ir/value.h +++ b/src/shader_recompiler/frontend/ir/value.h @@ -179,9 +179,13 @@ public: /// Get a pointer to the block of a phi argument. [[nodiscard]] Block* PhiBlock(size_t index) const; + /// Add phi operand to a phi instruction. void AddPhiOperand(Block* predecessor, const Value& value); + // Erase the phi operand at the given index. + void ErasePhiOperand(size_t index); + /// Orders the Phi arguments from farthest away to nearest. void OrderPhiArgs(); diff --git a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp index 400836301..6697fde85 100644 --- a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp +++ b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp @@ -2,25 +2,105 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include + +#include + #include "shader_recompiler/frontend/ir/basic_block.h" #include "shader_recompiler/frontend/ir/value.h" #include "shader_recompiler/ir_opt/passes.h" namespace Shader::Optimization { - -void DeadCodeEliminationPass(IR::Program& program) { +namespace { +template +void DeadInstElimination(IR::Block* const block) { // We iterate over the instructions in reverse order. // This is because removing an instruction reduces the number of uses for earlier instructions. - for (IR::Block* const block : program.post_order_blocks) { - auto it{block->end()}; - while (it != block->begin()) { - --it; - if (!it->HasUses() && !it->MayHaveSideEffects()) { - it->Invalidate(); - it = block->Instructions().erase(it); + auto it{block->end()}; + while (it != block->begin()) { + --it; + if constexpr (TEST_USES) { + if (it->HasUses() || it->MayHaveSideEffects()) { + continue; + } + } + it->Invalidate(); + it = block->Instructions().erase(it); + } +} + +void DeletedPhiArgElimination(IR::Program& program, std::span dead_blocks) { + for (IR::Block* const block : program.blocks) { + for (IR::Inst& phi : *block) { + if (!IR::IsPhi(phi)) { + continue; + } + for (size_t i = 0; i < phi.NumArgs(); ++i) { + if (std::ranges::find(dead_blocks, phi.PhiBlock(i)) == dead_blocks.end()) { + continue; + } + // Phi operand at this index is an unreachable block + phi.ErasePhiOperand(i); + --i; } } } } +void DeadBranchElimination(IR::Program& program) { + boost::container::small_vector dead_blocks; + const auto begin_it{program.syntax_list.begin()}; + for (auto node_it = begin_it; node_it != program.syntax_list.end(); ++node_it) { + if (node_it->type != IR::AbstractSyntaxNode::Type::If) { + continue; + } + IR::Inst* const cond_ref{node_it->data.if_node.cond.Inst()}; + const IR::U1 cond{cond_ref->Arg(0)}; + if (!cond.IsImmediate()) { + continue; + } + if (cond.U1()) { + continue; + } + // False immediate condition. Remove condition ref, erase the entire branch. + cond_ref->Invalidate(); + // Account for nested if-statements within the if(false) branch + u32 nested_ifs{1u}; + while (node_it->type != IR::AbstractSyntaxNode::Type::EndIf || nested_ifs > 0) { + node_it = program.syntax_list.erase(node_it); + switch (node_it->type) { + case IR::AbstractSyntaxNode::Type::If: + ++nested_ifs; + break; + case IR::AbstractSyntaxNode::Type::EndIf: + --nested_ifs; + break; + case IR::AbstractSyntaxNode::Type::Block: { + IR::Block* const block{node_it->data.block}; + DeadInstElimination(block); + dead_blocks.push_back(block); + break; + } + default: + break; + } + } + // Erase EndIf node of the if(false) branch + node_it = program.syntax_list.erase(node_it); + // Account for loop increment + --node_it; + } + if (!dead_blocks.empty()) { + DeletedPhiArgElimination(program, std::span(dead_blocks.data(), dead_blocks.size())); + } +} +} // namespace + +void DeadCodeEliminationPass(IR::Program& program) { + DeadBranchElimination(program); + for (IR::Block* const block : program.post_order_blocks) { + DeadInstElimination(block); + } +} + } // namespace Shader::Optimization