control_flow.cpp 24 KB

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