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