diff options
| author | 2021-01-09 03:30:07 -0300 | |
|---|---|---|
| committer | 2021-07-22 21:51:21 -0400 | |
| commit | 2d48a7b4d0666ad16d03a22d85712617a0849046 (patch) | |
| tree | dd1069afca86f66e77e3438da77421a43adf5091 /src/shader_recompiler/frontend/maxwell/control_flow.cpp | |
| parent | thread_worker: Fix compile time error (diff) | |
| download | yuzu-2d48a7b4d0666ad16d03a22d85712617a0849046.tar.gz yuzu-2d48a7b4d0666ad16d03a22d85712617a0849046.tar.xz yuzu-2d48a7b4d0666ad16d03a22d85712617a0849046.zip | |
shader: Initial recompiler work
Diffstat (limited to 'src/shader_recompiler/frontend/maxwell/control_flow.cpp')
| -rw-r--r-- | src/shader_recompiler/frontend/maxwell/control_flow.cpp | 531 |
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 | |||
| 19 | namespace Shader::Maxwell::Flow { | ||
| 20 | |||
| 21 | static u32 BranchOffset(Location pc, Instruction inst) { | ||
| 22 | return pc.Offset() + inst.branch.Offset() + 8; | ||
| 23 | } | ||
| 24 | |||
| 25 | static 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 | |||
| 53 | static 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 | |||
| 79 | static 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 | |||
| 90 | static 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 | |||
| 111 | static 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 | |||
| 119 | void Stack::Push(Token token, Location target) { | ||
| 120 | entries.push_back({ | ||
| 121 | .token{token}, | ||
| 122 | .target{target}, | ||
| 123 | }); | ||
| 124 | } | ||
| 125 | |||
| 126 | std::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 | |||
| 134 | std::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 | |||
| 143 | Stack 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 | |||
| 152 | bool Block::Contains(Location pc) const noexcept { | ||
| 153 | return pc >= begin && pc < end; | ||
| 154 | } | ||
| 155 | |||
| 156 | Function::Function(Location start_address) | ||
| 157 | : entrypoint{start_address}, labels{Label{ | ||
| 158 | .address{start_address}, | ||
| 159 | .block_id{0}, | ||
| 160 | .stack{}, | ||
| 161 | }} {} | ||
| 162 | |||
| 163 | CFG::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 | |||
| 175 | void 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 | |||
| 230 | bool 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 | |||
| 254 | CFG::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 | |||
| 330 | void 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 | |||
| 373 | bool 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 | |||
| 393 | void 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 | |||
| 399 | void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) { | ||
| 400 | throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX"); | ||
| 401 | } | ||
| 402 | |||
| 403 | void 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 | |||
| 413 | CFG::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 | |||
| 441 | BlockId 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 | |||
| 459 | std::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 | ||