diff options
| author | 2021-02-02 21:07:00 -0300 | |
|---|---|---|
| committer | 2021-07-22 21:51:21 -0400 | |
| commit | 6c4cc0cd062fbbba5349da1108d3c23cb330ca8a (patch) | |
| tree | 544291931da8a85fafcea71964c77d9278ec7f29 /src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp | |
| parent | shader: Initial recompiler work (diff) | |
| download | yuzu-6c4cc0cd062fbbba5349da1108d3c23cb330ca8a.tar.gz yuzu-6c4cc0cd062fbbba5349da1108d3c23cb330ca8a.tar.xz yuzu-6c4cc0cd062fbbba5349da1108d3c23cb330ca8a.zip | |
shader: SSA and dominance
Diffstat (limited to 'src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp')
| -rw-r--r-- | src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp new file mode 100644 index 000000000..a4b256a40 --- /dev/null +++ b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | // This file implements the SSA rewriting algorithm proposed in | ||
| 6 | // | ||
| 7 | // Simple and Efficient Construction of Static Single Assignment Form. | ||
| 8 | // Braun M., Buchwald S., Hack S., Leißa R., Mallon C., Zwinkau A. (2013) | ||
| 9 | // In: Jhala R., De Bosschere K. (eds) | ||
| 10 | // Compiler Construction. CC 2013. | ||
| 11 | // Lecture Notes in Computer Science, vol 7791. | ||
| 12 | // Springer, Berlin, Heidelberg | ||
| 13 | // | ||
| 14 | // https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6 | ||
| 15 | // | ||
| 16 | |||
| 17 | #include <map> | ||
| 18 | |||
| 19 | #include <boost/container/flat_map.hpp> | ||
| 20 | |||
| 21 | #include "shader_recompiler/frontend/ir/basic_block.h" | ||
| 22 | #include "shader_recompiler/frontend/ir/function.h" | ||
| 23 | #include "shader_recompiler/frontend/ir/microinstruction.h" | ||
| 24 | #include "shader_recompiler/frontend/ir/opcode.h" | ||
| 25 | #include "shader_recompiler/frontend/ir/pred.h" | ||
| 26 | #include "shader_recompiler/frontend/ir/reg.h" | ||
| 27 | #include "shader_recompiler/ir_opt/passes.h" | ||
| 28 | |||
| 29 | namespace Shader::Optimization { | ||
| 30 | namespace { | ||
| 31 | using ValueMap = boost::container::flat_map<IR::Block*, IR::Value, std::less<IR::Block*>>; | ||
| 32 | |||
| 33 | struct DefTable { | ||
| 34 | [[nodiscard]] ValueMap& operator[](IR::Reg variable) noexcept { | ||
| 35 | return regs[IR::RegIndex(variable)]; | ||
| 36 | } | ||
| 37 | |||
| 38 | [[nodiscard]] ValueMap& operator[](IR::Pred variable) noexcept { | ||
| 39 | return preds[IR::PredIndex(variable)]; | ||
| 40 | } | ||
| 41 | |||
| 42 | std::array<ValueMap, IR::NUM_USER_REGS> regs; | ||
| 43 | std::array<ValueMap, IR::NUM_USER_PREDS> preds; | ||
| 44 | }; | ||
| 45 | |||
| 46 | IR::Opcode UndefOpcode(IR::Reg) noexcept { | ||
| 47 | return IR::Opcode::Undef32; | ||
| 48 | } | ||
| 49 | |||
| 50 | IR::Opcode UndefOpcode(IR::Pred) noexcept { | ||
| 51 | return IR::Opcode::Undef1; | ||
| 52 | } | ||
| 53 | |||
| 54 | [[nodiscard]] bool IsPhi(const IR::Inst& inst) noexcept { | ||
| 55 | return inst.Opcode() == IR::Opcode::Phi; | ||
| 56 | } | ||
| 57 | |||
| 58 | class Pass { | ||
| 59 | public: | ||
| 60 | void WriteVariable(auto variable, IR::Block* block, const IR::Value& value) { | ||
| 61 | current_def[variable].insert_or_assign(block, value); | ||
| 62 | } | ||
| 63 | |||
| 64 | IR::Value ReadVariable(auto variable, IR::Block* block) { | ||
| 65 | auto& def{current_def[variable]}; | ||
| 66 | if (const auto it{def.find(block)}; it != def.end()) { | ||
| 67 | return it->second; | ||
| 68 | } | ||
| 69 | return ReadVariableRecursive(variable, block); | ||
| 70 | } | ||
| 71 | |||
| 72 | private: | ||
| 73 | IR::Value ReadVariableRecursive(auto variable, IR::Block* block) { | ||
| 74 | IR::Value val; | ||
| 75 | if (const std::span preds{block->ImmediatePredecessors()}; preds.size() == 1) { | ||
| 76 | val = ReadVariable(variable, preds.front()); | ||
| 77 | } else { | ||
| 78 | // Break potential cycles with operandless phi | ||
| 79 | val = IR::Value{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)}; | ||
| 80 | WriteVariable(variable, block, val); | ||
| 81 | val = AddPhiOperands(variable, val, block); | ||
| 82 | } | ||
| 83 | WriteVariable(variable, block, val); | ||
| 84 | return val; | ||
| 85 | } | ||
| 86 | |||
| 87 | IR::Value AddPhiOperands(auto variable, const IR::Value& phi, IR::Block* block) { | ||
| 88 | for (IR::Block* const pred : block->ImmediatePredecessors()) { | ||
| 89 | phi.Inst()->AddPhiOperand(pred, ReadVariable(variable, pred)); | ||
| 90 | } | ||
| 91 | return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable)); | ||
| 92 | } | ||
| 93 | |||
| 94 | IR::Value TryRemoveTrivialPhi(const IR::Value& phi, IR::Block* block, IR::Opcode undef_opcode) { | ||
| 95 | IR::Value same; | ||
| 96 | for (const auto& pair : phi.Inst()->PhiOperands()) { | ||
| 97 | const IR::Value& op{pair.second}; | ||
| 98 | if (op == same || op == phi) { | ||
| 99 | // Unique value or self-reference | ||
| 100 | continue; | ||
| 101 | } | ||
| 102 | if (!same.IsEmpty()) { | ||
| 103 | // The phi merges at least two values: not trivial | ||
| 104 | return phi; | ||
| 105 | } | ||
| 106 | same = op; | ||
| 107 | } | ||
| 108 | if (same.IsEmpty()) { | ||
| 109 | // The phi is unreachable or in the start block | ||
| 110 | const auto first_not_phi{std::ranges::find_if_not(block->Instructions(), IsPhi)}; | ||
| 111 | same = IR::Value{&*block->PrependNewInst(first_not_phi, undef_opcode)}; | ||
| 112 | } | ||
| 113 | // Reroute all uses of phi to same and remove phi | ||
| 114 | phi.Inst()->ReplaceUsesWith(same); | ||
| 115 | // TODO: Try to recursively remove all phi users, which might have become trivial | ||
| 116 | return same; | ||
| 117 | } | ||
| 118 | |||
| 119 | DefTable current_def; | ||
| 120 | }; | ||
| 121 | } // Anonymous namespace | ||
| 122 | |||
| 123 | void SsaRewritePass(IR::Function& function) { | ||
| 124 | Pass pass; | ||
| 125 | for (const auto& block : function.blocks) { | ||
| 126 | for (IR::Inst& inst : block->Instructions()) { | ||
| 127 | switch (inst.Opcode()) { | ||
| 128 | case IR::Opcode::SetRegister: | ||
| 129 | if (const IR::Reg reg{inst.Arg(0).Reg()}; reg != IR::Reg::RZ) { | ||
| 130 | pass.WriteVariable(reg, block.get(), inst.Arg(1)); | ||
| 131 | } | ||
| 132 | break; | ||
| 133 | case IR::Opcode::SetPred: | ||
| 134 | if (const IR::Pred pred{inst.Arg(0).Pred()}; pred != IR::Pred::PT) { | ||
| 135 | pass.WriteVariable(pred, block.get(), inst.Arg(1)); | ||
| 136 | } | ||
| 137 | break; | ||
| 138 | case IR::Opcode::GetRegister: | ||
| 139 | if (const IR::Reg reg{inst.Arg(0).Reg()}; reg != IR::Reg::RZ) { | ||
| 140 | inst.ReplaceUsesWith(pass.ReadVariable(reg, block.get())); | ||
| 141 | } | ||
| 142 | break; | ||
| 143 | case IR::Opcode::GetPred: | ||
| 144 | if (const IR::Pred pred{inst.Arg(0).Pred()}; pred != IR::Pred::PT) { | ||
| 145 | inst.ReplaceUsesWith(pass.ReadVariable(pred, block.get())); | ||
| 146 | } | ||
| 147 | break; | ||
| 148 | default: | ||
| 149 | break; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | } // namespace Shader::Optimization | ||