summaryrefslogtreecommitdiff
path: root/src/shader_recompiler/frontend/maxwell
diff options
context:
space:
mode:
authorGravatar ReinUsesLisp2021-03-14 03:41:05 -0300
committerGravatar ameerj2021-07-22 21:51:23 -0400
commit71f96fa6366dc6dd306a953bca1b958fb32bc55a (patch)
tree12e13f9502e4b9510446c967a831e5d4bacb729e /src/shader_recompiler/frontend/maxwell
parentspirv: Add SignedZeroInfNanPreserve logic (diff)
downloadyuzu-71f96fa6366dc6dd306a953bca1b958fb32bc55a.tar.gz
yuzu-71f96fa6366dc6dd306a953bca1b958fb32bc55a.tar.xz
yuzu-71f96fa6366dc6dd306a953bca1b958fb32bc55a.zip
shader: Implement CAL inlining function calls
Diffstat (limited to 'src/shader_recompiler/frontend/maxwell')
-rw-r--r--src/shader_recompiler/frontend/maxwell/control_flow.cpp78
-rw-r--r--src/shader_recompiler/frontend/maxwell/control_flow.h19
-rw-r--r--src/shader_recompiler/frontend/maxwell/program.cpp71
-rw-r--r--src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp770
-rw-r--r--src/shader_recompiler/frontend/maxwell/structured_control_flow.h24
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/impl.h2
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp4
7 files changed, 869 insertions, 99 deletions
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.cpp b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
index d0dc66330..715c0e92d 100644
--- a/src/shader_recompiler/frontend/maxwell/control_flow.cpp
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
@@ -31,13 +31,12 @@ struct Compare {
31 return lhs.begin < rhs.begin; 31 return lhs.begin < rhs.begin;
32 } 32 }
33}; 33};
34} // Anonymous namespace
35 34
36static u32 BranchOffset(Location pc, Instruction inst) { 35u32 BranchOffset(Location pc, Instruction inst) {
37 return pc.Offset() + inst.branch.Offset() + 8; 36 return pc.Offset() + inst.branch.Offset() + 8;
38} 37}
39 38
40static void Split(Block* old_block, Block* new_block, Location pc) { 39void Split(Block* old_block, Block* new_block, Location pc) {
41 if (pc <= old_block->begin || pc >= old_block->end) { 40 if (pc <= old_block->begin || pc >= old_block->end) {
42 throw InvalidArgument("Invalid address to split={}", pc); 41 throw InvalidArgument("Invalid address to split={}", pc);
43 } 42 }
@@ -49,21 +48,19 @@ static void Split(Block* old_block, Block* new_block, Location pc) {
49 .cond{old_block->cond}, 48 .cond{old_block->cond},
50 .branch_true{old_block->branch_true}, 49 .branch_true{old_block->branch_true},
51 .branch_false{old_block->branch_false}, 50 .branch_false{old_block->branch_false},
52 .ir{nullptr},
53 }; 51 };
54 *old_block = Block{ 52 *old_block = Block{
55 .begin{old_block->begin}, 53 .begin{old_block->begin},
56 .end{pc}, 54 .end{pc},
57 .end_class{EndClass::Branch}, 55 .end_class{EndClass::Branch},
58 .stack{std::move(old_block->stack)}, 56 .stack{std::move(old_block->stack)},
59 .cond{IR::Condition{true}}, 57 .cond{true},
60 .branch_true{new_block}, 58 .branch_true{new_block},
61 .branch_false{nullptr}, 59 .branch_false{nullptr},
62 .ir{nullptr},
63 }; 60 };
64} 61}
65 62
66static Token OpcodeToken(Opcode opcode) { 63Token OpcodeToken(Opcode opcode) {
67 switch (opcode) { 64 switch (opcode) {
68 case Opcode::PBK: 65 case Opcode::PBK:
69 case Opcode::BRK: 66 case Opcode::BRK:
@@ -89,7 +86,7 @@ static Token OpcodeToken(Opcode opcode) {
89 } 86 }
90} 87}
91 88
92static bool IsAbsoluteJump(Opcode opcode) { 89bool IsAbsoluteJump(Opcode opcode) {
93 switch (opcode) { 90 switch (opcode) {
94 case Opcode::JCAL: 91 case Opcode::JCAL:
95 case Opcode::JMP: 92 case Opcode::JMP:
@@ -100,7 +97,7 @@ static bool IsAbsoluteJump(Opcode opcode) {
100 } 97 }
101} 98}
102 99
103static bool HasFlowTest(Opcode opcode) { 100bool HasFlowTest(Opcode opcode) {
104 switch (opcode) { 101 switch (opcode) {
105 case Opcode::BRA: 102 case Opcode::BRA:
106 case Opcode::BRX: 103 case Opcode::BRX:
@@ -121,13 +118,14 @@ static bool HasFlowTest(Opcode opcode) {
121 } 118 }
122} 119}
123 120
124static std::string NameOf(const Block& block) { 121std::string NameOf(const Block& block) {
125 if (block.begin.IsVirtual()) { 122 if (block.begin.IsVirtual()) {
126 return fmt::format("\"Virtual {}\"", block.begin); 123 return fmt::format("\"Virtual {}\"", block.begin);
127 } else { 124 } else {
128 return fmt::format("\"{}\"", block.begin); 125 return fmt::format("\"{}\"", block.begin);
129 } 126 }
130} 127}
128} // Anonymous namespace
131 129
132void Stack::Push(Token token, Location target) { 130void Stack::Push(Token token, Location target) {
133 entries.push_back({ 131 entries.push_back({
@@ -166,26 +164,24 @@ bool Block::Contains(Location pc) const noexcept {
166 return pc >= begin && pc < end; 164 return pc >= begin && pc < end;
167} 165}
168 166
169Function::Function(Location start_address) 167Function::Function(ObjectPool<Block>& block_pool, Location start_address)
170 : entrypoint{start_address}, labels{{ 168 : entrypoint{start_address}, labels{{
171 .address{start_address}, 169 .address{start_address},
172 .block{nullptr}, 170 .block{block_pool.Create(Block{
171 .begin{start_address},
172 .end{start_address},
173 .end_class{EndClass::Branch},
174 .stack{},
175 .cond{true},
176 .branch_true{nullptr},
177 .branch_false{nullptr},
178 })},
173 .stack{}, 179 .stack{},
174 }} {} 180 }} {}
175 181
176CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address) 182CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address)
177 : env{env_}, block_pool{block_pool_} { 183 : env{env_}, block_pool{block_pool_} {
178 functions.emplace_back(start_address); 184 functions.emplace_back(block_pool, start_address);
179 functions.back().labels.back().block = block_pool.Create(Block{
180 .begin{start_address},
181 .end{start_address},
182 .end_class{EndClass::Branch},
183 .stack{},
184 .cond{IR::Condition{true}},
185 .branch_true{nullptr},
186 .branch_false{nullptr},
187 .ir{nullptr},
188 });
189 for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) { 185 for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
190 while (!functions[function_id].labels.empty()) { 186 while (!functions[function_id].labels.empty()) {
191 Function& function{functions[function_id]}; 187 Function& function{functions[function_id]};
@@ -308,11 +304,17 @@ CFG::AnalysisState CFG::AnalyzeInst(Block* block, FunctionId function_id, Locati
308 const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)}; 304 const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
309 // Technically CAL pushes into PRET, but that's implicit in the function call for us 305 // Technically CAL pushes into PRET, but that's implicit in the function call for us
310 // Insert the function into the list if it doesn't exist 306 // Insert the function into the list if it doesn't exist
311 if (std::ranges::find(functions, cal_pc, &Function::entrypoint) == functions.end()) { 307 const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
312 functions.emplace_back(cal_pc); 308 const bool exists{it != functions.end()};
309 const FunctionId call_id{exists ? std::distance(functions.begin(), it) : functions.size()};
310 if (!exists) {
311 functions.emplace_back(block_pool, cal_pc);
313 } 312 }
314 // Handle CAL like a regular instruction 313 block->end_class = EndClass::Call;
315 break; 314 block->function_call = call_id;
315 block->return_block = AddLabel(block, block->stack, pc + 1, function_id);
316 block->end = pc;
317 return AnalysisState::Branch;
316 } 318 }
317 default: 319 default:
318 break; 320 break;
@@ -348,7 +350,6 @@ void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc,
348 .cond{cond}, 350 .cond{cond},
349 .branch_true{conditional_block}, 351 .branch_true{conditional_block},
350 .branch_false{nullptr}, 352 .branch_false{nullptr},
351 .ir{nullptr},
352 }; 353 };
353 // Save the contents of the visited block in the conditional block 354 // Save the contents of the visited block in the conditional block
354 *conditional_block = std::move(*block); 355 *conditional_block = std::move(*block);
@@ -401,16 +402,6 @@ void CFG::AnalyzeBRX(Block*, Location, Instruction, bool is_absolute) {
401 throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX"); 402 throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
402} 403}
403 404
404void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) {
405 const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
406 // Technically CAL pushes into PRET, but that's implicit in the function call for us
407 // Insert the function to the function list if it doesn't exist
408 const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
409 if (it == functions.end()) {
410 functions.emplace_back(cal_pc);
411 }
412}
413
414CFG::AnalysisState CFG::AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, 405CFG::AnalysisState CFG::AnalyzeEXIT(Block* block, FunctionId function_id, Location pc,
415 Instruction inst) { 406 Instruction inst) {
416 const IR::FlowTest flow_test{inst.branch.flow_test}; 407 const IR::FlowTest flow_test{inst.branch.flow_test};
@@ -455,10 +446,9 @@ Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function
455 .end{pc}, 446 .end{pc},
456 .end_class{EndClass::Branch}, 447 .end_class{EndClass::Branch},
457 .stack{stack}, 448 .stack{stack},
458 .cond{IR::Condition{true}}, 449 .cond{true},
459 .branch_true{nullptr}, 450 .branch_true{nullptr},
460 .branch_false{nullptr}, 451 .branch_false{nullptr},
461 .ir{nullptr},
462 })}; 452 })};
463 function.labels.push_back(Label{ 453 function.labels.push_back(Label{
464 .address{pc}, 454 .address{pc},
@@ -495,6 +485,14 @@ std::string CFG::Dot() const {
495 add_branch(block.branch_false, false); 485 add_branch(block.branch_false, false);
496 } 486 }
497 break; 487 break;
488 case EndClass::Call:
489 dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
490 dot += fmt::format("\t\tN{}->{};\n", node_uid, NameOf(*block.return_block));
491 dot += fmt::format("\t\tN{} [label=\"Call {}\"][shape=square][style=stripped];\n",
492 node_uid, block.function_call);
493 dot += '\n';
494 ++node_uid;
495 break;
498 case EndClass::Exit: 496 case EndClass::Exit:
499 dot += fmt::format("\t\t{}->N{};\n", name, node_uid); 497 dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
500 dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n", 498 dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n",
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.h b/src/shader_recompiler/frontend/maxwell/control_flow.h
index 209c9e551..fe74f210f 100644
--- a/src/shader_recompiler/frontend/maxwell/control_flow.h
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.h
@@ -20,16 +20,13 @@
20#include "shader_recompiler/frontend/maxwell/opcodes.h" 20#include "shader_recompiler/frontend/maxwell/opcodes.h"
21#include "shader_recompiler/object_pool.h" 21#include "shader_recompiler/object_pool.h"
22 22
23namespace Shader::IR {
24class Block;
25}
26
27namespace Shader::Maxwell::Flow { 23namespace Shader::Maxwell::Flow {
28 24
29using FunctionId = size_t; 25using FunctionId = size_t;
30 26
31enum class EndClass { 27enum class EndClass {
32 Branch, 28 Branch,
29 Call,
33 Exit, 30 Exit,
34 Return, 31 Return,
35}; 32};
@@ -75,9 +72,14 @@ struct Block : boost::intrusive::set_base_hook<
75 EndClass end_class; 72 EndClass end_class;
76 Stack stack; 73 Stack stack;
77 IR::Condition cond; 74 IR::Condition cond;
78 Block* branch_true; 75 union {
79 Block* branch_false; 76 Block* branch_true;
80 IR::Block* ir; 77 FunctionId function_call;
78 };
79 union {
80 Block* branch_false;
81 Block* return_block;
82 };
81}; 83};
82 84
83struct Label { 85struct Label {
@@ -87,7 +89,7 @@ struct Label {
87}; 89};
88 90
89struct Function { 91struct Function {
90 Function(Location start_address); 92 explicit Function(ObjectPool<Block>& block_pool, Location start_address);
91 93
92 Location entrypoint; 94 Location entrypoint;
93 boost::container::small_vector<Label, 16> labels; 95 boost::container::small_vector<Label, 16> labels;
@@ -137,7 +139,6 @@ private:
137 void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst, 139 void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst,
138 bool is_absolute); 140 bool is_absolute);
139 void AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute); 141 void AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute);
140 void AnalyzeCAL(Location pc, Instruction inst, bool is_absolute);
141 AnalysisState AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, Instruction inst); 142 AnalysisState AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, Instruction inst);
142 143
143 /// Return the branch target block id 144 /// Return the branch target block id
diff --git a/src/shader_recompiler/frontend/maxwell/program.cpp b/src/shader_recompiler/frontend/maxwell/program.cpp
index b270bbccd..8bfa64326 100644
--- a/src/shader_recompiler/frontend/maxwell/program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/program.cpp
@@ -8,67 +8,44 @@
8 8
9#include "shader_recompiler/frontend/ir/basic_block.h" 9#include "shader_recompiler/frontend/ir/basic_block.h"
10#include "shader_recompiler/frontend/ir/post_order.h" 10#include "shader_recompiler/frontend/ir/post_order.h"
11#include "shader_recompiler/frontend/ir/structured_control_flow.h"
12#include "shader_recompiler/frontend/maxwell/program.h" 11#include "shader_recompiler/frontend/maxwell/program.h"
12#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
13#include "shader_recompiler/frontend/maxwell/translate/translate.h" 13#include "shader_recompiler/frontend/maxwell/translate/translate.h"
14#include "shader_recompiler/ir_opt/passes.h" 14#include "shader_recompiler/ir_opt/passes.h"
15 15
16namespace Shader::Maxwell { 16namespace Shader::Maxwell {
17namespace { 17
18IR::BlockList TranslateCode(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, 18static void RemoveUnreachableBlocks(IR::Program& program) {
19 Environment& env, Flow::Function& cfg_function) { 19 // Some blocks might be unreachable if a function call exists unconditionally
20 const size_t num_blocks{cfg_function.blocks.size()}; 20 // If this happens the number of blocks and post order blocks will mismatch
21 std::vector<IR::Block*> blocks(cfg_function.blocks.size()); 21 if (program.blocks.size() == program.post_order_blocks.size()) {
22 std::ranges::for_each(cfg_function.blocks, [&, i = size_t{0}](auto& cfg_block) mutable { 22 return;
23 const u32 begin{cfg_block.begin.Offset()}; 23 }
24 const u32 end{cfg_block.end.Offset()}; 24 const IR::BlockList& post_order{program.post_order_blocks};
25 blocks[i] = block_pool.Create(inst_pool, begin, end); 25 std::erase_if(program.blocks, [&](IR::Block* block) {
26 cfg_block.ir = blocks[i]; 26 return std::ranges::find(post_order, block) == post_order.end();
27 ++i;
28 });
29 std::ranges::for_each(cfg_function.blocks, [&, i = size_t{0}](auto& cfg_block) mutable {
30 IR::Block* const block{blocks[i]};
31 ++i;
32 if (cfg_block.end_class != Flow::EndClass::Branch) {
33 block->SetReturn();
34 } else if (cfg_block.cond == IR::Condition{true}) {
35 block->SetBranch(cfg_block.branch_true->ir);
36 } else if (cfg_block.cond == IR::Condition{false}) {
37 block->SetBranch(cfg_block.branch_false->ir);
38 } else {
39 block->SetBranches(cfg_block.cond, cfg_block.branch_true->ir,
40 cfg_block.branch_false->ir);
41 }
42 }); 27 });
43 return IR::VisitAST(inst_pool, block_pool, blocks,
44 [&](IR::Block* block) { Translate(env, block); });
45} 28}
46} // Anonymous namespace
47 29
48IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, 30IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
49 Environment& env, Flow::CFG& cfg) { 31 Environment& env, Flow::CFG& cfg) {
50 IR::Program program; 32 IR::Program program;
51 auto& functions{program.functions}; 33 program.blocks = VisitAST(inst_pool, block_pool, env, cfg);
52 functions.reserve(cfg.Functions().size()); 34 program.post_order_blocks = PostOrder(program.blocks);
53 for (Flow::Function& cfg_function : cfg.Functions()) { 35 RemoveUnreachableBlocks(program);
54 functions.push_back(IR::Function{ 36
55 .blocks{TranslateCode(inst_pool, block_pool, env, cfg_function)}, 37 // Replace instructions before the SSA rewrite
56 .post_order_blocks{},
57 });
58 }
59 Optimization::LowerFp16ToFp32(program); 38 Optimization::LowerFp16ToFp32(program);
60 for (IR::Function& function : functions) { 39
61 function.post_order_blocks = PostOrder(function.blocks); 40 Optimization::SsaRewritePass(program);
62 Optimization::SsaRewritePass(function.post_order_blocks); 41
63 }
64 Optimization::GlobalMemoryToStorageBufferPass(program); 42 Optimization::GlobalMemoryToStorageBufferPass(program);
65 Optimization::TexturePass(env, program); 43 Optimization::TexturePass(env, program);
66 for (IR::Function& function : functions) { 44
67 Optimization::PostOrderInvoke(Optimization::ConstantPropagationPass, function); 45 Optimization::ConstantPropagationPass(program);
68 Optimization::PostOrderInvoke(Optimization::DeadCodeEliminationPass, function); 46 Optimization::DeadCodeEliminationPass(program);
69 Optimization::IdentityRemovalPass(function); 47 Optimization::IdentityRemovalPass(program);
70 Optimization::VerificationPass(function); 48 Optimization::VerificationPass(program);
71 }
72 Optimization::CollectShaderInfoPass(program); 49 Optimization::CollectShaderInfoPass(program);
73 return program; 50 return program;
74} 51}
diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
new file mode 100644
index 000000000..5f5d9cf17
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp
@@ -0,0 +1,770 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <memory>
7#include <ranges>
8#include <string>
9#include <unordered_map>
10#include <utility>
11#include <vector>
12
13#include <fmt/format.h>
14
15#include <boost/intrusive/list.hpp>
16
17#include "shader_recompiler/environment.h"
18#include "shader_recompiler/frontend/ir/basic_block.h"
19#include "shader_recompiler/frontend/ir/ir_emitter.h"
20#include "shader_recompiler/frontend/maxwell/structured_control_flow.h"
21#include "shader_recompiler/frontend/maxwell/translate/translate.h"
22#include "shader_recompiler/object_pool.h"
23
24namespace Shader::Maxwell {
25namespace {
26struct Statement;
27
28// Use normal_link because we are not guaranteed to destroy the tree in order
29using ListBaseHook =
30 boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>;
31
32using Tree = boost::intrusive::list<Statement,
33 // Allow using Statement without a definition
34 boost::intrusive::base_hook<ListBaseHook>,
35 // Avoid linear complexity on splice, size is never called
36 boost::intrusive::constant_time_size<false>>;
37using Node = Tree::iterator;
38using ConstNode = Tree::const_iterator;
39
40enum class StatementType {
41 Code,
42 Goto,
43 Label,
44 If,
45 Loop,
46 Break,
47 Return,
48 Function,
49 Identity,
50 Not,
51 Or,
52 SetVariable,
53 Variable,
54};
55
56bool HasChildren(StatementType type) {
57 switch (type) {
58 case StatementType::If:
59 case StatementType::Loop:
60 case StatementType::Function:
61 return true;
62 default:
63 return false;
64 }
65}
66
67struct Goto {};
68struct Label {};
69struct If {};
70struct Loop {};
71struct Break {};
72struct Return {};
73struct FunctionTag {};
74struct Identity {};
75struct Not {};
76struct Or {};
77struct SetVariable {};
78struct Variable {};
79
80#ifdef _MSC_VER
81#pragma warning(push)
82#pragma warning(disable : 26495) // Always initialize a member variable, expected in Statement
83#endif
84struct Statement : ListBaseHook {
85 Statement(IR::Block* code_, Statement* up_) : code{code_}, up{up_}, type{StatementType::Code} {}
86 Statement(Goto, Statement* cond_, Node label_, Statement* up_)
87 : label{label_}, cond{cond_}, up{up_}, type{StatementType::Goto} {}
88 Statement(Label, u32 id_, Statement* up_) : id{id_}, up{up_}, type{StatementType::Label} {}
89 Statement(If, Statement* cond_, Tree&& children_, Statement* up_)
90 : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::If} {}
91 Statement(Loop, Statement* cond_, Tree&& children_, Statement* up_)
92 : children{std::move(children_)}, cond{cond_}, up{up_}, type{StatementType::Loop} {}
93 Statement(Break, Statement* cond_, Statement* up_)
94 : cond{cond_}, up{up_}, type{StatementType::Break} {}
95 Statement(Return) : type{StatementType::Return} {}
96 Statement(FunctionTag) : children{}, type{StatementType::Function} {}
97 Statement(Identity, IR::Condition cond_) : guest_cond{cond_}, type{StatementType::Identity} {}
98 Statement(Not, Statement* op_) : op{op_}, type{StatementType::Not} {}
99 Statement(Or, Statement* op_a_, Statement* op_b_)
100 : op_a{op_a_}, op_b{op_b_}, type{StatementType::Or} {}
101 Statement(SetVariable, u32 id_, Statement* op_, Statement* up_)
102 : op{op_}, id{id_}, up{up_}, type{StatementType::SetVariable} {}
103 Statement(Variable, u32 id_) : id{id_}, type{StatementType::Variable} {}
104
105 ~Statement() {
106 if (HasChildren(type)) {
107 std::destroy_at(&children);
108 }
109 }
110
111 union {
112 IR::Block* code;
113 Node label;
114 Tree children;
115 IR::Condition guest_cond;
116 Statement* op;
117 Statement* op_a;
118 };
119 union {
120 Statement* cond;
121 Statement* op_b;
122 u32 id;
123 };
124 Statement* up{};
125 StatementType type;
126};
127#ifdef _MSC_VER
128#pragma warning(pop)
129#endif
130
131std::string DumpExpr(const Statement* stmt) {
132 switch (stmt->type) {
133 case StatementType::Identity:
134 return fmt::format("{}", stmt->guest_cond);
135 case StatementType::Not:
136 return fmt::format("!{}", DumpExpr(stmt->op));
137 case StatementType::Or:
138 return fmt::format("{} || {}", DumpExpr(stmt->op_a), DumpExpr(stmt->op_b));
139 case StatementType::Variable:
140 return fmt::format("goto_L{}", stmt->id);
141 default:
142 return "<invalid type>";
143 }
144}
145
146std::string DumpTree(const Tree& tree, u32 indentation = 0) {
147 std::string ret;
148 std::string indent(indentation, ' ');
149 for (auto stmt = tree.begin(); stmt != tree.end(); ++stmt) {
150 switch (stmt->type) {
151 case StatementType::Code:
152 ret += fmt::format("{} Block {:04x};\n", indent, stmt->code->LocationBegin());
153 break;
154 case StatementType::Goto:
155 ret += fmt::format("{} if ({}) goto L{};\n", indent, DumpExpr(stmt->cond),
156 stmt->label->id);
157 break;
158 case StatementType::Label:
159 ret += fmt::format("{}L{}:\n", indent, stmt->id);
160 break;
161 case StatementType::If:
162 ret += fmt::format("{} if ({}) {{\n", indent, DumpExpr(stmt->cond));
163 ret += DumpTree(stmt->children, indentation + 4);
164 ret += fmt::format("{} }}\n", indent);
165 break;
166 case StatementType::Loop:
167 ret += fmt::format("{} do {{\n", indent);
168 ret += DumpTree(stmt->children, indentation + 4);
169 ret += fmt::format("{} }} while ({});\n", indent, DumpExpr(stmt->cond));
170 break;
171 case StatementType::Break:
172 ret += fmt::format("{} if ({}) break;\n", indent, DumpExpr(stmt->cond));
173 break;
174 case StatementType::Return:
175 ret += fmt::format("{} return;\n", indent);
176 break;
177 case StatementType::SetVariable:
178 ret += fmt::format("{} goto_L{} = {};\n", indent, stmt->id, DumpExpr(stmt->op));
179 break;
180 case StatementType::Function:
181 case StatementType::Identity:
182 case StatementType::Not:
183 case StatementType::Or:
184 case StatementType::Variable:
185 throw LogicError("Statement can't be printed");
186 }
187 }
188 return ret;
189}
190
191bool HasNode(const Tree& tree, ConstNode stmt) {
192 const auto end{tree.end()};
193 for (auto it = tree.begin(); it != end; ++it) {
194 if (it == stmt || (HasChildren(it->type) && HasNode(it->children, stmt))) {
195 return true;
196 }
197 }
198 return false;
199}
200
201Node FindStatementWithLabel(Tree& tree, ConstNode goto_stmt) {
202 const ConstNode label_stmt{goto_stmt->label};
203 const ConstNode end{tree.end()};
204 for (auto it = tree.begin(); it != end; ++it) {
205 if (it == label_stmt || (HasChildren(it->type) && HasNode(it->children, label_stmt))) {
206 return it;
207 }
208 }
209 throw LogicError("Lift label not in tree");
210}
211
212void SanitizeNoBreaks(const Tree& tree) {
213 if (std::ranges::find(tree, StatementType::Break, &Statement::type) != tree.end()) {
214 throw NotImplementedException("Capturing statement with break nodes");
215 }
216}
217
218size_t Level(Node stmt) {
219 size_t level{0};
220 Statement* node{stmt->up};
221 while (node) {
222 ++level;
223 node = node->up;
224 }
225 return level;
226}
227
228bool IsDirectlyRelated(Node goto_stmt, Node label_stmt) {
229 const size_t goto_level{Level(goto_stmt)};
230 const size_t label_level{Level(label_stmt)};
231 size_t min_level;
232 size_t max_level;
233 Node min;
234 Node max;
235 if (label_level < goto_level) {
236 min_level = label_level;
237 max_level = goto_level;
238 min = label_stmt;
239 max = goto_stmt;
240 } else { // goto_level < label_level
241 min_level = goto_level;
242 max_level = label_level;
243 min = goto_stmt;
244 max = label_stmt;
245 }
246 while (max_level > min_level) {
247 --max_level;
248 max = max->up;
249 }
250 return min->up == max->up;
251}
252
253bool IsIndirectlyRelated(Node goto_stmt, Node label_stmt) {
254 return goto_stmt->up != label_stmt->up && !IsDirectlyRelated(goto_stmt, label_stmt);
255}
256
257bool SearchNode(const Tree& tree, ConstNode stmt, size_t& offset) {
258 ++offset;
259
260 const auto end = tree.end();
261 for (ConstNode it = tree.begin(); it != end; ++it) {
262 ++offset;
263 if (stmt == it) {
264 return true;
265 }
266 if (HasChildren(it->type) && SearchNode(it->children, stmt, offset)) {
267 return true;
268 }
269 }
270 return false;
271}
272
273class GotoPass {
274public:
275 explicit GotoPass(Flow::CFG& cfg, ObjectPool<IR::Inst>& inst_pool_,
276 ObjectPool<IR::Block>& block_pool_, ObjectPool<Statement>& stmt_pool)
277 : inst_pool{inst_pool_}, block_pool{block_pool_}, pool{stmt_pool} {
278 std::vector gotos{BuildTree(cfg)};
279 for (const Node& goto_stmt : gotos | std::views::reverse) {
280 RemoveGoto(goto_stmt);
281 }
282 }
283
284 Statement& RootStatement() noexcept {
285 return root_stmt;
286 }
287
288private:
289 void RemoveGoto(Node goto_stmt) {
290 // Force goto_stmt and label_stmt to be directly related
291 const Node label_stmt{goto_stmt->label};
292 if (IsIndirectlyRelated(goto_stmt, label_stmt)) {
293 // Move goto_stmt out using outward-movement transformation until it becomes
294 // directly related to label_stmt
295 while (!IsDirectlyRelated(goto_stmt, label_stmt)) {
296 goto_stmt = MoveOutward(goto_stmt);
297 }
298 }
299 // Force goto_stmt and label_stmt to be siblings
300 if (IsDirectlyRelated(goto_stmt, label_stmt)) {
301 const size_t label_level{Level(label_stmt)};
302 size_t goto_level{Level(goto_stmt)};
303 if (goto_level > label_level) {
304 // Move goto_stmt out of its level using outward-movement transformations
305 while (goto_level > label_level) {
306 goto_stmt = MoveOutward(goto_stmt);
307 --goto_level;
308 }
309 } else { // Level(goto_stmt) < Level(label_stmt)
310 if (Offset(goto_stmt) > Offset(label_stmt)) {
311 // Lift goto_stmt to above stmt containing label_stmt using goto-lifting
312 // transformations
313 goto_stmt = Lift(goto_stmt);
314 }
315 // Move goto_stmt into label_stmt's level using inward-movement transformation
316 while (goto_level < label_level) {
317 goto_stmt = MoveInward(goto_stmt);
318 ++goto_level;
319 }
320 }
321 }
322 // TODO: Remove this
323 {
324 Node it{goto_stmt};
325 bool sibling{false};
326 do {
327 sibling |= it == label_stmt;
328 --it;
329 } while (it != goto_stmt->up->children.begin());
330 while (it != goto_stmt->up->children.end()) {
331 sibling |= it == label_stmt;
332 ++it;
333 }
334 if (!sibling) {
335 throw LogicError("Not siblings");
336 }
337 }
338 // goto_stmt and label_stmt are guaranteed to be siblings, eliminate
339 if (std::next(goto_stmt) == label_stmt) {
340 // Simply eliminate the goto if the label is next to it
341 goto_stmt->up->children.erase(goto_stmt);
342 } else if (Offset(goto_stmt) < Offset(label_stmt)) {
343 // Eliminate goto_stmt with a conditional
344 EliminateAsConditional(goto_stmt, label_stmt);
345 } else {
346 // Eliminate goto_stmt with a loop
347 EliminateAsLoop(goto_stmt, label_stmt);
348 }
349 }
350
351 std::vector<Node> BuildTree(Flow::CFG& cfg) {
352 u32 label_id{0};
353 std::vector<Node> gotos;
354 Flow::Function& first_function{cfg.Functions().front()};
355 BuildTree(cfg, first_function, label_id, gotos, root_stmt.children.end(), std::nullopt);
356 return gotos;
357 }
358
359 void BuildTree(Flow::CFG& cfg, Flow::Function& function, u32& label_id,
360 std::vector<Node>& gotos, Node function_insert_point,
361 std::optional<Node> return_label) {
362 Statement* const false_stmt{pool.Create(Identity{}, IR::Condition{false})};
363 Tree& root{root_stmt.children};
364 std::unordered_map<Flow::Block*, Node> local_labels;
365 local_labels.reserve(function.blocks.size());
366
367 for (Flow::Block& block : function.blocks) {
368 Statement* const label{pool.Create(Label{}, label_id, &root_stmt)};
369 const Node label_it{root.insert(function_insert_point, *label)};
370 local_labels.emplace(&block, label_it);
371 ++label_id;
372 }
373 for (Flow::Block& block : function.blocks) {
374 const Node label{local_labels.at(&block)};
375 // Insertion point
376 const Node ip{std::next(label)};
377
378 // Reset goto variables before the first block and after its respective label
379 const auto make_reset_variable{[&]() -> Statement& {
380 return *pool.Create(SetVariable{}, label->id, false_stmt, &root_stmt);
381 }};
382 root.push_front(make_reset_variable());
383 root.insert(ip, make_reset_variable());
384
385 const u32 begin_offset{block.begin.Offset()};
386 const u32 end_offset{block.end.Offset()};
387 IR::Block* const ir_block{block_pool.Create(inst_pool, begin_offset, end_offset)};
388 root.insert(ip, *pool.Create(ir_block, &root_stmt));
389
390 switch (block.end_class) {
391 case Flow::EndClass::Branch: {
392 Statement* const always_cond{pool.Create(Identity{}, IR::Condition{true})};
393 if (block.cond == IR::Condition{true}) {
394 const Node true_label{local_labels.at(block.branch_true)};
395 gotos.push_back(
396 root.insert(ip, *pool.Create(Goto{}, always_cond, true_label, &root_stmt)));
397 } else if (block.cond == IR::Condition{false}) {
398 const Node false_label{local_labels.at(block.branch_false)};
399 gotos.push_back(root.insert(
400 ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
401 } else {
402 const Node true_label{local_labels.at(block.branch_true)};
403 const Node false_label{local_labels.at(block.branch_false)};
404 Statement* const true_cond{pool.Create(Identity{}, block.cond)};
405 gotos.push_back(
406 root.insert(ip, *pool.Create(Goto{}, true_cond, true_label, &root_stmt)));
407 gotos.push_back(root.insert(
408 ip, *pool.Create(Goto{}, always_cond, false_label, &root_stmt)));
409 }
410 break;
411 }
412 case Flow::EndClass::Call: {
413 Flow::Function& call{cfg.Functions()[block.function_call]};
414 const Node call_return_label{local_labels.at(block.return_block)};
415 BuildTree(cfg, call, label_id, gotos, ip, call_return_label);
416 break;
417 }
418 case Flow::EndClass::Exit:
419 root.insert(ip, *pool.Create(Return{}));
420 break;
421 case Flow::EndClass::Return: {
422 Statement* const always_cond{pool.Create(Identity{}, block.cond)};
423 auto goto_stmt{pool.Create(Goto{}, always_cond, return_label.value(), &root_stmt)};
424 gotos.push_back(root.insert(ip, *goto_stmt));
425 break;
426 }
427 }
428 }
429 }
430
431 void UpdateTreeUp(Statement* tree) {
432 for (Statement& stmt : tree->children) {
433 stmt.up = tree;
434 }
435 }
436
437 void EliminateAsConditional(Node goto_stmt, Node label_stmt) {
438 Tree& body{goto_stmt->up->children};
439 Tree if_body;
440 if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_stmt);
441 Statement* const cond{pool.Create(Not{}, goto_stmt->cond)};
442 Statement* const if_stmt{pool.Create(If{}, cond, std::move(if_body), goto_stmt->up)};
443 UpdateTreeUp(if_stmt);
444 body.insert(goto_stmt, *if_stmt);
445 body.erase(goto_stmt);
446 }
447
448 void EliminateAsLoop(Node goto_stmt, Node label_stmt) {
449 Tree& body{goto_stmt->up->children};
450 Tree loop_body;
451 loop_body.splice(loop_body.begin(), body, label_stmt, goto_stmt);
452 Statement* const cond{goto_stmt->cond};
453 Statement* const loop{pool.Create(Loop{}, cond, std::move(loop_body), goto_stmt->up)};
454 UpdateTreeUp(loop);
455 body.insert(goto_stmt, *loop);
456 body.erase(goto_stmt);
457 }
458
459 [[nodiscard]] Node MoveOutward(Node goto_stmt) {
460 switch (goto_stmt->up->type) {
461 case StatementType::If:
462 return MoveOutwardIf(goto_stmt);
463 case StatementType::Loop:
464 return MoveOutwardLoop(goto_stmt);
465 default:
466 throw LogicError("Invalid outward movement");
467 }
468 }
469
470 [[nodiscard]] Node MoveInward(Node goto_stmt) {
471 Statement* const parent{goto_stmt->up};
472 Tree& body{parent->children};
473 const Node label_nested_stmt{FindStatementWithLabel(body, goto_stmt)};
474 const Node label{goto_stmt->label};
475 const u32 label_id{label->id};
476
477 Statement* const goto_cond{goto_stmt->cond};
478 Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
479 body.insert(goto_stmt, *set_var);
480
481 Tree if_body;
482 if_body.splice(if_body.begin(), body, std::next(goto_stmt), label_nested_stmt);
483 Statement* const variable{pool.Create(Variable{}, label_id)};
484 Statement* const neg_var{pool.Create(Not{}, variable)};
485 if (!if_body.empty()) {
486 Statement* const if_stmt{pool.Create(If{}, neg_var, std::move(if_body), parent)};
487 UpdateTreeUp(if_stmt);
488 body.insert(goto_stmt, *if_stmt);
489 }
490 body.erase(goto_stmt);
491
492 switch (label_nested_stmt->type) {
493 case StatementType::If:
494 // Update nested if condition
495 label_nested_stmt->cond = pool.Create(Or{}, variable, label_nested_stmt->cond);
496 break;
497 case StatementType::Loop:
498 break;
499 default:
500 throw LogicError("Invalid inward movement");
501 }
502 Tree& nested_tree{label_nested_stmt->children};
503 Statement* const new_goto{pool.Create(Goto{}, variable, label, &*label_nested_stmt)};
504 return nested_tree.insert(nested_tree.begin(), *new_goto);
505 }
506
507 [[nodiscard]] Node Lift(Node goto_stmt) {
508 Statement* const parent{goto_stmt->up};
509 Tree& body{parent->children};
510 const Node label{goto_stmt->label};
511 const u32 label_id{label->id};
512 const Node label_nested_stmt{FindStatementWithLabel(body, goto_stmt)};
513 const auto type{label_nested_stmt->type};
514
515 Tree loop_body;
516 loop_body.splice(loop_body.begin(), body, label_nested_stmt, goto_stmt);
517 SanitizeNoBreaks(loop_body);
518 Statement* const variable{pool.Create(Variable{}, label_id)};
519 Statement* const loop_stmt{pool.Create(Loop{}, variable, std::move(loop_body), parent)};
520 UpdateTreeUp(loop_stmt);
521 const Node loop_node{body.insert(goto_stmt, *loop_stmt)};
522
523 Statement* const new_goto{pool.Create(Goto{}, variable, label, loop_stmt)};
524 loop_stmt->children.push_front(*new_goto);
525 const Node new_goto_node{loop_stmt->children.begin()};
526
527 Statement* const set_var{pool.Create(SetVariable{}, label_id, goto_stmt->cond, loop_stmt)};
528 loop_stmt->children.push_back(*set_var);
529
530 body.erase(goto_stmt);
531 return new_goto_node;
532 }
533
534 Node MoveOutwardIf(Node goto_stmt) {
535 const Node parent{Tree::s_iterator_to(*goto_stmt->up)};
536 Tree& body{parent->children};
537 const u32 label_id{goto_stmt->label->id};
538 Statement* const goto_cond{goto_stmt->cond};
539 Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, &*parent)};
540 body.insert(goto_stmt, *set_goto_var);
541
542 Tree if_body;
543 if_body.splice(if_body.begin(), body, std::next(goto_stmt), body.end());
544 if_body.pop_front();
545 Statement* const cond{pool.Create(Variable{}, label_id)};
546 Statement* const neg_cond{pool.Create(Not{}, cond)};
547 Statement* const if_stmt{pool.Create(If{}, neg_cond, std::move(if_body), &*parent)};
548 UpdateTreeUp(if_stmt);
549 body.insert(goto_stmt, *if_stmt);
550
551 body.erase(goto_stmt);
552
553 Statement* const new_cond{pool.Create(Variable{}, label_id)};
554 Statement* const new_goto{pool.Create(Goto{}, new_cond, goto_stmt->label, parent->up)};
555 Tree& parent_tree{parent->up->children};
556 return parent_tree.insert(std::next(parent), *new_goto);
557 }
558
559 Node MoveOutwardLoop(Node goto_stmt) {
560 Statement* const parent{goto_stmt->up};
561 Tree& body{parent->children};
562 const u32 label_id{goto_stmt->label->id};
563 Statement* const goto_cond{goto_stmt->cond};
564 Statement* const set_goto_var{pool.Create(SetVariable{}, label_id, goto_cond, parent)};
565 Statement* const cond{pool.Create(Variable{}, label_id)};
566 Statement* const break_stmt{pool.Create(Break{}, cond, parent)};
567 body.insert(goto_stmt, *set_goto_var);
568 body.insert(goto_stmt, *break_stmt);
569 body.erase(goto_stmt);
570
571 const Node loop{Tree::s_iterator_to(*goto_stmt->up)};
572 Statement* const new_goto_cond{pool.Create(Variable{}, label_id)};
573 Statement* const new_goto{pool.Create(Goto{}, new_goto_cond, goto_stmt->label, loop->up)};
574 Tree& parent_tree{loop->up->children};
575 return parent_tree.insert(std::next(loop), *new_goto);
576 }
577
578 size_t Offset(ConstNode stmt) const {
579 size_t offset{0};
580 if (!SearchNode(root_stmt.children, stmt, offset)) {
581 throw LogicError("Node not found in tree");
582 }
583 return offset;
584 }
585
586 ObjectPool<IR::Inst>& inst_pool;
587 ObjectPool<IR::Block>& block_pool;
588 ObjectPool<Statement>& pool;
589 Statement root_stmt{FunctionTag{}};
590};
591
592IR::Block* TryFindForwardBlock(const Statement& stmt) {
593 const Tree& tree{stmt.up->children};
594 const ConstNode end{tree.cend()};
595 ConstNode forward_node{std::next(Tree::s_iterator_to(stmt))};
596 while (forward_node != end && !HasChildren(forward_node->type)) {
597 if (forward_node->type == StatementType::Code) {
598 return forward_node->code;
599 }
600 ++forward_node;
601 }
602 return nullptr;
603}
604
605[[nodiscard]] IR::U1 VisitExpr(IR::IREmitter& ir, const Statement& stmt) {
606 switch (stmt.type) {
607 case StatementType::Identity:
608 return ir.Condition(stmt.guest_cond);
609 case StatementType::Not:
610 return ir.LogicalNot(IR::U1{VisitExpr(ir, *stmt.op)});
611 case StatementType::Or:
612 return ir.LogicalOr(VisitExpr(ir, *stmt.op_a), VisitExpr(ir, *stmt.op_b));
613 case StatementType::Variable:
614 return ir.GetGotoVariable(stmt.id);
615 default:
616 throw NotImplementedException("Statement type {}", stmt.type);
617 }
618}
619
620class TranslatePass {
621public:
622 TranslatePass(ObjectPool<IR::Inst>& inst_pool_, ObjectPool<IR::Block>& block_pool_,
623 ObjectPool<Statement>& stmt_pool_, Environment& env_, Statement& root_stmt,
624 IR::BlockList& block_list_)
625 : stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_}, env{env_},
626 block_list{block_list_} {
627 Visit(root_stmt, nullptr, nullptr);
628 }
629
630private:
631 void Visit(Statement& parent, IR::Block* continue_block, IR::Block* break_block) {
632 Tree& tree{parent.children};
633 IR::Block* current_block{nullptr};
634
635 for (auto it = tree.begin(); it != tree.end(); ++it) {
636 Statement& stmt{*it};
637 switch (stmt.type) {
638 case StatementType::Label:
639 // Labels can be ignored
640 break;
641 case StatementType::Code: {
642 if (current_block && current_block != stmt.code) {
643 IR::IREmitter{*current_block}.Branch(stmt.code);
644 }
645 current_block = stmt.code;
646 Translate(env, stmt.code);
647 block_list.push_back(stmt.code);
648 break;
649 }
650 case StatementType::SetVariable: {
651 if (!current_block) {
652 current_block = MergeBlock(parent, stmt);
653 }
654 IR::IREmitter ir{*current_block};
655 ir.SetGotoVariable(stmt.id, VisitExpr(ir, *stmt.op));
656 break;
657 }
658 case StatementType::If: {
659 if (!current_block) {
660 current_block = block_pool.Create(inst_pool);
661 block_list.push_back(current_block);
662 }
663 IR::Block* const merge_block{MergeBlock(parent, stmt)};
664
665 // Visit children
666 const size_t first_block_index{block_list.size()};
667 Visit(stmt, merge_block, break_block);
668
669 // Implement if header block
670 IR::Block* const first_if_block{block_list.at(first_block_index)};
671 IR::IREmitter ir{*current_block};
672 const IR::U1 cond{VisitExpr(ir, *stmt.cond)};
673 ir.SelectionMerge(merge_block);
674 ir.BranchConditional(cond, first_if_block, merge_block);
675
676 current_block = merge_block;
677 break;
678 }
679 case StatementType::Loop: {
680 IR::Block* const loop_header_block{block_pool.Create(inst_pool)};
681 if (current_block) {
682 IR::IREmitter{*current_block}.Branch(loop_header_block);
683 }
684 block_list.push_back(loop_header_block);
685
686 IR::Block* const new_continue_block{block_pool.Create(inst_pool)};
687 IR::Block* const merge_block{MergeBlock(parent, stmt)};
688
689 // Visit children
690 const size_t first_block_index{block_list.size()};
691 Visit(stmt, new_continue_block, merge_block);
692
693 // The continue block is located at the end of the loop
694 block_list.push_back(new_continue_block);
695
696 // Implement loop header block
697 IR::Block* const first_loop_block{block_list.at(first_block_index)};
698 IR::IREmitter ir{*loop_header_block};
699 ir.LoopMerge(merge_block, new_continue_block);
700 ir.Branch(first_loop_block);
701
702 // Implement continue block
703 IR::IREmitter continue_ir{*new_continue_block};
704 const IR::U1 continue_cond{VisitExpr(continue_ir, *stmt.cond)};
705 continue_ir.BranchConditional(continue_cond, ir.block, merge_block);
706
707 current_block = merge_block;
708 break;
709 }
710 case StatementType::Break: {
711 if (!current_block) {
712 current_block = block_pool.Create(inst_pool);
713 block_list.push_back(current_block);
714 }
715 IR::Block* const skip_block{MergeBlock(parent, stmt)};
716
717 IR::IREmitter ir{*current_block};
718 ir.BranchConditional(VisitExpr(ir, *stmt.cond), break_block, skip_block);
719
720 current_block = skip_block;
721 break;
722 }
723 case StatementType::Return: {
724 if (!current_block) {
725 current_block = block_pool.Create(inst_pool);
726 block_list.push_back(current_block);
727 }
728 IR::IREmitter{*current_block}.Return();
729 current_block = nullptr;
730 break;
731 }
732 default:
733 throw NotImplementedException("Statement type {}", stmt.type);
734 }
735 }
736 if (current_block && continue_block) {
737 IR::IREmitter{*current_block}.Branch(continue_block);
738 }
739 }
740
741 IR::Block* MergeBlock(Statement& parent, Statement& stmt) {
742 if (IR::Block* const block{TryFindForwardBlock(stmt)}) {
743 return block;
744 }
745 // Create a merge block we can visit later
746 IR::Block* const block{block_pool.Create(inst_pool)};
747 Statement* const merge_stmt{stmt_pool.Create(block, &parent)};
748 parent.children.insert(std::next(Tree::s_iterator_to(stmt)), *merge_stmt);
749 return block;
750 }
751
752 ObjectPool<Statement>& stmt_pool;
753 ObjectPool<IR::Inst>& inst_pool;
754 ObjectPool<IR::Block>& block_pool;
755 Environment& env;
756 IR::BlockList& block_list;
757};
758} // Anonymous namespace
759
760IR::BlockList VisitAST(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool,
761 Environment& env, Flow::CFG& cfg) {
762 ObjectPool<Statement> stmt_pool{64};
763 GotoPass goto_pass{cfg, inst_pool, block_pool, stmt_pool};
764 Statement& root{goto_pass.RootStatement()};
765 IR::BlockList block_list;
766 TranslatePass{inst_pool, block_pool, stmt_pool, env, root, block_list};
767 return block_list;
768}
769
770} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.h b/src/shader_recompiler/frontend/maxwell/structured_control_flow.h
new file mode 100644
index 000000000..e4797291e
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.h
@@ -0,0 +1,24 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <span>
9
10#include <boost/intrusive/list.hpp>
11
12#include "shader_recompiler/environment.h"
13#include "shader_recompiler/frontend/ir/basic_block.h"
14#include "shader_recompiler/frontend/ir/microinstruction.h"
15#include "shader_recompiler/frontend/maxwell/control_flow.h"
16#include "shader_recompiler/object_pool.h"
17
18namespace Shader::Maxwell {
19
20[[nodiscard]] IR::BlockList VisitAST(ObjectPool<IR::Inst>& inst_pool,
21 ObjectPool<IR::Block>& block_pool, Environment& env,
22 Flow::CFG& cfg);
23
24} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
index c6253c40c..45d6f5e06 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
@@ -62,7 +62,7 @@ public:
62 void BRA(u64 insn); 62 void BRA(u64 insn);
63 void BRK(u64 insn); 63 void BRK(u64 insn);
64 void BRX(u64 insn); 64 void BRX(u64 insn);
65 void CAL(u64 insn); 65 void CAL();
66 void CCTL(u64 insn); 66 void CCTL(u64 insn);
67 void CCTLL(u64 insn); 67 void CCTLL(u64 insn);
68 void CONT(u64 insn); 68 void CONT(u64 insn);
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
index 01ecbb4cc..92da5c7e8 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
@@ -65,8 +65,8 @@ void TranslatorVisitor::BRX(u64) {
65 ThrowNotImplemented(Opcode::BRX); 65 ThrowNotImplemented(Opcode::BRX);
66} 66}
67 67
68void TranslatorVisitor::CAL(u64) { 68void TranslatorVisitor::CAL() {
69 ThrowNotImplemented(Opcode::CAL); 69 // CAL is a no-op
70} 70}
71 71
72void TranslatorVisitor::CCTL(u64) { 72void TranslatorVisitor::CCTL(u64) {