control_flow.cpp 23 KB

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