summaryrefslogtreecommitdiff
path: root/src/shader_recompiler/frontend/maxwell/control_flow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/shader_recompiler/frontend/maxwell/control_flow.cpp')
-rw-r--r--src/shader_recompiler/frontend/maxwell/control_flow.cpp531
1 files changed, 531 insertions, 0 deletions
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.cpp b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
new file mode 100644
index 000000000..fc4dba826
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
@@ -0,0 +1,531 @@
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 <array>
7#include <optional>
8#include <ranges>
9#include <string>
10#include <utility>
11
12#include <fmt/format.h>
13
14#include "shader_recompiler/exception.h"
15#include "shader_recompiler/frontend/maxwell/control_flow.h"
16#include "shader_recompiler/frontend/maxwell/decode.h"
17#include "shader_recompiler/frontend/maxwell/location.h"
18
19namespace Shader::Maxwell::Flow {
20
21static u32 BranchOffset(Location pc, Instruction inst) {
22 return pc.Offset() + inst.branch.Offset() + 8;
23}
24
25static std::array<Block, 2> Split(Block&& block, Location pc, BlockId new_id) {
26 if (pc <= block.begin || pc >= block.end) {
27 throw InvalidArgument("Invalid address to split={}", pc);
28 }
29 return {
30 Block{
31 .begin{block.begin},
32 .end{pc},
33 .end_class{EndClass::Branch},
34 .id{block.id},
35 .stack{block.stack},
36 .cond{true},
37 .branch_true{new_id},
38 .branch_false{UNREACHABLE_BLOCK_ID},
39 },
40 Block{
41 .begin{pc},
42 .end{block.end},
43 .end_class{block.end_class},
44 .id{new_id},
45 .stack{std::move(block.stack)},
46 .cond{block.cond},
47 .branch_true{block.branch_true},
48 .branch_false{block.branch_false},
49 },
50 };
51}
52
53static Token OpcodeToken(Opcode opcode) {
54 switch (opcode) {
55 case Opcode::PBK:
56 case Opcode::BRK:
57 return Token::PBK;
58 case Opcode::PCNT:
59 case Opcode::CONT:
60 return Token::PBK;
61 case Opcode::PEXIT:
62 case Opcode::EXIT:
63 return Token::PEXIT;
64 case Opcode::PLONGJMP:
65 case Opcode::LONGJMP:
66 return Token::PLONGJMP;
67 case Opcode::PRET:
68 case Opcode::RET:
69 case Opcode::CAL:
70 return Token::PRET;
71 case Opcode::SSY:
72 case Opcode::SYNC:
73 return Token::SSY;
74 default:
75 throw InvalidArgument("{}", opcode);
76 }
77}
78
79static bool IsAbsoluteJump(Opcode opcode) {
80 switch (opcode) {
81 case Opcode::JCAL:
82 case Opcode::JMP:
83 case Opcode::JMX:
84 return true;
85 default:
86 return false;
87 }
88}
89
90static bool HasFlowTest(Opcode opcode) {
91 switch (opcode) {
92 case Opcode::BRA:
93 case Opcode::BRX:
94 case Opcode::EXIT:
95 case Opcode::JMP:
96 case Opcode::JMX:
97 case Opcode::BRK:
98 case Opcode::CONT:
99 case Opcode::LONGJMP:
100 case Opcode::RET:
101 case Opcode::SYNC:
102 return true;
103 case Opcode::CAL:
104 case Opcode::JCAL:
105 return false;
106 default:
107 throw InvalidArgument("Invalid branch {}", opcode);
108 }
109}
110
111static std::string Name(const Block& block) {
112 if (block.begin.IsVirtual()) {
113 return fmt::format("\"Virtual {}\"", block.id);
114 } else {
115 return fmt::format("\"{}\"", block.begin);
116 }
117}
118
119void Stack::Push(Token token, Location target) {
120 entries.push_back({
121 .token{token},
122 .target{target},
123 });
124}
125
126std::pair<Location, Stack> Stack::Pop(Token token) const {
127 const std::optional<Location> pc{Peek(token)};
128 if (!pc) {
129 throw LogicError("Token could not be found");
130 }
131 return {*pc, Remove(token)};
132}
133
134std::optional<Location> Stack::Peek(Token token) const {
135 const auto reverse_entries{entries | std::views::reverse};
136 const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
137 if (it == reverse_entries.end()) {
138 return std::nullopt;
139 }
140 return it->target;
141}
142
143Stack Stack::Remove(Token token) const {
144 const auto reverse_entries{entries | std::views::reverse};
145 const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
146 const auto pos{std::distance(reverse_entries.begin(), it)};
147 Stack result;
148 result.entries.insert(result.entries.end(), entries.begin(), entries.end() - pos - 1);
149 return result;
150}
151
152bool Block::Contains(Location pc) const noexcept {
153 return pc >= begin && pc < end;
154}
155
156Function::Function(Location start_address)
157 : entrypoint{start_address}, labels{Label{
158 .address{start_address},
159 .block_id{0},
160 .stack{},
161 }} {}
162
163CFG::CFG(Environment& env_, Location start_address) : env{env_} {
164 functions.emplace_back(start_address);
165 for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
166 while (!functions[function_id].labels.empty()) {
167 Function& function{functions[function_id]};
168 Label label{function.labels.back()};
169 function.labels.pop_back();
170 AnalyzeLabel(function_id, label);
171 }
172 }
173}
174
175void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
176 if (InspectVisitedBlocks(function_id, label)) {
177 // Label address has been visited
178 return;
179 }
180 // Try to find the next block
181 Function* function{&functions[function_id]};
182 Location pc{label.address};
183 const auto next{std::upper_bound(function->blocks.begin(), function->blocks.end(), pc,
184 [function](Location pc, u32 block_index) {
185 return pc < function->blocks_data[block_index].begin;
186 })};
187 const auto next_index{std::distance(function->blocks.begin(), next)};
188 const bool is_last{next == function->blocks.end()};
189 Location next_pc;
190 BlockId next_id{UNREACHABLE_BLOCK_ID};
191 if (!is_last) {
192 next_pc = function->blocks_data[*next].begin;
193 next_id = function->blocks_data[*next].id;
194 }
195 // Insert before the next block
196 Block block{
197 .begin{pc},
198 .end{pc},
199 .end_class{EndClass::Branch},
200 .id{label.block_id},
201 .stack{std::move(label.stack)},
202 .cond{true},
203 .branch_true{UNREACHABLE_BLOCK_ID},
204 .branch_false{UNREACHABLE_BLOCK_ID},
205 };
206 // Analyze instructions until it reaches an already visited block or there's a branch
207 bool is_branch{false};
208 while (is_last || pc < next_pc) {
209 is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
210 if (is_branch) {
211 break;
212 }
213 ++pc;
214 }
215 if (!is_branch) {
216 // If the block finished without a branch,
217 // it means that the next instruction is already visited, jump to it
218 block.end = pc;
219 block.cond = true;
220 block.branch_true = next_id;
221 block.branch_false = UNREACHABLE_BLOCK_ID;
222 }
223 // Function's pointer might be invalid, resolve it again
224 function = &functions[function_id];
225 const u32 new_block_index = static_cast<u32>(function->blocks_data.size());
226 function->blocks.insert(function->blocks.begin() + next_index, new_block_index);
227 function->blocks_data.push_back(std::move(block));
228}
229
230bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
231 const Location pc{label.address};
232 Function& function{functions[function_id]};
233 const auto it{std::ranges::find_if(function.blocks, [&function, pc](u32 block_index) {
234 return function.blocks_data[block_index].Contains(pc);
235 })};
236 if (it == function.blocks.end()) {
237 // Address has not been visited
238 return false;
239 }
240 Block& block{function.blocks_data[*it]};
241 if (block.begin == pc) {
242 throw LogicError("Dangling branch");
243 }
244 const u32 first_index{*it};
245 const u32 second_index{static_cast<u32>(function.blocks_data.size())};
246 const std::array new_indices{first_index, second_index};
247 std::array split_blocks{Split(std::move(block), pc, label.block_id)};
248 function.blocks_data[*it] = std::move(split_blocks[0]);
249 function.blocks_data.push_back(std::move(split_blocks[1]));
250 function.blocks.insert(function.blocks.erase(it), new_indices.begin(), new_indices.end());
251 return true;
252}
253
254CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Location pc) {
255 const Instruction inst{env.ReadInstruction(pc.Offset())};
256 const Opcode opcode{Decode(inst.raw)};
257 switch (opcode) {
258 case Opcode::BRA:
259 case Opcode::BRX:
260 case Opcode::JMP:
261 case Opcode::JMX:
262 case Opcode::RET:
263 if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
264 return AnalysisState::Continue;
265 }
266 switch (opcode) {
267 case Opcode::BRA:
268 case Opcode::JMP:
269 AnalyzeBRA(block, function_id, pc, inst, IsAbsoluteJump(opcode));
270 break;
271 case Opcode::BRX:
272 case Opcode::JMX:
273 AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode));
274 break;
275 case Opcode::RET:
276 block.end_class = EndClass::Return;
277 break;
278 default:
279 break;
280 }
281 block.end = pc;
282 return AnalysisState::Branch;
283 case Opcode::BRK:
284 case Opcode::CONT:
285 case Opcode::LONGJMP:
286 case Opcode::SYNC: {
287 if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
288 return AnalysisState::Continue;
289 }
290 const auto [stack_pc, new_stack]{block.stack.Pop(OpcodeToken(opcode))};
291 block.branch_true = AddLabel(block, new_stack, stack_pc, function_id);
292 block.end = pc;
293 return AnalysisState::Branch;
294 }
295 case Opcode::PBK:
296 case Opcode::PCNT:
297 case Opcode::PEXIT:
298 case Opcode::PLONGJMP:
299 case Opcode::SSY:
300 block.stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
301 return AnalysisState::Continue;
302 case Opcode::EXIT:
303 return AnalyzeEXIT(block, function_id, pc, inst);
304 case Opcode::PRET:
305 throw NotImplementedException("PRET flow analysis");
306 case Opcode::CAL:
307 case Opcode::JCAL: {
308 const bool is_absolute{IsAbsoluteJump(opcode)};
309 const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
310 // Technically CAL pushes into PRET, but that's implicit in the function call for us
311 // Insert the function into the list if it doesn't exist
312 if (std::ranges::find(functions, cal_pc, &Function::entrypoint) == functions.end()) {
313 functions.push_back(cal_pc);
314 }
315 // Handle CAL like a regular instruction
316 break;
317 }
318 default:
319 break;
320 }
321 const Predicate pred{inst.Pred()};
322 if (pred == Predicate{true} || pred == Predicate{false}) {
323 return AnalysisState::Continue;
324 }
325 const IR::Condition cond{static_cast<IR::Pred>(pred.index), pred.negated};
326 AnalyzeCondInst(block, function_id, pc, EndClass::Branch, cond);
327 return AnalysisState::Branch;
328}
329
330void CFG::AnalyzeCondInst(Block& block, FunctionId function_id, Location pc,
331 EndClass insn_end_class, IR::Condition cond) {
332 if (block.begin != pc) {
333 // If the block doesn't start in the conditional instruction
334 // mark it as a label to visit it later
335 block.end = pc;
336 block.cond = true;
337 block.branch_true = AddLabel(block, block.stack, pc, function_id);
338 block.branch_false = UNREACHABLE_BLOCK_ID;
339 return;
340 }
341 // Impersonate the visited block with a virtual block
342 // Jump from this virtual to the real conditional instruction and the next instruction
343 Function& function{functions[function_id]};
344 const BlockId conditional_block_id{++function.current_block_id};
345 function.blocks.push_back(static_cast<u32>(function.blocks_data.size()));
346 Block& virtual_block{function.blocks_data.emplace_back(Block{
347 .begin{}, // Virtual block
348 .end{},
349 .end_class{EndClass::Branch},
350 .id{block.id}, // Impersonating
351 .stack{block.stack},
352 .cond{cond},
353 .branch_true{conditional_block_id},
354 .branch_false{UNREACHABLE_BLOCK_ID},
355 })};
356 // Set the end properties of the conditional instruction and give it a new identity
357 Block& conditional_block{block};
358 conditional_block.end = pc;
359 conditional_block.end_class = insn_end_class;
360 conditional_block.id = conditional_block_id;
361 // Add a label to the instruction after the conditional instruction
362 const BlockId endif_block_id{AddLabel(conditional_block, block.stack, pc + 1, function_id)};
363 // Branch to the next instruction from the virtual block
364 virtual_block.branch_false = endif_block_id;
365 // And branch to it from the conditional instruction if it is a branch
366 if (insn_end_class == EndClass::Branch) {
367 conditional_block.cond = true;
368 conditional_block.branch_true = endif_block_id;
369 conditional_block.branch_false = UNREACHABLE_BLOCK_ID;
370 }
371}
372
373bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst,
374 Opcode opcode) {
375 if (inst.branch.is_cbuf) {
376 throw NotImplementedException("Branch with constant buffer offset");
377 }
378 const Predicate pred{inst.Pred()};
379 if (pred == Predicate{false}) {
380 return false;
381 }
382 const bool has_flow_test{HasFlowTest(opcode)};
383 const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
384 if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
385 block.cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
386 block.branch_false = AddLabel(block, block.stack, pc + 1, function_id);
387 } else {
388 block.cond = true;
389 }
390 return true;
391}
392
393void CFG::AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
394 bool is_absolute) {
395 const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
396 block.branch_true = AddLabel(block, block.stack, bra_pc, function_id);
397}
398
399void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) {
400 throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
401}
402
403void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) {
404 const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
405 // Technically CAL pushes into PRET, but that's implicit in the function call for us
406 // Insert the function to the function list if it doesn't exist
407 const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
408 if (it == functions.end()) {
409 functions.emplace_back(cal_pc);
410 }
411}
412
413CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Location pc,
414 Instruction inst) {
415 const IR::FlowTest flow_test{inst.branch.flow_test};
416 const Predicate pred{inst.Pred()};
417 if (pred == Predicate{false} || flow_test == IR::FlowTest::F) {
418 // EXIT will never be taken
419 return AnalysisState::Continue;
420 }
421 if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
422 if (block.stack.Peek(Token::PEXIT).has_value()) {
423 throw NotImplementedException("Conditional EXIT with PEXIT token");
424 }
425 const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
426 AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
427 return AnalysisState::Branch;
428 }
429 if (const std::optional<Location> exit_pc{block.stack.Peek(Token::PEXIT)}) {
430 const Stack popped_stack{block.stack.Remove(Token::PEXIT)};
431 block.cond = true;
432 block.branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
433 block.branch_false = UNREACHABLE_BLOCK_ID;
434 return AnalysisState::Branch;
435 }
436 block.end = pc;
437 block.end_class = EndClass::Exit;
438 return AnalysisState::Branch;
439}
440
441BlockId CFG::AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id) {
442 Function& function{functions[function_id]};
443 if (block.begin == pc) {
444 return block.id;
445 }
446 const auto target{std::ranges::find(function.blocks_data, pc, &Block::begin)};
447 if (target != function.blocks_data.end()) {
448 return target->id;
449 }
450 const BlockId block_id{++function.current_block_id};
451 function.labels.push_back(Label{
452 .address{pc},
453 .block_id{block_id},
454 .stack{std::move(stack)},
455 });
456 return block_id;
457}
458
459std::string CFG::Dot() const {
460 int node_uid{0};
461
462 std::string dot{"digraph shader {\n"};
463 for (const Function& function : functions) {
464 dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
465 dot += fmt::format("\t\tnode [style=filled];\n");
466 for (const u32 block_index : function.blocks) {
467 const Block& block{function.blocks_data[block_index]};
468 const std::string name{Name(block)};
469 const auto add_branch = [&](BlockId branch_id, bool add_label) {
470 const auto it{std::ranges::find(function.blocks_data, branch_id, &Block::id)};
471 dot += fmt::format("\t\t{}->", name);
472 if (it == function.blocks_data.end()) {
473 dot += fmt::format("\"Unknown label {}\"", branch_id);
474 } else {
475 dot += Name(*it);
476 };
477 if (add_label && block.cond != true && block.cond != false) {
478 dot += fmt::format(" [label=\"{}\"]", block.cond);
479 }
480 dot += '\n';
481 };
482 dot += fmt::format("\t\t{};\n", name);
483 switch (block.end_class) {
484 case EndClass::Branch:
485 if (block.cond != false) {
486 add_branch(block.branch_true, true);
487 }
488 if (block.cond != true) {
489 add_branch(block.branch_false, false);
490 }
491 break;
492 case EndClass::Exit:
493 dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
494 dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n",
495 node_uid);
496 ++node_uid;
497 break;
498 case EndClass::Return:
499 dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
500 dot += fmt::format("\t\tN{} [label=\"Return\"][shape=square][style=stripped];\n",
501 node_uid);
502 ++node_uid;
503 break;
504 case EndClass::Unreachable:
505 dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
506 dot += fmt::format(
507 "\t\tN{} [label=\"Unreachable\"][shape=square][style=stripped];\n", node_uid);
508 ++node_uid;
509 break;
510 }
511 }
512 if (function.entrypoint == 8) {
513 dot += fmt::format("\t\tlabel = \"main\";\n");
514 } else {
515 dot += fmt::format("\t\tlabel = \"Function {}\";\n", function.entrypoint);
516 }
517 dot += "\t}\n";
518 }
519 if (!functions.empty()) {
520 if (functions.front().blocks.empty()) {
521 dot += "Start;\n";
522 } else {
523 dot += fmt::format("\tStart -> {};\n", Name(functions.front().blocks_data.front()));
524 }
525 dot += fmt::format("\tStart [shape=diamond];\n");
526 }
527 dot += "}\n";
528 return dot;
529}
530
531} // namespace Shader::Maxwell::Flow