control_flow.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. // Copyright 2021 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <algorithm>
  5. #include <array>
  6. #include <optional>
  7. #include <ranges>
  8. #include <string>
  9. #include <utility>
  10. #include <fmt/format.h>
  11. #include "shader_recompiler/exception.h"
  12. #include "shader_recompiler/frontend/maxwell/control_flow.h"
  13. #include "shader_recompiler/frontend/maxwell/decode.h"
  14. #include "shader_recompiler/frontend/maxwell/indirect_branch_table_track.h"
  15. #include "shader_recompiler/frontend/maxwell/location.h"
  16. namespace Shader::Maxwell::Flow {
  17. namespace {
  18. struct Compare {
  19. bool operator()(const Block& lhs, Location rhs) const noexcept {
  20. return lhs.begin < rhs;
  21. }
  22. bool operator()(Location lhs, const Block& rhs) const noexcept {
  23. return lhs < rhs.begin;
  24. }
  25. bool operator()(const Block& lhs, const Block& rhs) const noexcept {
  26. return lhs.begin < rhs.begin;
  27. }
  28. };
  29. u32 BranchOffset(Location pc, Instruction inst) {
  30. return pc.Offset() + static_cast<u32>(inst.branch.Offset()) + 8u;
  31. }
  32. void Split(Block* old_block, Block* new_block, Location pc) {
  33. if (pc <= old_block->begin || pc >= old_block->end) {
  34. throw InvalidArgument("Invalid address to split={}", pc);
  35. }
  36. *new_block = Block{};
  37. new_block->begin = pc;
  38. new_block->end = old_block->end;
  39. new_block->end_class = old_block->end_class;
  40. new_block->cond = old_block->cond;
  41. new_block->stack = old_block->stack;
  42. new_block->branch_true = old_block->branch_true;
  43. new_block->branch_false = old_block->branch_false;
  44. new_block->function_call = old_block->function_call;
  45. new_block->return_block = old_block->return_block;
  46. new_block->branch_reg = old_block->branch_reg;
  47. new_block->branch_offset = old_block->branch_offset;
  48. new_block->indirect_branches = std::move(old_block->indirect_branches);
  49. const Location old_begin{old_block->begin};
  50. Stack old_stack{std::move(old_block->stack)};
  51. *old_block = Block{};
  52. old_block->begin = old_begin;
  53. old_block->end = pc;
  54. old_block->end_class = EndClass::Branch;
  55. old_block->cond = IR::Condition(true);
  56. old_block->stack = old_stack;
  57. old_block->branch_true = new_block;
  58. old_block->branch_false = nullptr;
  59. }
  60. Token OpcodeToken(Opcode opcode) {
  61. switch (opcode) {
  62. case Opcode::PBK:
  63. case Opcode::BRK:
  64. return Token::PBK;
  65. case Opcode::PCNT:
  66. case Opcode::CONT:
  67. return Token::PBK;
  68. case Opcode::PEXIT:
  69. case Opcode::EXIT:
  70. return Token::PEXIT;
  71. case Opcode::PLONGJMP:
  72. case Opcode::LONGJMP:
  73. return Token::PLONGJMP;
  74. case Opcode::PRET:
  75. case Opcode::RET:
  76. case Opcode::CAL:
  77. return Token::PRET;
  78. case Opcode::SSY:
  79. case Opcode::SYNC:
  80. return Token::SSY;
  81. default:
  82. throw InvalidArgument("{}", opcode);
  83. }
  84. }
  85. bool IsAbsoluteJump(Opcode opcode) {
  86. switch (opcode) {
  87. case Opcode::JCAL:
  88. case Opcode::JMP:
  89. case Opcode::JMX:
  90. return true;
  91. default:
  92. return false;
  93. }
  94. }
  95. bool HasFlowTest(Opcode opcode) {
  96. switch (opcode) {
  97. case Opcode::BRA:
  98. case Opcode::BRX:
  99. case Opcode::EXIT:
  100. case Opcode::JMP:
  101. case Opcode::JMX:
  102. case Opcode::KIL:
  103. case Opcode::BRK:
  104. case Opcode::CONT:
  105. case Opcode::LONGJMP:
  106. case Opcode::RET:
  107. case Opcode::SYNC:
  108. return true;
  109. case Opcode::CAL:
  110. case Opcode::JCAL:
  111. return false;
  112. default:
  113. throw InvalidArgument("Invalid branch {}", opcode);
  114. }
  115. }
  116. std::string NameOf(const Block& block) {
  117. if (block.begin.IsVirtual()) {
  118. return fmt::format("\"Virtual {}\"", block.begin);
  119. } else {
  120. return fmt::format("\"{}\"", block.begin);
  121. }
  122. }
  123. } // Anonymous namespace
  124. void Stack::Push(Token token, Location target) {
  125. entries.push_back({
  126. .token = token,
  127. .target{target},
  128. });
  129. }
  130. std::pair<Location, Stack> Stack::Pop(Token token) const {
  131. const std::optional<Location> pc{Peek(token)};
  132. if (!pc) {
  133. throw LogicError("Token could not be found");
  134. }
  135. return {*pc, Remove(token)};
  136. }
  137. std::optional<Location> Stack::Peek(Token token) const {
  138. const auto reverse_entries{entries | std::views::reverse};
  139. const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
  140. if (it == reverse_entries.end()) {
  141. return std::nullopt;
  142. }
  143. return it->target;
  144. }
  145. Stack Stack::Remove(Token token) const {
  146. const auto reverse_entries{entries | std::views::reverse};
  147. const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
  148. const auto pos{std::distance(reverse_entries.begin(), it)};
  149. Stack result;
  150. result.entries.insert(result.entries.end(), entries.begin(), entries.end() - pos - 1);
  151. return result;
  152. }
  153. bool Block::Contains(Location pc) const noexcept {
  154. return pc >= begin && pc < end;
  155. }
  156. Function::Function(ObjectPool<Block>& block_pool, Location start_address)
  157. : entrypoint{start_address} {
  158. Label& label{labels.emplace_back()};
  159. label.address = start_address;
  160. label.block = block_pool.Create(Block{});
  161. label.block->begin = start_address;
  162. label.block->end = start_address;
  163. label.block->end_class = EndClass::Branch;
  164. label.block->cond = IR::Condition(true);
  165. label.block->branch_true = nullptr;
  166. label.block->branch_false = nullptr;
  167. }
  168. CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address,
  169. bool exits_to_dispatcher_)
  170. : env{env_}, block_pool{block_pool_}, program_start{start_address}, exits_to_dispatcher{
  171. exits_to_dispatcher_} {
  172. if (exits_to_dispatcher) {
  173. dispatch_block = block_pool.Create(Block{});
  174. dispatch_block->begin = {};
  175. dispatch_block->end = {};
  176. dispatch_block->end_class = EndClass::Exit;
  177. dispatch_block->cond = IR::Condition(true);
  178. dispatch_block->stack = {};
  179. dispatch_block->branch_true = nullptr;
  180. dispatch_block->branch_false = nullptr;
  181. }
  182. functions.emplace_back(block_pool, start_address);
  183. for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
  184. while (!functions[function_id].labels.empty()) {
  185. Function& function{functions[function_id]};
  186. Label label{function.labels.back()};
  187. function.labels.pop_back();
  188. AnalyzeLabel(function_id, label);
  189. }
  190. }
  191. if (exits_to_dispatcher) {
  192. const auto last_block{functions[0].blocks.rbegin()};
  193. dispatch_block->begin = last_block->end + 1;
  194. dispatch_block->end = last_block->end + 1;
  195. functions[0].blocks.insert(*dispatch_block);
  196. }
  197. }
  198. void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
  199. if (InspectVisitedBlocks(function_id, label)) {
  200. // Label address has been visited
  201. return;
  202. }
  203. // Try to find the next block
  204. Function* const function{&functions[function_id]};
  205. Location pc{label.address};
  206. const auto next_it{function->blocks.upper_bound(pc, Compare{})};
  207. const bool is_last{next_it == function->blocks.end()};
  208. Block* const next{is_last ? nullptr : &*next_it};
  209. // Insert before the next block
  210. Block* const block{label.block};
  211. // Analyze instructions until it reaches an already visited block or there's a branch
  212. bool is_branch{false};
  213. while (!next || pc < next->begin) {
  214. is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
  215. if (is_branch) {
  216. break;
  217. }
  218. ++pc;
  219. }
  220. if (!is_branch) {
  221. // If the block finished without a branch,
  222. // it means that the next instruction is already visited, jump to it
  223. block->end = pc;
  224. block->cond = IR::Condition{true};
  225. block->branch_true = next;
  226. block->branch_false = nullptr;
  227. }
  228. // Function's pointer might be invalid, resolve it again
  229. // Insert the new block
  230. functions[function_id].blocks.insert(*block);
  231. }
  232. bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
  233. const Location pc{label.address};
  234. Function& function{functions[function_id]};
  235. const auto it{
  236. std::ranges::find_if(function.blocks, [pc](auto& block) { return block.Contains(pc); })};
  237. if (it == function.blocks.end()) {
  238. // Address has not been visited
  239. return false;
  240. }
  241. Block* const visited_block{&*it};
  242. if (visited_block->begin == pc) {
  243. throw LogicError("Dangling block");
  244. }
  245. Block* const new_block{label.block};
  246. Split(visited_block, new_block, pc);
  247. function.blocks.insert(it, *new_block);
  248. return true;
  249. }
  250. CFG::AnalysisState CFG::AnalyzeInst(Block* block, FunctionId function_id, Location pc) {
  251. const Instruction inst{env.ReadInstruction(pc.Offset())};
  252. const Opcode opcode{Decode(inst.raw)};
  253. switch (opcode) {
  254. case Opcode::BRA:
  255. case Opcode::JMP:
  256. case Opcode::RET:
  257. if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
  258. return AnalysisState::Continue;
  259. }
  260. switch (opcode) {
  261. case Opcode::BRA:
  262. case Opcode::JMP:
  263. AnalyzeBRA(block, function_id, pc, inst, IsAbsoluteJump(opcode));
  264. break;
  265. case Opcode::RET:
  266. block->end_class = EndClass::Return;
  267. break;
  268. default:
  269. break;
  270. }
  271. block->end = pc;
  272. return AnalysisState::Branch;
  273. case Opcode::BRK:
  274. case Opcode::CONT:
  275. case Opcode::LONGJMP:
  276. case Opcode::SYNC: {
  277. if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
  278. return AnalysisState::Continue;
  279. }
  280. const auto [stack_pc, new_stack]{block->stack.Pop(OpcodeToken(opcode))};
  281. block->branch_true = AddLabel(block, new_stack, stack_pc, function_id);
  282. block->end = pc;
  283. return AnalysisState::Branch;
  284. }
  285. case Opcode::KIL: {
  286. const Predicate pred{inst.Pred()};
  287. const auto ir_pred{static_cast<IR::Pred>(pred.index)};
  288. const IR::Condition cond{inst.branch.flow_test, ir_pred, pred.negated};
  289. AnalyzeCondInst(block, function_id, pc, EndClass::Kill, cond);
  290. return AnalysisState::Branch;
  291. }
  292. case Opcode::PBK:
  293. case Opcode::PCNT:
  294. case Opcode::PEXIT:
  295. case Opcode::PLONGJMP:
  296. case Opcode::SSY:
  297. block->stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
  298. return AnalysisState::Continue;
  299. case Opcode::BRX:
  300. case Opcode::JMX:
  301. return AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode), function_id);
  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. const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
  313. const bool exists{it != functions.end()};
  314. const FunctionId call_id{exists ? static_cast<size_t>(std::distance(functions.begin(), it))
  315. : functions.size()};
  316. if (!exists) {
  317. functions.emplace_back(block_pool, cal_pc);
  318. }
  319. block->end_class = EndClass::Call;
  320. block->function_call = call_id;
  321. block->return_block = AddLabel(block, block->stack, pc + 1, function_id);
  322. block->end = pc;
  323. return AnalysisState::Branch;
  324. }
  325. default:
  326. break;
  327. }
  328. const Predicate pred{inst.Pred()};
  329. if (pred == Predicate{true} || pred == Predicate{false}) {
  330. return AnalysisState::Continue;
  331. }
  332. const IR::Condition cond{static_cast<IR::Pred>(pred.index), pred.negated};
  333. AnalyzeCondInst(block, function_id, pc, EndClass::Branch, cond);
  334. return AnalysisState::Branch;
  335. }
  336. void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc,
  337. EndClass insn_end_class, IR::Condition cond) {
  338. if (block->begin != pc) {
  339. // If the block doesn't start in the conditional instruction
  340. // mark it as a label to visit it later
  341. block->end = pc;
  342. block->cond = IR::Condition{true};
  343. block->branch_true = AddLabel(block, block->stack, pc, function_id);
  344. block->branch_false = nullptr;
  345. return;
  346. }
  347. // Create a virtual block and a conditional block
  348. Block* const conditional_block{block_pool.Create()};
  349. Block virtual_block{};
  350. virtual_block.begin = block->begin.Virtual();
  351. virtual_block.end = block->begin.Virtual();
  352. virtual_block.end_class = EndClass::Branch;
  353. virtual_block.stack = block->stack;
  354. virtual_block.cond = cond;
  355. virtual_block.branch_true = conditional_block;
  356. virtual_block.branch_false = nullptr;
  357. // Save the contents of the visited block in the conditional block
  358. *conditional_block = std::move(*block);
  359. // Impersonate the visited block with a virtual block
  360. *block = std::move(virtual_block);
  361. // Set the end properties of the conditional instruction
  362. conditional_block->end = pc + 1;
  363. conditional_block->end_class = insn_end_class;
  364. // Add a label to the instruction after the conditional instruction
  365. Block* const endif_block{AddLabel(conditional_block, block->stack, pc + 1, function_id)};
  366. // Branch to the next instruction from the virtual block
  367. block->branch_false = endif_block;
  368. // And branch to it from the conditional instruction if it is a branch or a kill instruction
  369. // Kill instructions are considered a branch because they demote to a helper invocation and
  370. // execution may continue.
  371. if (insn_end_class == EndClass::Branch || insn_end_class == EndClass::Kill) {
  372. conditional_block->cond = IR::Condition{true};
  373. conditional_block->branch_true = endif_block;
  374. conditional_block->branch_false = nullptr;
  375. }
  376. // Finally insert the condition block into the list of blocks
  377. functions[function_id].blocks.insert(*conditional_block);
  378. }
  379. bool CFG::AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst,
  380. Opcode opcode) {
  381. if (inst.branch.is_cbuf) {
  382. throw NotImplementedException("Branch with constant buffer offset");
  383. }
  384. const Predicate pred{inst.Pred()};
  385. if (pred == Predicate{false}) {
  386. return false;
  387. }
  388. const bool has_flow_test{HasFlowTest(opcode)};
  389. const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
  390. if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
  391. block->cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
  392. block->branch_false = AddLabel(block, block->stack, pc + 1, function_id);
  393. } else {
  394. block->cond = IR::Condition{true};
  395. }
  396. return true;
  397. }
  398. void CFG::AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst,
  399. bool is_absolute) {
  400. const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
  401. block->branch_true = AddLabel(block, block->stack, bra_pc, function_id);
  402. }
  403. CFG::AnalysisState CFG::AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute,
  404. FunctionId function_id) {
  405. const std::optional brx_table{TrackIndirectBranchTable(env, pc, program_start)};
  406. if (!brx_table) {
  407. TrackIndirectBranchTable(env, pc, program_start);
  408. throw NotImplementedException("Failed to track indirect branch");
  409. }
  410. const IR::FlowTest flow_test{inst.branch.flow_test};
  411. const Predicate pred{inst.Pred()};
  412. if (flow_test != IR::FlowTest::T || pred != Predicate{true}) {
  413. throw NotImplementedException("Conditional indirect branch");
  414. }
  415. std::vector<u32> targets;
  416. targets.reserve(brx_table->num_entries);
  417. for (u32 i = 0; i < brx_table->num_entries; ++i) {
  418. u32 target{env.ReadCbufValue(brx_table->cbuf_index, brx_table->cbuf_offset + i * 4)};
  419. if (!is_absolute) {
  420. target += pc.Offset();
  421. }
  422. target += static_cast<u32>(brx_table->branch_offset);
  423. target += 8;
  424. targets.push_back(target);
  425. }
  426. std::ranges::sort(targets);
  427. targets.erase(std::unique(targets.begin(), targets.end()), targets.end());
  428. block->indirect_branches.reserve(targets.size());
  429. for (const u32 target : targets) {
  430. Block* const branch{AddLabel(block, block->stack, target, function_id)};
  431. block->indirect_branches.push_back({
  432. .block = branch,
  433. .address = target,
  434. });
  435. }
  436. block->cond = IR::Condition{true};
  437. block->end = pc + 1;
  438. block->end_class = EndClass::IndirectBranch;
  439. block->branch_reg = brx_table->branch_reg;
  440. block->branch_offset = brx_table->branch_offset + 8;
  441. if (!is_absolute) {
  442. block->branch_offset += pc.Offset();
  443. }
  444. return AnalysisState::Branch;
  445. }
  446. CFG::AnalysisState CFG::AnalyzeEXIT(Block* block, FunctionId function_id, Location pc,
  447. Instruction inst) {
  448. const IR::FlowTest flow_test{inst.branch.flow_test};
  449. const Predicate pred{inst.Pred()};
  450. if (pred == Predicate{false} || flow_test == IR::FlowTest::F) {
  451. // EXIT will never be taken
  452. return AnalysisState::Continue;
  453. }
  454. if (exits_to_dispatcher && function_id != 0) {
  455. throw NotImplementedException("Dispatch EXIT on external function");
  456. }
  457. if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
  458. if (block->stack.Peek(Token::PEXIT).has_value()) {
  459. throw NotImplementedException("Conditional EXIT with PEXIT token");
  460. }
  461. const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
  462. if (exits_to_dispatcher) {
  463. block->end = pc;
  464. block->end_class = EndClass::Branch;
  465. block->cond = cond;
  466. block->branch_true = dispatch_block;
  467. block->branch_false = AddLabel(block, block->stack, pc + 1, function_id);
  468. return AnalysisState::Branch;
  469. }
  470. AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
  471. return AnalysisState::Branch;
  472. }
  473. if (const std::optional<Location> exit_pc{block->stack.Peek(Token::PEXIT)}) {
  474. const Stack popped_stack{block->stack.Remove(Token::PEXIT)};
  475. block->cond = IR::Condition{true};
  476. block->branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
  477. block->branch_false = nullptr;
  478. return AnalysisState::Branch;
  479. }
  480. if (exits_to_dispatcher) {
  481. block->cond = IR::Condition{true};
  482. block->end = pc;
  483. block->end_class = EndClass::Branch;
  484. block->branch_true = dispatch_block;
  485. block->branch_false = nullptr;
  486. return AnalysisState::Branch;
  487. }
  488. block->end = pc + 1;
  489. block->end_class = EndClass::Exit;
  490. return AnalysisState::Branch;
  491. }
  492. Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id) {
  493. Function& function{functions[function_id]};
  494. if (block->begin == pc) {
  495. // Jumps to itself
  496. return block;
  497. }
  498. if (const auto it{function.blocks.find(pc, Compare{})}; it != function.blocks.end()) {
  499. // Block already exists and it has been visited
  500. if (function.blocks.begin() != it) {
  501. // Check if the previous node is the virtual variant of the label
  502. // This won't exist if a virtual node is not needed or it hasn't been visited
  503. // If it hasn't been visited and a virtual node is needed, this will still behave as
  504. // expected because the node impersonated with its virtual node.
  505. const auto prev{std::prev(it)};
  506. if (it->begin.Virtual() == prev->begin) {
  507. return &*prev;
  508. }
  509. }
  510. return &*it;
  511. }
  512. // Make sure we don't insert the same layer twice
  513. const auto label_it{std::ranges::find(function.labels, pc, &Label::address)};
  514. if (label_it != function.labels.end()) {
  515. return label_it->block;
  516. }
  517. Block* const new_block{block_pool.Create()};
  518. new_block->begin = pc;
  519. new_block->end = pc;
  520. new_block->end_class = EndClass::Branch;
  521. new_block->cond = IR::Condition(true);
  522. new_block->stack = stack;
  523. new_block->branch_true = nullptr;
  524. new_block->branch_false = nullptr;
  525. function.labels.push_back(Label{
  526. .address{pc},
  527. .block = new_block,
  528. .stack{std::move(stack)},
  529. });
  530. return new_block;
  531. }
  532. std::string CFG::Dot() const {
  533. int node_uid{0};
  534. std::string dot{"digraph shader {\n"};
  535. for (const Function& function : functions) {
  536. dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
  537. dot += fmt::format("\t\tnode [style=filled];\n");
  538. for (const Block& block : function.blocks) {
  539. const std::string name{NameOf(block)};
  540. const auto add_branch = [&](Block* branch, bool add_label) {
  541. dot += fmt::format("\t\t{}->{}", name, NameOf(*branch));
  542. if (add_label && block.cond != IR::Condition{true} &&
  543. block.cond != IR::Condition{false}) {
  544. dot += fmt::format(" [label=\"{}\"]", block.cond);
  545. }
  546. dot += '\n';
  547. };
  548. dot += fmt::format("\t\t{};\n", name);
  549. switch (block.end_class) {
  550. case EndClass::Branch:
  551. if (block.cond != IR::Condition{false}) {
  552. add_branch(block.branch_true, true);
  553. }
  554. if (block.cond != IR::Condition{true}) {
  555. add_branch(block.branch_false, false);
  556. }
  557. break;
  558. case EndClass::IndirectBranch:
  559. for (const IndirectBranch& branch : block.indirect_branches) {
  560. add_branch(branch.block, false);
  561. }
  562. break;
  563. case EndClass::Call:
  564. dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
  565. dot += fmt::format("\t\tN{}->{};\n", node_uid, NameOf(*block.return_block));
  566. dot += fmt::format("\t\tN{} [label=\"Call {}\"][shape=square][style=stripped];\n",
  567. node_uid, block.function_call);
  568. dot += '\n';
  569. ++node_uid;
  570. break;
  571. case EndClass::Exit:
  572. dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
  573. dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n",
  574. node_uid);
  575. ++node_uid;
  576. break;
  577. case EndClass::Return:
  578. dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
  579. dot += fmt::format("\t\tN{} [label=\"Return\"][shape=square][style=stripped];\n",
  580. node_uid);
  581. ++node_uid;
  582. break;
  583. case EndClass::Kill:
  584. dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
  585. dot += fmt::format("\t\tN{} [label=\"Kill\"][shape=square][style=stripped];\n",
  586. node_uid);
  587. ++node_uid;
  588. break;
  589. }
  590. }
  591. if (function.entrypoint == 8) {
  592. dot += fmt::format("\t\tlabel = \"main\";\n");
  593. } else {
  594. dot += fmt::format("\t\tlabel = \"Function {}\";\n", function.entrypoint);
  595. }
  596. dot += "\t}\n";
  597. }
  598. if (!functions.empty()) {
  599. auto& function{functions.front()};
  600. if (function.blocks.empty()) {
  601. dot += "Start;\n";
  602. } else {
  603. dot += fmt::format("\tStart -> {};\n", NameOf(*function.blocks.begin()));
  604. }
  605. dot += fmt::format("\tStart [shape=diamond];\n");
  606. }
  607. dot += "}\n";
  608. return dot;
  609. }
  610. } // namespace Shader::Maxwell::Flow