summaryrefslogtreecommitdiff
path: root/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2021-02-02 21:07:00 -0300
committerGravatar ameerj2021-07-22 21:51:21 -0400
commit6c4cc0cd062fbbba5349da1108d3c23cb330ca8a (patch)
tree544291931da8a85fafcea71964c77d9278ec7f29 /src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
parentshader: Initial recompiler work (diff)
downloadyuzu-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.cpp155
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
29namespace Shader::Optimization {
30namespace {
31using ValueMap = boost::container::flat_map<IR::Block*, IR::Value, std::less<IR::Block*>>;
32
33struct 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
46IR::Opcode UndefOpcode(IR::Reg) noexcept {
47 return IR::Opcode::Undef32;
48}
49
50IR::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
58class Pass {
59public:
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
72private:
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
123void 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