diff options
| author | 2021-07-25 11:39:04 -0700 | |
|---|---|---|
| committer | 2021-07-25 11:39:04 -0700 | |
| commit | 98b26b6e126d4775fdf3f773fe8a8ac808a8ff8f (patch) | |
| tree | 816faa96c2c4d291825063433331a8ea4b3d08f1 /src/shader_recompiler/ir_opt/ssa_rewrite_pass.cpp | |
| parent | Merge pull request #6699 from lat9nq/common-threads (diff) | |
| parent | shader: Support out of bound local memory reads and immediate writes (diff) | |
| download | yuzu-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.cpp | 383 |
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 | |||
| 31 | namespace Shader::Optimization { | ||
| 32 | namespace { | ||
| 33 | struct FlagTag { | ||
| 34 | auto operator<=>(const FlagTag&) const noexcept = default; | ||
| 35 | }; | ||
| 36 | struct ZeroFlagTag : FlagTag {}; | ||
| 37 | struct SignFlagTag : FlagTag {}; | ||
| 38 | struct CarryFlagTag : FlagTag {}; | ||
| 39 | struct OverflowFlagTag : FlagTag {}; | ||
| 40 | |||
| 41 | struct 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 | |||
| 50 | struct IndirectBranchVariable { | ||
| 51 | auto operator<=>(const IndirectBranchVariable&) const noexcept = default; | ||
| 52 | }; | ||
| 53 | |||
| 54 | using Variant = std::variant<IR::Reg, IR::Pred, ZeroFlagTag, SignFlagTag, CarryFlagTag, | ||
| 55 | OverflowFlagTag, GotoVariable, IndirectBranchVariable>; | ||
| 56 | using ValueMap = boost::container::flat_map<IR::Block*, IR::Value>; | ||
| 57 | |||
| 58 | struct 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 | |||
| 124 | IR::Opcode UndefOpcode(IR::Reg) noexcept { | ||
| 125 | return IR::Opcode::UndefU32; | ||
| 126 | } | ||
| 127 | |||
| 128 | IR::Opcode UndefOpcode(IR::Pred) noexcept { | ||
| 129 | return IR::Opcode::UndefU1; | ||
| 130 | } | ||
| 131 | |||
| 132 | IR::Opcode UndefOpcode(const FlagTag&) noexcept { | ||
| 133 | return IR::Opcode::UndefU1; | ||
| 134 | } | ||
| 135 | |||
| 136 | IR::Opcode UndefOpcode(IndirectBranchVariable) noexcept { | ||
| 137 | return IR::Opcode::UndefU32; | ||
| 138 | } | ||
| 139 | |||
| 140 | enum class Status { | ||
| 141 | Start, | ||
| 142 | SetValue, | ||
| 143 | PreparePhiArgument, | ||
| 144 | PushPhiArgument, | ||
| 145 | }; | ||
| 146 | |||
| 147 | template <typename Type> | ||
| 148 | struct 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 | |||
| 160 | class Pass { | ||
| 161 | public: | ||
| 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 | |||
| 254 | private: | ||
| 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 | |||
| 304 | void 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 | |||
| 367 | void 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 | |||
| 375 | void 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 | ||