summaryrefslogtreecommitdiff
path: root/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
diff options
context:
space:
mode:
authorGravatar bunnei2021-07-25 11:39:04 -0700
committerGravatar GitHub2021-07-25 11:39:04 -0700
commit98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f (patch)
tree816faa96c2c4d291825063433331a8ea4b3d08f1 /src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
parentMerge pull request #6699 from lat9nq/common-threads (diff)
parentshader: Support out of bound local memory reads and immediate writes (diff)
downloadyuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.gz
yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.tar.xz
yuzu-98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f.zip
Merge pull request #6585 from ameerj/hades
Shader Decompiler Rewrite
Diffstat (limited to 'src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp')
-rw-r--r--src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp383
1 files changed, 383 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..53145fb5e
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp
@@ -0,0 +1,383 @@
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., Leiba 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 <span>
18#include <variant>
19#include <vector>
20
21#include <boost/container/flat_map.hpp>
22#include <boost/container/flat_set.hpp>
23
24#include "shader_recompiler/frontend/ir/basic_block.h"
25#include "shader_recompiler/frontend/ir/opcodes.h"
26#include "shader_recompiler/frontend/ir/pred.h"
27#include "shader_recompiler/frontend/ir/reg.h"
28#include "shader_recompiler/frontend/ir/value.h"
29#include "shader_recompiler/ir_opt/passes.h"
30
31namespace Shader::Optimization {
32namespace {
33struct FlagTag {
34 auto operator<=>(const FlagTag&) const noexcept = default;
35};
36struct ZeroFlagTag : FlagTag {};
37struct SignFlagTag : FlagTag {};
38struct CarryFlagTag : FlagTag {};
39struct OverflowFlagTag : FlagTag {};
40
41struct GotoVariable : FlagTag {
42 GotoVariable() = default;
43 explicit GotoVariable(u32 index_) : index{index_} {}
44
45 auto operator<=>(const GotoVariable&) const noexcept = default;
46
47 u32 index;
48};
49
50struct IndirectBranchVariable {
51 auto operator<=>(const IndirectBranchVariable&) const noexcept = default;
52};
53
54using Variant = std::variant<IR::Reg, IR::Pred, ZeroFlagTag, SignFlagTag, CarryFlagTag,
55 OverflowFlagTag, GotoVariable, IndirectBranchVariable>;
56using ValueMap = boost::container::flat_map<IR::Block*, IR::Value>;
57
58struct DefTable {
59 const IR::Value& Def(IR::Block* block, IR::Reg variable) {
60 return block->SsaRegValue(variable);
61 }
62 void SetDef(IR::Block* block, IR::Reg variable, const IR::Value& value) {
63 block->SetSsaRegValue(variable, value);
64 }
65
66 const IR::Value& Def(IR::Block* block, IR::Pred variable) {
67 return preds[IR::PredIndex(variable)][block];
68 }
69 void SetDef(IR::Block* block, IR::Pred variable, const IR::Value& value) {
70 preds[IR::PredIndex(variable)].insert_or_assign(block, value);
71 }
72
73 const IR::Value& Def(IR::Block* block, GotoVariable variable) {
74 return goto_vars[variable.index][block];
75 }
76 void SetDef(IR::Block* block, GotoVariable variable, const IR::Value& value) {
77 goto_vars[variable.index].insert_or_assign(block, value);
78 }
79
80 const IR::Value& Def(IR::Block* block, IndirectBranchVariable) {
81 return indirect_branch_var[block];
82 }
83 void SetDef(IR::Block* block, IndirectBranchVariable, const IR::Value& value) {
84 indirect_branch_var.insert_or_assign(block, value);
85 }
86
87 const IR::Value& Def(IR::Block* block, ZeroFlagTag) {
88 return zero_flag[block];
89 }
90 void SetDef(IR::Block* block, ZeroFlagTag, const IR::Value& value) {
91 zero_flag.insert_or_assign(block, value);
92 }
93
94 const IR::Value& Def(IR::Block* block, SignFlagTag) {
95 return sign_flag[block];
96 }
97 void SetDef(IR::Block* block, SignFlagTag, const IR::Value& value) {
98 sign_flag.insert_or_assign(block, value);
99 }
100
101 const IR::Value& Def(IR::Block* block, CarryFlagTag) {
102 return carry_flag[block];
103 }
104 void SetDef(IR::Block* block, CarryFlagTag, const IR::Value& value) {
105 carry_flag.insert_or_assign(block, value);
106 }
107
108 const IR::Value& Def(IR::Block* block, OverflowFlagTag) {
109 return overflow_flag[block];
110 }
111 void SetDef(IR::Block* block, OverflowFlagTag, const IR::Value& value) {
112 overflow_flag.insert_or_assign(block, value);
113 }
114
115 std::array<ValueMap, IR::NUM_USER_PREDS> preds;
116 boost::container::flat_map<u32, ValueMap> goto_vars;
117 ValueMap indirect_branch_var;
118 ValueMap zero_flag;
119 ValueMap sign_flag;
120 ValueMap carry_flag;
121 ValueMap overflow_flag;
122};
123
124IR::Opcode UndefOpcode(IR::Reg) noexcept {
125 return IR::Opcode::UndefU32;
126}
127
128IR::Opcode UndefOpcode(IR::Pred) noexcept {
129 return IR::Opcode::UndefU1;
130}
131
132IR::Opcode UndefOpcode(const FlagTag&) noexcept {
133 return IR::Opcode::UndefU1;
134}
135
136IR::Opcode UndefOpcode(IndirectBranchVariable) noexcept {
137 return IR::Opcode::UndefU32;
138}
139
140enum class Status {
141 Start,
142 SetValue,
143 PreparePhiArgument,
144 PushPhiArgument,
145};
146
147template <typename Type>
148struct ReadState {
149 ReadState(IR::Block* block_) : block{block_} {}
150 ReadState() = default;
151
152 IR::Block* block{};
153 IR::Value result{};
154 IR::Inst* phi{};
155 IR::Block* const* pred_it{};
156 IR::Block* const* pred_end{};
157 Status pc{Status::Start};
158};
159
160class Pass {
161public:
162 template <typename Type>
163 void WriteVariable(Type variable, IR::Block* block, const IR::Value& value) {
164 current_def.SetDef(block, variable, value);
165 }
166
167 template <typename Type>
168 IR::Value ReadVariable(Type variable, IR::Block* root_block) {
169 boost::container::small_vector<ReadState<Type>, 64> stack{
170 ReadState<Type>(nullptr),
171 ReadState<Type>(root_block),
172 };
173 const auto prepare_phi_operand{[&] {
174 if (stack.back().pred_it == stack.back().pred_end) {
175 IR::Inst* const phi{stack.back().phi};
176 IR::Block* const block{stack.back().block};
177 const IR::Value result{TryRemoveTrivialPhi(*phi, block, UndefOpcode(variable))};
178 stack.pop_back();
179 stack.back().result = result;
180 WriteVariable(variable, block, result);
181 } else {
182 IR::Block* const imm_pred{*stack.back().pred_it};
183 stack.back().pc = Status::PushPhiArgument;
184 stack.emplace_back(imm_pred);
185 }
186 }};
187 do {
188 IR::Block* const block{stack.back().block};
189 switch (stack.back().pc) {
190 case Status::Start: {
191 if (const IR::Value& def = current_def.Def(block, variable); !def.IsEmpty()) {
192 stack.back().result = def;
193 } else if (!block->IsSsaSealed()) {
194 // Incomplete CFG
195 IR::Inst* phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
196 phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
197
198 incomplete_phis[block].insert_or_assign(variable, phi);
199 stack.back().result = IR::Value{&*phi};
200 } else if (const std::span imm_preds = block->ImmPredecessors();
201 imm_preds.size() == 1) {
202 // Optimize the common case of one predecessor: no phi needed
203 stack.back().pc = Status::SetValue;
204 stack.emplace_back(imm_preds.front());
205 break;
206 } else {
207 // Break potential cycles with operandless phi
208 IR::Inst* const phi{&*block->PrependNewInst(block->begin(), IR::Opcode::Phi)};
209 phi->SetFlags(IR::TypeOf(UndefOpcode(variable)));
210
211 WriteVariable(variable, block, IR::Value{phi});
212
213 stack.back().phi = phi;
214 stack.back().pred_it = imm_preds.data();
215 stack.back().pred_end = imm_preds.data() + imm_preds.size();
216 prepare_phi_operand();
217 break;
218 }
219 }
220 [[fallthrough]];
221 case Status::SetValue: {
222 const IR::Value result{stack.back().result};
223 WriteVariable(variable, block, result);
224 stack.pop_back();
225 stack.back().result = result;
226 break;
227 }
228 case Status::PushPhiArgument: {
229 IR::Inst* const phi{stack.back().phi};
230 phi->AddPhiOperand(*stack.back().pred_it, stack.back().result);
231 ++stack.back().pred_it;
232 }
233 [[fallthrough]];
234 case Status::PreparePhiArgument:
235 prepare_phi_operand();
236 break;
237 }
238 } while (stack.size() > 1);
239 return stack.back().result;
240 }
241
242 void SealBlock(IR::Block* block) {
243 const auto it{incomplete_phis.find(block)};
244 if (it != incomplete_phis.end()) {
245 for (auto& pair : it->second) {
246 auto& variant{pair.first};
247 auto& phi{pair.second};
248 std::visit([&](auto& variable) { AddPhiOperands(variable, *phi, block); }, variant);
249 }
250 }
251 block->SsaSeal();
252 }
253
254private:
255 template <typename Type>
256 IR::Value AddPhiOperands(Type variable, IR::Inst& phi, IR::Block* block) {
257 for (IR::Block* const imm_pred : block->ImmPredecessors()) {
258 phi.AddPhiOperand(imm_pred, ReadVariable(variable, imm_pred));
259 }
260 return TryRemoveTrivialPhi(phi, block, UndefOpcode(variable));
261 }
262
263 IR::Value TryRemoveTrivialPhi(IR::Inst& phi, IR::Block* block, IR::Opcode undef_opcode) {
264 IR::Value same;
265 const size_t num_args{phi.NumArgs()};
266 for (size_t arg_index = 0; arg_index < num_args; ++arg_index) {
267 const IR::Value& op{phi.Arg(arg_index)};
268 if (op.Resolve() == same.Resolve() || op == IR::Value{&phi}) {
269 // Unique value or self-reference
270 continue;
271 }
272 if (!same.IsEmpty()) {
273 // The phi merges at least two values: not trivial
274 return IR::Value{&phi};
275 }
276 same = op;
277 }
278 // Remove the phi node from the block, it will be reinserted
279 IR::Block::InstructionList& list{block->Instructions()};
280 list.erase(IR::Block::InstructionList::s_iterator_to(phi));
281
282 // Find the first non-phi instruction and use it as an insertion point
283 IR::Block::iterator reinsert_point{std::ranges::find_if_not(list, IR::IsPhi)};
284 if (same.IsEmpty()) {
285 // The phi is unreachable or in the start block
286 // Insert an undefined instruction and make it the phi node replacement
287 // The "phi" node reinsertion point is specified after this instruction
288 reinsert_point = block->PrependNewInst(reinsert_point, undef_opcode);
289 same = IR::Value{&*reinsert_point};
290 ++reinsert_point;
291 }
292 // Reinsert the phi node and reroute all its uses to the "same" value
293 list.insert(reinsert_point, phi);
294 phi.ReplaceUsesWith(same);
295 // TODO: Try to recursively remove all phi users, which might have become trivial
296 return same;
297 }
298
299 boost::container::flat_map<IR::Block*, boost::container::flat_map<Variant, IR::Inst*>>
300 incomplete_phis;
301 DefTable current_def;
302};
303
304void VisitInst(Pass& pass, IR::Block* block, IR::Inst& inst) {
305 switch (inst.GetOpcode()) {
306 case IR::Opcode::SetRegister:
307 if (const IR::Reg reg{inst.Arg(0).Reg()}; reg != IR::Reg::RZ) {
308 pass.WriteVariable(reg, block, inst.Arg(1));
309 }
310 break;
311 case IR::Opcode::SetPred:
312 if (const IR::Pred pred{inst.Arg(0).Pred()}; pred != IR::Pred::PT) {
313 pass.WriteVariable(pred, block, inst.Arg(1));
314 }
315 break;
316 case IR::Opcode::SetGotoVariable:
317 pass.WriteVariable(GotoVariable{inst.Arg(0).U32()}, block, inst.Arg(1));
318 break;
319 case IR::Opcode::SetIndirectBranchVariable:
320 pass.WriteVariable(IndirectBranchVariable{}, block, inst.Arg(0));
321 break;
322 case IR::Opcode::SetZFlag:
323 pass.WriteVariable(ZeroFlagTag{}, block, inst.Arg(0));
324 break;
325 case IR::Opcode::SetSFlag:
326 pass.WriteVariable(SignFlagTag{}, block, inst.Arg(0));
327 break;
328 case IR::Opcode::SetCFlag:
329 pass.WriteVariable(CarryFlagTag{}, block, inst.Arg(0));
330 break;
331 case IR::Opcode::SetOFlag:
332 pass.WriteVariable(OverflowFlagTag{}, block, inst.Arg(0));
333 break;
334 case IR::Opcode::GetRegister:
335 if (const IR::Reg reg{inst.Arg(0).Reg()}; reg != IR::Reg::RZ) {
336 inst.ReplaceUsesWith(pass.ReadVariable(reg, block));
337 }
338 break;
339 case IR::Opcode::GetPred:
340 if (const IR::Pred pred{inst.Arg(0).Pred()}; pred != IR::Pred::PT) {
341 inst.ReplaceUsesWith(pass.ReadVariable(pred, block));
342 }
343 break;
344 case IR::Opcode::GetGotoVariable:
345 inst.ReplaceUsesWith(pass.ReadVariable(GotoVariable{inst.Arg(0).U32()}, block));
346 break;
347 case IR::Opcode::GetIndirectBranchVariable:
348 inst.ReplaceUsesWith(pass.ReadVariable(IndirectBranchVariable{}, block));
349 break;
350 case IR::Opcode::GetZFlag:
351 inst.ReplaceUsesWith(pass.ReadVariable(ZeroFlagTag{}, block));
352 break;
353 case IR::Opcode::GetSFlag:
354 inst.ReplaceUsesWith(pass.ReadVariable(SignFlagTag{}, block));
355 break;
356 case IR::Opcode::GetCFlag:
357 inst.ReplaceUsesWith(pass.ReadVariable(CarryFlagTag{}, block));
358 break;
359 case IR::Opcode::GetOFlag:
360 inst.ReplaceUsesWith(pass.ReadVariable(OverflowFlagTag{}, block));
361 break;
362 default:
363 break;
364 }
365}
366
367void VisitBlock(Pass& pass, IR::Block* block) {
368 for (IR::Inst& inst : block->Instructions()) {
369 VisitInst(pass, block, inst);
370 }
371 pass.SealBlock(block);
372}
373} // Anonymous namespace
374
375void SsaRewritePass(IR::Program& program) {
376 Pass pass;
377 const auto end{program.post_order_blocks.rend()};
378 for (auto block = program.post_order_blocks.rbegin(); block != end; ++block) {
379 VisitBlock(pass, *block);
380 }
381}
382
383} // namespace Shader::Optimization