diff options
Diffstat (limited to 'src/shader_recompiler/frontend/maxwell')
10 files changed, 238 insertions, 477 deletions
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.cpp b/src/shader_recompiler/frontend/maxwell/control_flow.cpp index 21ee98137..e766b555b 100644 --- a/src/shader_recompiler/frontend/maxwell/control_flow.cpp +++ b/src/shader_recompiler/frontend/maxwell/control_flow.cpp | |||
| @@ -17,38 +17,49 @@ | |||
| 17 | #include "shader_recompiler/frontend/maxwell/location.h" | 17 | #include "shader_recompiler/frontend/maxwell/location.h" |
| 18 | 18 | ||
| 19 | namespace Shader::Maxwell::Flow { | 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 | } // Anonymous namespace | ||
| 20 | 35 | ||
| 21 | static u32 BranchOffset(Location pc, Instruction inst) { | 36 | static u32 BranchOffset(Location pc, Instruction inst) { |
| 22 | return pc.Offset() + inst.branch.Offset() + 8; | 37 | return pc.Offset() + inst.branch.Offset() + 8; |
| 23 | } | 38 | } |
| 24 | 39 | ||
| 25 | static std::array<Block, 2> Split(Block&& block, Location pc, BlockId new_id) { | 40 | static void Split(Block* old_block, Block* new_block, Location pc) { |
| 26 | if (pc <= block.begin || pc >= block.end) { | 41 | if (pc <= old_block->begin || pc >= old_block->end) { |
| 27 | throw InvalidArgument("Invalid address to split={}", pc); | 42 | throw InvalidArgument("Invalid address to split={}", pc); |
| 28 | } | 43 | } |
| 29 | return { | 44 | *new_block = Block{ |
| 30 | Block{ | 45 | .begin{pc}, |
| 31 | .begin{block.begin}, | 46 | .end{old_block->end}, |
| 32 | .end{pc}, | 47 | .end_class{old_block->end_class}, |
| 33 | .end_class{EndClass::Branch}, | 48 | .stack{old_block->stack}, |
| 34 | .id{block.id}, | 49 | .cond{old_block->cond}, |
| 35 | .stack{block.stack}, | 50 | .branch_true{old_block->branch_true}, |
| 36 | .cond{true}, | 51 | .branch_false{old_block->branch_false}, |
| 37 | .branch_true{new_id}, | 52 | .ir{nullptr}, |
| 38 | .branch_false{UNREACHABLE_BLOCK_ID}, | 53 | }; |
| 39 | .imm_predecessors{}, | 54 | *old_block = Block{ |
| 40 | }, | 55 | .begin{old_block->begin}, |
| 41 | Block{ | 56 | .end{pc}, |
| 42 | .begin{pc}, | 57 | .end_class{EndClass::Branch}, |
| 43 | .end{block.end}, | 58 | .stack{std::move(old_block->stack)}, |
| 44 | .end_class{block.end_class}, | 59 | .cond{IR::Condition{true}}, |
| 45 | .id{new_id}, | 60 | .branch_true{new_block}, |
| 46 | .stack{std::move(block.stack)}, | 61 | .branch_false{nullptr}, |
| 47 | .cond{block.cond}, | 62 | .ir{nullptr}, |
| 48 | .branch_true{block.branch_true}, | ||
| 49 | .branch_false{block.branch_false}, | ||
| 50 | .imm_predecessors{}, | ||
| 51 | }, | ||
| 52 | }; | 63 | }; |
| 53 | } | 64 | } |
| 54 | 65 | ||
| @@ -112,7 +123,7 @@ static bool HasFlowTest(Opcode opcode) { | |||
| 112 | 123 | ||
| 113 | static std::string NameOf(const Block& block) { | 124 | static std::string NameOf(const Block& block) { |
| 114 | if (block.begin.IsVirtual()) { | 125 | if (block.begin.IsVirtual()) { |
| 115 | return fmt::format("\"Virtual {}\"", block.id); | 126 | return fmt::format("\"Virtual {}\"", block.begin); |
| 116 | } else { | 127 | } else { |
| 117 | return fmt::format("\"{}\"", block.begin); | 128 | return fmt::format("\"{}\"", block.begin); |
| 118 | } | 129 | } |
| @@ -158,126 +169,23 @@ bool Block::Contains(Location pc) const noexcept { | |||
| 158 | Function::Function(Location start_address) | 169 | Function::Function(Location start_address) |
| 159 | : entrypoint{start_address}, labels{{ | 170 | : entrypoint{start_address}, labels{{ |
| 160 | .address{start_address}, | 171 | .address{start_address}, |
| 161 | .block_id{0}, | 172 | .block{nullptr}, |
| 162 | .stack{}, | 173 | .stack{}, |
| 163 | }} {} | 174 | }} {} |
| 164 | 175 | ||
| 165 | void Function::BuildBlocksMap() { | 176 | CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address) |
| 166 | const size_t num_blocks{NumBlocks()}; | 177 | : env{env_}, block_pool{block_pool_} { |
| 167 | blocks_map.resize(num_blocks); | ||
| 168 | for (size_t block_index = 0; block_index < num_blocks; ++block_index) { | ||
| 169 | Block& block{blocks_data[block_index]}; | ||
| 170 | blocks_map[block.id] = █ | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | void Function::BuildImmediatePredecessors() { | ||
| 175 | for (const Block& block : blocks_data) { | ||
| 176 | if (block.branch_true != UNREACHABLE_BLOCK_ID) { | ||
| 177 | blocks_map[block.branch_true]->imm_predecessors.push_back(block.id); | ||
| 178 | } | ||
| 179 | if (block.branch_false != UNREACHABLE_BLOCK_ID) { | ||
| 180 | blocks_map[block.branch_false]->imm_predecessors.push_back(block.id); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | void Function::BuildPostOrder() { | ||
| 186 | boost::container::small_vector<BlockId, 0x110> block_stack; | ||
| 187 | post_order_map.resize(NumBlocks()); | ||
| 188 | |||
| 189 | Block& first_block{blocks_data[blocks.front()]}; | ||
| 190 | first_block.post_order_visited = true; | ||
| 191 | block_stack.push_back(first_block.id); | ||
| 192 | |||
| 193 | const auto visit_branch = [&](BlockId block_id, BlockId branch_id) { | ||
| 194 | if (branch_id == UNREACHABLE_BLOCK_ID) { | ||
| 195 | return false; | ||
| 196 | } | ||
| 197 | if (blocks_map[branch_id]->post_order_visited) { | ||
| 198 | return false; | ||
| 199 | } | ||
| 200 | blocks_map[branch_id]->post_order_visited = true; | ||
| 201 | |||
| 202 | // Calling push_back twice is faster than insert on msvc | ||
| 203 | block_stack.push_back(block_id); | ||
| 204 | block_stack.push_back(branch_id); | ||
| 205 | return true; | ||
| 206 | }; | ||
| 207 | while (!block_stack.empty()) { | ||
| 208 | const Block* const block{blocks_map[block_stack.back()]}; | ||
| 209 | block_stack.pop_back(); | ||
| 210 | |||
| 211 | if (!visit_branch(block->id, block->branch_true) && | ||
| 212 | !visit_branch(block->id, block->branch_false)) { | ||
| 213 | post_order_map[block->id] = static_cast<u32>(post_order_blocks.size()); | ||
| 214 | post_order_blocks.push_back(block->id); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | void Function::BuildImmediateDominators() { | ||
| 220 | auto transform_block_id{std::views::transform([this](BlockId id) { return blocks_map[id]; })}; | ||
| 221 | auto reverse_order_but_first{std::views::reverse | std::views::drop(1) | transform_block_id}; | ||
| 222 | auto has_idom{std::views::filter([](Block* block) { return block->imm_dominator; })}; | ||
| 223 | auto intersect{[this](Block* finger1, Block* finger2) { | ||
| 224 | while (finger1 != finger2) { | ||
| 225 | while (post_order_map[finger1->id] < post_order_map[finger2->id]) { | ||
| 226 | finger1 = finger1->imm_dominator; | ||
| 227 | } | ||
| 228 | while (post_order_map[finger2->id] < post_order_map[finger1->id]) { | ||
| 229 | finger2 = finger2->imm_dominator; | ||
| 230 | } | ||
| 231 | } | ||
| 232 | return finger1; | ||
| 233 | }}; | ||
| 234 | for (Block& block : blocks_data) { | ||
| 235 | block.imm_dominator = nullptr; | ||
| 236 | } | ||
| 237 | Block* const start_block{&blocks_data[blocks.front()]}; | ||
| 238 | start_block->imm_dominator = start_block; | ||
| 239 | |||
| 240 | bool changed{true}; | ||
| 241 | while (changed) { | ||
| 242 | changed = false; | ||
| 243 | for (Block* const block : post_order_blocks | reverse_order_but_first) { | ||
| 244 | Block* new_idom{}; | ||
| 245 | for (Block* predecessor : block->imm_predecessors | transform_block_id | has_idom) { | ||
| 246 | new_idom = new_idom ? intersect(predecessor, new_idom) : predecessor; | ||
| 247 | } | ||
| 248 | changed |= block->imm_dominator != new_idom; | ||
| 249 | block->imm_dominator = new_idom; | ||
| 250 | } | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | void Function::BuildDominanceFrontier() { | ||
| 255 | auto transform_block_id{std::views::transform([this](BlockId id) { return blocks_map[id]; })}; | ||
| 256 | auto has_enough_predecessors{[](Block& block) { return block.imm_predecessors.size() >= 2; }}; | ||
| 257 | for (Block& block : blocks_data | std::views::filter(has_enough_predecessors)) { | ||
| 258 | for (Block* current : block.imm_predecessors | transform_block_id) { | ||
| 259 | while (current != block.imm_dominator) { | ||
| 260 | current->dominance_frontiers.push_back(current->id); | ||
| 261 | current = current->imm_dominator; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | CFG::CFG(Environment& env_, Location start_address) : env{env_} { | ||
| 268 | VisitFunctions(start_address); | ||
| 269 | |||
| 270 | for (Function& function : functions) { | ||
| 271 | function.BuildBlocksMap(); | ||
| 272 | function.BuildImmediatePredecessors(); | ||
| 273 | function.BuildPostOrder(); | ||
| 274 | function.BuildImmediateDominators(); | ||
| 275 | function.BuildDominanceFrontier(); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | void CFG::VisitFunctions(Location start_address) { | ||
| 280 | functions.emplace_back(start_address); | 178 | functions.emplace_back(start_address); |
| 179 | functions.back().labels.back().block = block_pool.Create(Block{ | ||
| 180 | .begin{start_address}, | ||
| 181 | .end{start_address}, | ||
| 182 | .end_class{EndClass::Branch}, | ||
| 183 | .stack{}, | ||
| 184 | .cond{IR::Condition{true}}, | ||
| 185 | .branch_true{nullptr}, | ||
| 186 | .branch_false{nullptr}, | ||
| 187 | .ir{nullptr}, | ||
| 188 | }); | ||
| 281 | for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) { | 189 | for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) { |
| 282 | while (!functions[function_id].labels.empty()) { | 190 | while (!functions[function_id].labels.empty()) { |
| 283 | Function& function{functions[function_id]}; | 191 | Function& function{functions[function_id]}; |
| @@ -294,35 +202,16 @@ void CFG::AnalyzeLabel(FunctionId function_id, Label& label) { | |||
| 294 | return; | 202 | return; |
| 295 | } | 203 | } |
| 296 | // Try to find the next block | 204 | // Try to find the next block |
| 297 | Function* function{&functions[function_id]}; | 205 | Function* const function{&functions[function_id]}; |
| 298 | Location pc{label.address}; | 206 | Location pc{label.address}; |
| 299 | const auto next{std::upper_bound(function->blocks.begin(), function->blocks.end(), pc, | 207 | const auto next_it{function->blocks.upper_bound(pc, Compare{})}; |
| 300 | [function](Location pc, u32 block_index) { | 208 | const bool is_last{next_it == function->blocks.end()}; |
| 301 | return pc < function->blocks_data[block_index].begin; | 209 | Block* const next{is_last ? nullptr : &*next_it}; |
| 302 | })}; | ||
| 303 | const auto next_index{std::distance(function->blocks.begin(), next)}; | ||
| 304 | const bool is_last{next == function->blocks.end()}; | ||
| 305 | Location next_pc; | ||
| 306 | BlockId next_id{UNREACHABLE_BLOCK_ID}; | ||
| 307 | if (!is_last) { | ||
| 308 | next_pc = function->blocks_data[*next].begin; | ||
| 309 | next_id = function->blocks_data[*next].id; | ||
| 310 | } | ||
| 311 | // Insert before the next block | 210 | // Insert before the next block |
| 312 | Block block{ | 211 | Block* const block{label.block}; |
| 313 | .begin{pc}, | ||
| 314 | .end{pc}, | ||
| 315 | .end_class{EndClass::Branch}, | ||
| 316 | .id{label.block_id}, | ||
| 317 | .stack{std::move(label.stack)}, | ||
| 318 | .cond{true}, | ||
| 319 | .branch_true{UNREACHABLE_BLOCK_ID}, | ||
| 320 | .branch_false{UNREACHABLE_BLOCK_ID}, | ||
| 321 | .imm_predecessors{}, | ||
| 322 | }; | ||
| 323 | // Analyze instructions until it reaches an already visited block or there's a branch | 212 | // Analyze instructions until it reaches an already visited block or there's a branch |
| 324 | bool is_branch{false}; | 213 | bool is_branch{false}; |
| 325 | while (is_last || pc < next_pc) { | 214 | while (!next || pc < next->begin) { |
| 326 | is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch; | 215 | is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch; |
| 327 | if (is_branch) { | 216 | if (is_branch) { |
| 328 | break; | 217 | break; |
| @@ -332,43 +221,36 @@ void CFG::AnalyzeLabel(FunctionId function_id, Label& label) { | |||
| 332 | if (!is_branch) { | 221 | if (!is_branch) { |
| 333 | // If the block finished without a branch, | 222 | // If the block finished without a branch, |
| 334 | // it means that the next instruction is already visited, jump to it | 223 | // it means that the next instruction is already visited, jump to it |
| 335 | block.end = pc; | 224 | block->end = pc; |
| 336 | block.cond = true; | 225 | block->cond = IR::Condition{true}; |
| 337 | block.branch_true = next_id; | 226 | block->branch_true = next; |
| 338 | block.branch_false = UNREACHABLE_BLOCK_ID; | 227 | block->branch_false = nullptr; |
| 339 | } | 228 | } |
| 340 | // Function's pointer might be invalid, resolve it again | 229 | // Function's pointer might be invalid, resolve it again |
| 341 | function = &functions[function_id]; | 230 | // Insert the new block |
| 342 | const u32 new_block_index = static_cast<u32>(function->blocks_data.size()); | 231 | functions[function_id].blocks.insert(*block); |
| 343 | function->blocks.insert(function->blocks.begin() + next_index, new_block_index); | ||
| 344 | function->blocks_data.push_back(std::move(block)); | ||
| 345 | } | 232 | } |
| 346 | 233 | ||
| 347 | bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) { | 234 | bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) { |
| 348 | const Location pc{label.address}; | 235 | const Location pc{label.address}; |
| 349 | Function& function{functions[function_id]}; | 236 | Function& function{functions[function_id]}; |
| 350 | const auto it{std::ranges::find_if(function.blocks, [&function, pc](u32 block_index) { | 237 | const auto it{ |
| 351 | return function.blocks_data[block_index].Contains(pc); | 238 | std::ranges::find_if(function.blocks, [pc](auto& block) { return block.Contains(pc); })}; |
| 352 | })}; | ||
| 353 | if (it == function.blocks.end()) { | 239 | if (it == function.blocks.end()) { |
| 354 | // Address has not been visited | 240 | // Address has not been visited |
| 355 | return false; | 241 | return false; |
| 356 | } | 242 | } |
| 357 | Block& block{function.blocks_data[*it]}; | 243 | Block* const visited_block{&*it}; |
| 358 | if (block.begin == pc) { | 244 | if (visited_block->begin == pc) { |
| 359 | throw LogicError("Dangling branch"); | 245 | throw LogicError("Dangling block"); |
| 360 | } | 246 | } |
| 361 | const u32 first_index{*it}; | 247 | Block* const new_block{label.block}; |
| 362 | const u32 second_index{static_cast<u32>(function.blocks_data.size())}; | 248 | Split(visited_block, new_block, pc); |
| 363 | const std::array new_indices{first_index, second_index}; | 249 | function.blocks.insert(it, *new_block); |
| 364 | std::array split_blocks{Split(std::move(block), pc, label.block_id)}; | ||
| 365 | function.blocks_data[*it] = std::move(split_blocks[0]); | ||
| 366 | function.blocks_data.push_back(std::move(split_blocks[1])); | ||
| 367 | function.blocks.insert(function.blocks.erase(it), new_indices.begin(), new_indices.end()); | ||
| 368 | return true; | 250 | return true; |
| 369 | } | 251 | } |
| 370 | 252 | ||
| 371 | CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Location pc) { | 253 | CFG::AnalysisState CFG::AnalyzeInst(Block* block, FunctionId function_id, Location pc) { |
| 372 | const Instruction inst{env.ReadInstruction(pc.Offset())}; | 254 | const Instruction inst{env.ReadInstruction(pc.Offset())}; |
| 373 | const Opcode opcode{Decode(inst.raw)}; | 255 | const Opcode opcode{Decode(inst.raw)}; |
| 374 | switch (opcode) { | 256 | switch (opcode) { |
| @@ -390,12 +272,12 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati | |||
| 390 | AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode)); | 272 | AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode)); |
| 391 | break; | 273 | break; |
| 392 | case Opcode::RET: | 274 | case Opcode::RET: |
| 393 | block.end_class = EndClass::Return; | 275 | block->end_class = EndClass::Return; |
| 394 | break; | 276 | break; |
| 395 | default: | 277 | default: |
| 396 | break; | 278 | break; |
| 397 | } | 279 | } |
| 398 | block.end = pc; | 280 | block->end = pc; |
| 399 | return AnalysisState::Branch; | 281 | return AnalysisState::Branch; |
| 400 | case Opcode::BRK: | 282 | case Opcode::BRK: |
| 401 | case Opcode::CONT: | 283 | case Opcode::CONT: |
| @@ -404,9 +286,9 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati | |||
| 404 | if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) { | 286 | if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) { |
| 405 | return AnalysisState::Continue; | 287 | return AnalysisState::Continue; |
| 406 | } | 288 | } |
| 407 | const auto [stack_pc, new_stack]{block.stack.Pop(OpcodeToken(opcode))}; | 289 | const auto [stack_pc, new_stack]{block->stack.Pop(OpcodeToken(opcode))}; |
| 408 | block.branch_true = AddLabel(block, new_stack, stack_pc, function_id); | 290 | block->branch_true = AddLabel(block, new_stack, stack_pc, function_id); |
| 409 | block.end = pc; | 291 | block->end = pc; |
| 410 | return AnalysisState::Branch; | 292 | return AnalysisState::Branch; |
| 411 | } | 293 | } |
| 412 | case Opcode::PBK: | 294 | case Opcode::PBK: |
| @@ -414,7 +296,7 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati | |||
| 414 | case Opcode::PEXIT: | 296 | case Opcode::PEXIT: |
| 415 | case Opcode::PLONGJMP: | 297 | case Opcode::PLONGJMP: |
| 416 | case Opcode::SSY: | 298 | case Opcode::SSY: |
| 417 | block.stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst)); | 299 | block->stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst)); |
| 418 | return AnalysisState::Continue; | 300 | return AnalysisState::Continue; |
| 419 | case Opcode::EXIT: | 301 | case Opcode::EXIT: |
| 420 | return AnalyzeEXIT(block, function_id, pc, inst); | 302 | return AnalyzeEXIT(block, function_id, pc, inst); |
| @@ -444,51 +326,51 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati | |||
| 444 | return AnalysisState::Branch; | 326 | return AnalysisState::Branch; |
| 445 | } | 327 | } |
| 446 | 328 | ||
| 447 | void CFG::AnalyzeCondInst(Block& block, FunctionId function_id, Location pc, | 329 | void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, |
| 448 | EndClass insn_end_class, IR::Condition cond) { | 330 | EndClass insn_end_class, IR::Condition cond) { |
| 449 | if (block.begin != pc) { | 331 | if (block->begin != pc) { |
| 450 | // If the block doesn't start in the conditional instruction | 332 | // If the block doesn't start in the conditional instruction |
| 451 | // mark it as a label to visit it later | 333 | // mark it as a label to visit it later |
| 452 | block.end = pc; | 334 | block->end = pc; |
| 453 | block.cond = true; | 335 | block->cond = IR::Condition{true}; |
| 454 | block.branch_true = AddLabel(block, block.stack, pc, function_id); | 336 | block->branch_true = AddLabel(block, block->stack, pc, function_id); |
| 455 | block.branch_false = UNREACHABLE_BLOCK_ID; | 337 | block->branch_false = nullptr; |
| 456 | return; | 338 | return; |
| 457 | } | 339 | } |
| 458 | // Impersonate the visited block with a virtual block | 340 | // Create a virtual block and a conditional block |
| 459 | // Jump from this virtual to the real conditional instruction and the next instruction | 341 | Block* const conditional_block{block_pool.Create()}; |
| 460 | Function& function{functions[function_id]}; | 342 | Block virtual_block{ |
| 461 | const BlockId conditional_block_id{++function.current_block_id}; | 343 | .begin{block->begin.Virtual()}, |
| 462 | function.blocks.push_back(static_cast<u32>(function.blocks_data.size())); | 344 | .end{block->begin.Virtual()}, |
| 463 | Block& virtual_block{function.blocks_data.emplace_back(Block{ | ||
| 464 | .begin{}, // Virtual block | ||
| 465 | .end{}, | ||
| 466 | .end_class{EndClass::Branch}, | 345 | .end_class{EndClass::Branch}, |
| 467 | .id{block.id}, // Impersonating | 346 | .stack{block->stack}, |
| 468 | .stack{block.stack}, | ||
| 469 | .cond{cond}, | 347 | .cond{cond}, |
| 470 | .branch_true{conditional_block_id}, | 348 | .branch_true{conditional_block}, |
| 471 | .branch_false{UNREACHABLE_BLOCK_ID}, | 349 | .branch_false{nullptr}, |
| 472 | .imm_predecessors{}, | 350 | .ir{nullptr}, |
| 473 | })}; | 351 | }; |
| 474 | // Set the end properties of the conditional instruction and give it a new identity | 352 | // Save the contents of the visited block in the conditional block |
| 475 | Block& conditional_block{block}; | 353 | *conditional_block = std::move(*block); |
| 476 | conditional_block.end = pc; | 354 | // Impersonate the visited block with a virtual block |
| 477 | conditional_block.end_class = insn_end_class; | 355 | *block = std::move(virtual_block); |
| 478 | conditional_block.id = conditional_block_id; | 356 | // Set the end properties of the conditional instruction |
| 357 | conditional_block->end = pc; | ||
| 358 | conditional_block->end_class = insn_end_class; | ||
| 479 | // Add a label to the instruction after the conditional instruction | 359 | // Add a label to the instruction after the conditional instruction |
| 480 | const BlockId endif_block_id{AddLabel(conditional_block, block.stack, pc + 1, function_id)}; | 360 | Block* const endif_block{AddLabel(conditional_block, block->stack, pc + 1, function_id)}; |
| 481 | // Branch to the next instruction from the virtual block | 361 | // Branch to the next instruction from the virtual block |
| 482 | virtual_block.branch_false = endif_block_id; | 362 | block->branch_false = endif_block; |
| 483 | // And branch to it from the conditional instruction if it is a branch | 363 | // And branch to it from the conditional instruction if it is a branch |
| 484 | if (insn_end_class == EndClass::Branch) { | 364 | if (insn_end_class == EndClass::Branch) { |
| 485 | conditional_block.cond = true; | 365 | conditional_block->cond = IR::Condition{true}; |
| 486 | conditional_block.branch_true = endif_block_id; | 366 | conditional_block->branch_true = endif_block; |
| 487 | conditional_block.branch_false = UNREACHABLE_BLOCK_ID; | 367 | conditional_block->branch_false = nullptr; |
| 488 | } | 368 | } |
| 369 | // Finally insert the condition block into the list of blocks | ||
| 370 | functions[function_id].blocks.insert(*conditional_block); | ||
| 489 | } | 371 | } |
| 490 | 372 | ||
| 491 | bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst, | 373 | bool CFG::AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst, |
| 492 | Opcode opcode) { | 374 | Opcode opcode) { |
| 493 | if (inst.branch.is_cbuf) { | 375 | if (inst.branch.is_cbuf) { |
| 494 | throw NotImplementedException("Branch with constant buffer offset"); | 376 | throw NotImplementedException("Branch with constant buffer offset"); |
| @@ -500,21 +382,21 @@ bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instr | |||
| 500 | const bool has_flow_test{HasFlowTest(opcode)}; | 382 | const bool has_flow_test{HasFlowTest(opcode)}; |
| 501 | const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T}; | 383 | const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T}; |
| 502 | if (pred != Predicate{true} || flow_test != IR::FlowTest::T) { | 384 | if (pred != Predicate{true} || flow_test != IR::FlowTest::T) { |
| 503 | block.cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated); | 385 | block->cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated); |
| 504 | block.branch_false = AddLabel(block, block.stack, pc + 1, function_id); | 386 | block->branch_false = AddLabel(block, block->stack, pc + 1, function_id); |
| 505 | } else { | 387 | } else { |
| 506 | block.cond = true; | 388 | block->cond = IR::Condition{true}; |
| 507 | } | 389 | } |
| 508 | return true; | 390 | return true; |
| 509 | } | 391 | } |
| 510 | 392 | ||
| 511 | void CFG::AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst, | 393 | void CFG::AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst, |
| 512 | bool is_absolute) { | 394 | bool is_absolute) { |
| 513 | const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)}; | 395 | const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)}; |
| 514 | block.branch_true = AddLabel(block, block.stack, bra_pc, function_id); | 396 | block->branch_true = AddLabel(block, block->stack, bra_pc, function_id); |
| 515 | } | 397 | } |
| 516 | 398 | ||
| 517 | void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) { | 399 | void CFG::AnalyzeBRX(Block*, Location, Instruction, bool is_absolute) { |
| 518 | throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX"); | 400 | throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX"); |
| 519 | } | 401 | } |
| 520 | 402 | ||
| @@ -528,7 +410,7 @@ void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) { | |||
| 528 | } | 410 | } |
| 529 | } | 411 | } |
| 530 | 412 | ||
| 531 | CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Location pc, | 413 | CFG::AnalysisState CFG::AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, |
| 532 | Instruction inst) { | 414 | Instruction inst) { |
| 533 | const IR::FlowTest flow_test{inst.branch.flow_test}; | 415 | const IR::FlowTest flow_test{inst.branch.flow_test}; |
| 534 | const Predicate pred{inst.Pred()}; | 416 | const Predicate pred{inst.Pred()}; |
| @@ -537,41 +419,52 @@ CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Locati | |||
| 537 | return AnalysisState::Continue; | 419 | return AnalysisState::Continue; |
| 538 | } | 420 | } |
| 539 | if (pred != Predicate{true} || flow_test != IR::FlowTest::T) { | 421 | if (pred != Predicate{true} || flow_test != IR::FlowTest::T) { |
| 540 | if (block.stack.Peek(Token::PEXIT).has_value()) { | 422 | if (block->stack.Peek(Token::PEXIT).has_value()) { |
| 541 | throw NotImplementedException("Conditional EXIT with PEXIT token"); | 423 | throw NotImplementedException("Conditional EXIT with PEXIT token"); |
| 542 | } | 424 | } |
| 543 | const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated}; | 425 | const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated}; |
| 544 | AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond); | 426 | AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond); |
| 545 | return AnalysisState::Branch; | 427 | return AnalysisState::Branch; |
| 546 | } | 428 | } |
| 547 | if (const std::optional<Location> exit_pc{block.stack.Peek(Token::PEXIT)}) { | 429 | if (const std::optional<Location> exit_pc{block->stack.Peek(Token::PEXIT)}) { |
| 548 | const Stack popped_stack{block.stack.Remove(Token::PEXIT)}; | 430 | const Stack popped_stack{block->stack.Remove(Token::PEXIT)}; |
| 549 | block.cond = true; | 431 | block->cond = IR::Condition{true}; |
| 550 | block.branch_true = AddLabel(block, popped_stack, *exit_pc, function_id); | 432 | block->branch_true = AddLabel(block, popped_stack, *exit_pc, function_id); |
| 551 | block.branch_false = UNREACHABLE_BLOCK_ID; | 433 | block->branch_false = nullptr; |
| 552 | return AnalysisState::Branch; | 434 | return AnalysisState::Branch; |
| 553 | } | 435 | } |
| 554 | block.end = pc; | 436 | block->end = pc; |
| 555 | block.end_class = EndClass::Exit; | 437 | block->end_class = EndClass::Exit; |
| 556 | return AnalysisState::Branch; | 438 | return AnalysisState::Branch; |
| 557 | } | 439 | } |
| 558 | 440 | ||
| 559 | BlockId CFG::AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id) { | 441 | Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id) { |
| 560 | Function& function{functions[function_id]}; | 442 | Function& function{functions[function_id]}; |
| 561 | if (block.begin == pc) { | 443 | if (block->begin == pc) { |
| 562 | return block.id; | 444 | // Jumps to itself |
| 445 | return block; | ||
| 563 | } | 446 | } |
| 564 | const auto target{std::ranges::find(function.blocks_data, pc, &Block::begin)}; | 447 | if (const auto it{function.blocks.find(pc, Compare{})}; it != function.blocks.end()) { |
| 565 | if (target != function.blocks_data.end()) { | 448 | // Block already exists and it has been visited |
| 566 | return target->id; | 449 | return &*it; |
| 567 | } | 450 | } |
| 568 | const BlockId block_id{++function.current_block_id}; | 451 | // TODO: FIX DANGLING BLOCKS |
| 452 | Block* const new_block{block_pool.Create(Block{ | ||
| 453 | .begin{pc}, | ||
| 454 | .end{pc}, | ||
| 455 | .end_class{EndClass::Branch}, | ||
| 456 | .stack{stack}, | ||
| 457 | .cond{IR::Condition{true}}, | ||
| 458 | .branch_true{nullptr}, | ||
| 459 | .branch_false{nullptr}, | ||
| 460 | .ir{nullptr}, | ||
| 461 | })}; | ||
| 569 | function.labels.push_back(Label{ | 462 | function.labels.push_back(Label{ |
| 570 | .address{pc}, | 463 | .address{pc}, |
| 571 | .block_id{block_id}, | 464 | .block{new_block}, |
| 572 | .stack{std::move(stack)}, | 465 | .stack{std::move(stack)}, |
| 573 | }); | 466 | }); |
| 574 | return block_id; | 467 | return new_block; |
| 575 | } | 468 | } |
| 576 | 469 | ||
| 577 | std::string CFG::Dot() const { | 470 | std::string CFG::Dot() const { |
| @@ -581,18 +474,12 @@ std::string CFG::Dot() const { | |||
| 581 | for (const Function& function : functions) { | 474 | for (const Function& function : functions) { |
| 582 | dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint); | 475 | dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint); |
| 583 | dot += fmt::format("\t\tnode [style=filled];\n"); | 476 | dot += fmt::format("\t\tnode [style=filled];\n"); |
| 584 | for (const u32 block_index : function.blocks) { | 477 | for (const Block& block : function.blocks) { |
| 585 | const Block& block{function.blocks_data[block_index]}; | ||
| 586 | const std::string name{NameOf(block)}; | 478 | const std::string name{NameOf(block)}; |
| 587 | const auto add_branch = [&](BlockId branch_id, bool add_label) { | 479 | const auto add_branch = [&](Block* branch, bool add_label) { |
| 588 | const auto it{std::ranges::find(function.blocks_data, branch_id, &Block::id)}; | 480 | dot += fmt::format("\t\t{}->{}", name, NameOf(*branch)); |
| 589 | dot += fmt::format("\t\t{}->", name); | 481 | if (add_label && block.cond != IR::Condition{true} && |
| 590 | if (it == function.blocks_data.end()) { | 482 | block.cond != IR::Condition{false}) { |
| 591 | dot += fmt::format("\"Unknown label {}\"", branch_id); | ||
| 592 | } else { | ||
| 593 | dot += NameOf(*it); | ||
| 594 | }; | ||
| 595 | if (add_label && block.cond != true && block.cond != false) { | ||
| 596 | dot += fmt::format(" [label=\"{}\"]", block.cond); | 483 | dot += fmt::format(" [label=\"{}\"]", block.cond); |
| 597 | } | 484 | } |
| 598 | dot += '\n'; | 485 | dot += '\n'; |
| @@ -600,10 +487,10 @@ std::string CFG::Dot() const { | |||
| 600 | dot += fmt::format("\t\t{};\n", name); | 487 | dot += fmt::format("\t\t{};\n", name); |
| 601 | switch (block.end_class) { | 488 | switch (block.end_class) { |
| 602 | case EndClass::Branch: | 489 | case EndClass::Branch: |
| 603 | if (block.cond != false) { | 490 | if (block.cond != IR::Condition{false}) { |
| 604 | add_branch(block.branch_true, true); | 491 | add_branch(block.branch_true, true); |
| 605 | } | 492 | } |
| 606 | if (block.cond != true) { | 493 | if (block.cond != IR::Condition{true}) { |
| 607 | add_branch(block.branch_false, false); | 494 | add_branch(block.branch_false, false); |
| 608 | } | 495 | } |
| 609 | break; | 496 | break; |
| @@ -619,12 +506,6 @@ std::string CFG::Dot() const { | |||
| 619 | node_uid); | 506 | node_uid); |
| 620 | ++node_uid; | 507 | ++node_uid; |
| 621 | break; | 508 | break; |
| 622 | case EndClass::Unreachable: | ||
| 623 | dot += fmt::format("\t\t{}->N{};\n", name, node_uid); | ||
| 624 | dot += fmt::format( | ||
| 625 | "\t\tN{} [label=\"Unreachable\"][shape=square][style=stripped];\n", node_uid); | ||
| 626 | ++node_uid; | ||
| 627 | break; | ||
| 628 | } | 509 | } |
| 629 | } | 510 | } |
| 630 | if (function.entrypoint == 8) { | 511 | if (function.entrypoint == 8) { |
| @@ -635,10 +516,11 @@ std::string CFG::Dot() const { | |||
| 635 | dot += "\t}\n"; | 516 | dot += "\t}\n"; |
| 636 | } | 517 | } |
| 637 | if (!functions.empty()) { | 518 | if (!functions.empty()) { |
| 638 | if (functions.front().blocks.empty()) { | 519 | auto& function{functions.front()}; |
| 520 | if (function.blocks.empty()) { | ||
| 639 | dot += "Start;\n"; | 521 | dot += "Start;\n"; |
| 640 | } else { | 522 | } else { |
| 641 | dot += fmt::format("\tStart -> {};\n", NameOf(functions.front().blocks_data.front())); | 523 | dot += fmt::format("\tStart -> {};\n", NameOf(*function.blocks.begin())); |
| 642 | } | 524 | } |
| 643 | dot += fmt::format("\tStart [shape=diamond];\n"); | 525 | dot += fmt::format("\tStart [shape=diamond];\n"); |
| 644 | } | 526 | } |
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.h b/src/shader_recompiler/frontend/maxwell/control_flow.h index 49b369282..8179787b8 100644 --- a/src/shader_recompiler/frontend/maxwell/control_flow.h +++ b/src/shader_recompiler/frontend/maxwell/control_flow.h | |||
| @@ -11,25 +11,27 @@ | |||
| 11 | #include <vector> | 11 | #include <vector> |
| 12 | 12 | ||
| 13 | #include <boost/container/small_vector.hpp> | 13 | #include <boost/container/small_vector.hpp> |
| 14 | #include <boost/intrusive/set.hpp> | ||
| 14 | 15 | ||
| 15 | #include "shader_recompiler/environment.h" | 16 | #include "shader_recompiler/environment.h" |
| 16 | #include "shader_recompiler/frontend/ir/condition.h" | 17 | #include "shader_recompiler/frontend/ir/condition.h" |
| 17 | #include "shader_recompiler/frontend/maxwell/instruction.h" | 18 | #include "shader_recompiler/frontend/maxwell/instruction.h" |
| 18 | #include "shader_recompiler/frontend/maxwell/location.h" | 19 | #include "shader_recompiler/frontend/maxwell/location.h" |
| 19 | #include "shader_recompiler/frontend/maxwell/opcodes.h" | 20 | #include "shader_recompiler/frontend/maxwell/opcodes.h" |
| 21 | #include "shader_recompiler/object_pool.h" | ||
| 22 | |||
| 23 | namespace Shader::IR { | ||
| 24 | class Block; | ||
| 25 | } | ||
| 20 | 26 | ||
| 21 | namespace Shader::Maxwell::Flow { | 27 | namespace Shader::Maxwell::Flow { |
| 22 | 28 | ||
| 23 | using BlockId = u32; | ||
| 24 | using FunctionId = size_t; | 29 | using FunctionId = size_t; |
| 25 | 30 | ||
| 26 | constexpr BlockId UNREACHABLE_BLOCK_ID{static_cast<u32>(-1)}; | ||
| 27 | |||
| 28 | enum class EndClass { | 31 | enum class EndClass { |
| 29 | Branch, | 32 | Branch, |
| 30 | Exit, | 33 | Exit, |
| 31 | Return, | 34 | Return, |
| 32 | Unreachable, | ||
| 33 | }; | 35 | }; |
| 34 | 36 | ||
| 35 | enum class Token { | 37 | enum class Token { |
| @@ -59,58 +61,37 @@ private: | |||
| 59 | boost::container::small_vector<StackEntry, 3> entries; | 61 | boost::container::small_vector<StackEntry, 3> entries; |
| 60 | }; | 62 | }; |
| 61 | 63 | ||
| 62 | struct Block { | 64 | struct Block : boost::intrusive::set_base_hook< |
| 65 | // Normal link is ~2.5% faster compared to safe link | ||
| 66 | boost::intrusive::link_mode<boost::intrusive::normal_link>> { | ||
| 63 | [[nodiscard]] bool Contains(Location pc) const noexcept; | 67 | [[nodiscard]] bool Contains(Location pc) const noexcept; |
| 64 | 68 | ||
| 69 | bool operator<(const Block& rhs) const noexcept { | ||
| 70 | return begin < rhs.begin; | ||
| 71 | } | ||
| 72 | |||
| 65 | Location begin; | 73 | Location begin; |
| 66 | Location end; | 74 | Location end; |
| 67 | EndClass end_class; | 75 | EndClass end_class; |
| 68 | BlockId id; | ||
| 69 | Stack stack; | 76 | Stack stack; |
| 70 | IR::Condition cond; | 77 | IR::Condition cond; |
| 71 | BlockId branch_true; | 78 | Block* branch_true; |
| 72 | BlockId branch_false; | 79 | Block* branch_false; |
| 73 | boost::container::small_vector<BlockId, 4> imm_predecessors; | 80 | IR::Block* ir; |
| 74 | boost::container::small_vector<BlockId, 8> dominance_frontiers; | ||
| 75 | union { | ||
| 76 | bool post_order_visited{false}; | ||
| 77 | Block* imm_dominator; | ||
| 78 | }; | ||
| 79 | }; | 81 | }; |
| 80 | 82 | ||
| 81 | struct Label { | 83 | struct Label { |
| 82 | Location address; | 84 | Location address; |
| 83 | BlockId block_id; | 85 | Block* block; |
| 84 | Stack stack; | 86 | Stack stack; |
| 85 | }; | 87 | }; |
| 86 | 88 | ||
| 87 | struct Function { | 89 | struct Function { |
| 88 | Function(Location start_address); | 90 | Function(Location start_address); |
| 89 | 91 | ||
| 90 | void BuildBlocksMap(); | ||
| 91 | |||
| 92 | void BuildImmediatePredecessors(); | ||
| 93 | |||
| 94 | void BuildPostOrder(); | ||
| 95 | |||
| 96 | void BuildImmediateDominators(); | ||
| 97 | |||
| 98 | void BuildDominanceFrontier(); | ||
| 99 | |||
| 100 | [[nodiscard]] size_t NumBlocks() const noexcept { | ||
| 101 | return static_cast<size_t>(current_block_id) + 1; | ||
| 102 | } | ||
| 103 | |||
| 104 | Location entrypoint; | 92 | Location entrypoint; |
| 105 | BlockId current_block_id{0}; | ||
| 106 | boost::container::small_vector<Label, 16> labels; | 93 | boost::container::small_vector<Label, 16> labels; |
| 107 | boost::container::small_vector<u32, 0x130> blocks; | 94 | boost::intrusive::set<Block> blocks; |
| 108 | boost::container::small_vector<Block, 0x130> blocks_data; | ||
| 109 | // Translates from BlockId to block index | ||
| 110 | boost::container::small_vector<Block*, 0x130> blocks_map; | ||
| 111 | |||
| 112 | boost::container::small_vector<u32, 0x130> post_order_blocks; | ||
| 113 | boost::container::small_vector<BlockId, 0x130> post_order_map; | ||
| 114 | }; | 95 | }; |
| 115 | 96 | ||
| 116 | class CFG { | 97 | class CFG { |
| @@ -120,7 +101,7 @@ class CFG { | |||
| 120 | }; | 101 | }; |
| 121 | 102 | ||
| 122 | public: | 103 | public: |
| 123 | explicit CFG(Environment& env, Location start_address); | 104 | explicit CFG(Environment& env, ObjectPool<Block>& block_pool, Location start_address); |
| 124 | 105 | ||
| 125 | CFG& operator=(const CFG&) = delete; | 106 | CFG& operator=(const CFG&) = delete; |
| 126 | CFG(const CFG&) = delete; | 107 | CFG(const CFG&) = delete; |
| @@ -133,35 +114,37 @@ public: | |||
| 133 | [[nodiscard]] std::span<const Function> Functions() const noexcept { | 114 | [[nodiscard]] std::span<const Function> Functions() const noexcept { |
| 134 | return std::span(functions.data(), functions.size()); | 115 | return std::span(functions.data(), functions.size()); |
| 135 | } | 116 | } |
| 117 | [[nodiscard]] std::span<Function> Functions() noexcept { | ||
| 118 | return std::span(functions.data(), functions.size()); | ||
| 119 | } | ||
| 136 | 120 | ||
| 137 | private: | 121 | private: |
| 138 | void VisitFunctions(Location start_address); | ||
| 139 | |||
| 140 | void AnalyzeLabel(FunctionId function_id, Label& label); | 122 | void AnalyzeLabel(FunctionId function_id, Label& label); |
| 141 | 123 | ||
| 142 | /// Inspect already visited blocks. | 124 | /// Inspect already visited blocks. |
| 143 | /// Return true when the block has already been visited | 125 | /// Return true when the block has already been visited |
| 144 | bool InspectVisitedBlocks(FunctionId function_id, const Label& label); | 126 | bool InspectVisitedBlocks(FunctionId function_id, const Label& label); |
| 145 | 127 | ||
| 146 | AnalysisState AnalyzeInst(Block& block, FunctionId function_id, Location pc); | 128 | AnalysisState AnalyzeInst(Block* block, FunctionId function_id, Location pc); |
| 147 | 129 | ||
| 148 | void AnalyzeCondInst(Block& block, FunctionId function_id, Location pc, EndClass insn_end_class, | 130 | void AnalyzeCondInst(Block* block, FunctionId function_id, Location pc, EndClass insn_end_class, |
| 149 | IR::Condition cond); | 131 | IR::Condition cond); |
| 150 | 132 | ||
| 151 | /// Return true when the branch instruction is confirmed to be a branch | 133 | /// Return true when the branch instruction is confirmed to be a branch |
| 152 | bool AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst, | 134 | bool AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst, |
| 153 | Opcode opcode); | 135 | Opcode opcode); |
| 154 | 136 | ||
| 155 | void AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst, | 137 | void AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst, |
| 156 | bool is_absolute); | 138 | bool is_absolute); |
| 157 | void AnalyzeBRX(Block& block, Location pc, Instruction inst, bool is_absolute); | 139 | void AnalyzeBRX(Block* block, Location pc, Instruction inst, bool is_absolute); |
| 158 | void AnalyzeCAL(Location pc, Instruction inst, bool is_absolute); | 140 | void AnalyzeCAL(Location pc, Instruction inst, bool is_absolute); |
| 159 | AnalysisState AnalyzeEXIT(Block& block, FunctionId function_id, Location pc, Instruction inst); | 141 | AnalysisState AnalyzeEXIT(Block* block, FunctionId function_id, Location pc, Instruction inst); |
| 160 | 142 | ||
| 161 | /// Return the branch target block id | 143 | /// Return the branch target block id |
| 162 | BlockId AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id); | 144 | Block* AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id); |
| 163 | 145 | ||
| 164 | Environment& env; | 146 | Environment& env; |
| 147 | ObjectPool<Block>& block_pool; | ||
| 165 | boost::container::small_vector<Function, 1> functions; | 148 | boost::container::small_vector<Function, 1> functions; |
| 166 | FunctionId current_function_id{0}; | 149 | FunctionId current_function_id{0}; |
| 167 | }; | 150 | }; |
diff --git a/src/shader_recompiler/frontend/maxwell/location.h b/src/shader_recompiler/frontend/maxwell/location.h index 66b51a19e..26d29eae2 100644 --- a/src/shader_recompiler/frontend/maxwell/location.h +++ b/src/shader_recompiler/frontend/maxwell/location.h | |||
| @@ -15,7 +15,7 @@ | |||
| 15 | namespace Shader::Maxwell { | 15 | namespace Shader::Maxwell { |
| 16 | 16 | ||
| 17 | class Location { | 17 | class Location { |
| 18 | static constexpr u32 VIRTUAL_OFFSET{std::numeric_limits<u32>::max()}; | 18 | static constexpr u32 VIRTUAL_BIAS{4}; |
| 19 | 19 | ||
| 20 | public: | 20 | public: |
| 21 | constexpr Location() = default; | 21 | constexpr Location() = default; |
| @@ -27,12 +27,18 @@ public: | |||
| 27 | Align(); | 27 | Align(); |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | constexpr Location Virtual() const noexcept { | ||
| 31 | Location virtual_location; | ||
| 32 | virtual_location.offset = offset - VIRTUAL_BIAS; | ||
| 33 | return virtual_location; | ||
| 34 | } | ||
| 35 | |||
| 30 | [[nodiscard]] constexpr u32 Offset() const noexcept { | 36 | [[nodiscard]] constexpr u32 Offset() const noexcept { |
| 31 | return offset; | 37 | return offset; |
| 32 | } | 38 | } |
| 33 | 39 | ||
| 34 | [[nodiscard]] constexpr bool IsVirtual() const { | 40 | [[nodiscard]] constexpr bool IsVirtual() const { |
| 35 | return offset == VIRTUAL_OFFSET; | 41 | return offset % 8 == VIRTUAL_BIAS; |
| 36 | } | 42 | } |
| 37 | 43 | ||
| 38 | constexpr auto operator<=>(const Location&) const noexcept = default; | 44 | constexpr auto operator<=>(const Location&) const noexcept = default; |
| @@ -89,7 +95,7 @@ private: | |||
| 89 | offset -= 8 + (offset % 32 == 8 ? 8 : 0); | 95 | offset -= 8 + (offset % 32 == 8 ? 8 : 0); |
| 90 | } | 96 | } |
| 91 | 97 | ||
| 92 | u32 offset{VIRTUAL_OFFSET}; | 98 | u32 offset{0xcccccccc}; |
| 93 | }; | 99 | }; |
| 94 | 100 | ||
| 95 | } // namespace Shader::Maxwell | 101 | } // namespace Shader::Maxwell |
diff --git a/src/shader_recompiler/frontend/maxwell/program.cpp b/src/shader_recompiler/frontend/maxwell/program.cpp index 8cdd20804..9fa912ed8 100644 --- a/src/shader_recompiler/frontend/maxwell/program.cpp +++ b/src/shader_recompiler/frontend/maxwell/program.cpp | |||
| @@ -4,57 +4,58 @@ | |||
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include <memory> | 6 | #include <memory> |
| 7 | #include <vector> | ||
| 7 | 8 | ||
| 8 | #include "shader_recompiler/frontend/ir/basic_block.h" | 9 | #include "shader_recompiler/frontend/ir/basic_block.h" |
| 10 | #include "shader_recompiler/frontend/ir/structured_control_flow.h" | ||
| 9 | #include "shader_recompiler/frontend/maxwell/program.h" | 11 | #include "shader_recompiler/frontend/maxwell/program.h" |
| 10 | #include "shader_recompiler/frontend/maxwell/termination_code.h" | ||
| 11 | #include "shader_recompiler/frontend/maxwell/translate/translate.h" | 12 | #include "shader_recompiler/frontend/maxwell/translate/translate.h" |
| 12 | #include "shader_recompiler/ir_opt/passes.h" | 13 | #include "shader_recompiler/ir_opt/passes.h" |
| 13 | 14 | ||
| 14 | namespace Shader::Maxwell { | 15 | namespace Shader::Maxwell { |
| 15 | namespace { | 16 | namespace { |
| 16 | void TranslateCode(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, | 17 | IR::BlockList TranslateCode(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, |
| 17 | Environment& env, const Flow::Function& cfg_function, IR::Function& function, | 18 | Environment& env, Flow::Function& cfg_function) { |
| 18 | std::span<IR::Block*> block_map) { | ||
| 19 | const size_t num_blocks{cfg_function.blocks.size()}; | 19 | const size_t num_blocks{cfg_function.blocks.size()}; |
| 20 | function.blocks.reserve(num_blocks); | 20 | std::vector<IR::Block*> blocks(cfg_function.blocks.size()); |
| 21 | 21 | std::ranges::for_each(cfg_function.blocks, [&, i = size_t{0}](auto& cfg_block) mutable { | |
| 22 | for (const Flow::BlockId block_id : cfg_function.blocks) { | 22 | const u32 begin{cfg_block.begin.Offset()}; |
| 23 | const Flow::Block& flow_block{cfg_function.blocks_data[block_id]}; | 23 | const u32 end{cfg_block.end.Offset()}; |
| 24 | 24 | blocks[i] = block_pool.Create(inst_pool, begin, end); | |
| 25 | IR::Block* const ir_block{block_pool.Create(Translate(inst_pool, env, flow_block))}; | 25 | cfg_block.ir = blocks[i]; |
| 26 | block_map[flow_block.id] = ir_block; | 26 | ++i; |
| 27 | function.blocks.emplace_back(ir_block); | 27 | }); |
| 28 | } | 28 | std::ranges::for_each(cfg_function.blocks, [&, i = size_t{0}](auto& cfg_block) mutable { |
| 29 | } | 29 | IR::Block* const block{blocks[i]}; |
| 30 | 30 | ++i; | |
| 31 | void EmitTerminationInsts(const Flow::Function& cfg_function, | 31 | if (cfg_block.end_class != Flow::EndClass::Branch) { |
| 32 | std::span<IR::Block* const> block_map) { | 32 | block->SetReturn(); |
| 33 | for (const Flow::BlockId block_id : cfg_function.blocks) { | 33 | } else if (cfg_block.cond == IR::Condition{true}) { |
| 34 | const Flow::Block& flow_block{cfg_function.blocks_data[block_id]}; | 34 | block->SetBranch(cfg_block.branch_true->ir); |
| 35 | EmitTerminationCode(flow_block, block_map); | 35 | } else if (cfg_block.cond == IR::Condition{false}) { |
| 36 | } | 36 | block->SetBranch(cfg_block.branch_false->ir); |
| 37 | } | 37 | } else { |
| 38 | 38 | block->SetBranches(cfg_block.cond, cfg_block.branch_true->ir, | |
| 39 | void TranslateFunction(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, | 39 | cfg_block.branch_false->ir); |
| 40 | Environment& env, const Flow::Function& cfg_function, | 40 | } |
| 41 | IR::Function& function) { | 41 | }); |
| 42 | std::vector<IR::Block*> block_map; | 42 | return IR::VisitAST(inst_pool, block_pool, blocks, |
| 43 | block_map.resize(cfg_function.blocks_data.size()); | 43 | [&](IR::Block* block) { Translate(env, block); }); |
| 44 | |||
| 45 | TranslateCode(inst_pool, block_pool, env, cfg_function, function, block_map); | ||
| 46 | EmitTerminationInsts(cfg_function, block_map); | ||
| 47 | } | 44 | } |
| 48 | } // Anonymous namespace | 45 | } // Anonymous namespace |
| 49 | 46 | ||
| 50 | IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, | 47 | IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Block>& block_pool, |
| 51 | Environment& env, const Flow::CFG& cfg) { | 48 | Environment& env, Flow::CFG& cfg) { |
| 52 | IR::Program program; | 49 | IR::Program program; |
| 53 | auto& functions{program.functions}; | 50 | auto& functions{program.functions}; |
| 54 | functions.reserve(cfg.Functions().size()); | 51 | functions.reserve(cfg.Functions().size()); |
| 55 | for (const Flow::Function& cfg_function : cfg.Functions()) { | 52 | for (Flow::Function& cfg_function : cfg.Functions()) { |
| 56 | TranslateFunction(inst_pool, block_pool, env, cfg_function, functions.emplace_back()); | 53 | functions.push_back(IR::Function{ |
| 54 | .blocks{TranslateCode(inst_pool, block_pool, env, cfg_function)}, | ||
| 55 | }); | ||
| 57 | } | 56 | } |
| 57 | |||
| 58 | fmt::print(stdout, "No optimizations: {}", IR::DumpProgram(program)); | ||
| 58 | std::ranges::for_each(functions, Optimization::SsaRewritePass); | 59 | std::ranges::for_each(functions, Optimization::SsaRewritePass); |
| 59 | for (IR::Function& function : functions) { | 60 | for (IR::Function& function : functions) { |
| 60 | Optimization::Invoke(Optimization::GlobalMemoryToStorageBufferPass, function); | 61 | Optimization::Invoke(Optimization::GlobalMemoryToStorageBufferPass, function); |
diff --git a/src/shader_recompiler/frontend/maxwell/program.h b/src/shader_recompiler/frontend/maxwell/program.h index 3355ab129..542621a1d 100644 --- a/src/shader_recompiler/frontend/maxwell/program.h +++ b/src/shader_recompiler/frontend/maxwell/program.h | |||
| @@ -19,6 +19,6 @@ namespace Shader::Maxwell { | |||
| 19 | 19 | ||
| 20 | [[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, | 20 | [[nodiscard]] IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, |
| 21 | ObjectPool<IR::Block>& block_pool, Environment& env, | 21 | ObjectPool<IR::Block>& block_pool, Environment& env, |
| 22 | const Flow::CFG& cfg); | 22 | Flow::CFG& cfg); |
| 23 | 23 | ||
| 24 | } // namespace Shader::Maxwell | 24 | } // namespace Shader::Maxwell |
diff --git a/src/shader_recompiler/frontend/maxwell/termination_code.cpp b/src/shader_recompiler/frontend/maxwell/termination_code.cpp deleted file mode 100644 index ed5137f20..000000000 --- a/src/shader_recompiler/frontend/maxwell/termination_code.cpp +++ /dev/null | |||
| @@ -1,86 +0,0 @@ | |||
| 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 <span> | ||
| 6 | |||
| 7 | #include "shader_recompiler/exception.h" | ||
| 8 | #include "shader_recompiler/frontend/ir/basic_block.h" | ||
| 9 | #include "shader_recompiler/frontend/ir/ir_emitter.h" | ||
| 10 | #include "shader_recompiler/frontend/maxwell/control_flow.h" | ||
| 11 | #include "shader_recompiler/frontend/maxwell/termination_code.h" | ||
| 12 | |||
| 13 | namespace Shader::Maxwell { | ||
| 14 | |||
| 15 | static void EmitExit(IR::IREmitter& ir) { | ||
| 16 | ir.Exit(); | ||
| 17 | } | ||
| 18 | |||
| 19 | static IR::U1 GetFlowTest(IR::FlowTest flow_test, IR::IREmitter& ir) { | ||
| 20 | switch (flow_test) { | ||
| 21 | case IR::FlowTest::T: | ||
| 22 | return ir.Imm1(true); | ||
| 23 | case IR::FlowTest::F: | ||
| 24 | return ir.Imm1(false); | ||
| 25 | case IR::FlowTest::NE: | ||
| 26 | // FIXME: Verify this | ||
| 27 | return ir.LogicalNot(ir.GetZFlag()); | ||
| 28 | case IR::FlowTest::NaN: | ||
| 29 | // FIXME: Verify this | ||
| 30 | return ir.LogicalAnd(ir.GetSFlag(), ir.GetZFlag()); | ||
| 31 | default: | ||
| 32 | throw NotImplementedException("Flow test {}", flow_test); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | static IR::U1 GetCond(IR::Condition cond, IR::IREmitter& ir) { | ||
| 37 | const IR::FlowTest flow_test{cond.FlowTest()}; | ||
| 38 | const auto [pred, pred_negated]{cond.Pred()}; | ||
| 39 | if (pred == IR::Pred::PT && !pred_negated) { | ||
| 40 | return GetFlowTest(flow_test, ir); | ||
| 41 | } | ||
| 42 | if (flow_test == IR::FlowTest::T) { | ||
| 43 | return ir.GetPred(pred, pred_negated); | ||
| 44 | } | ||
| 45 | return ir.LogicalAnd(ir.GetPred(pred, pred_negated), GetFlowTest(flow_test, ir)); | ||
| 46 | } | ||
| 47 | |||
| 48 | static void EmitBranch(const Flow::Block& flow_block, std::span<IR::Block* const> block_map, | ||
| 49 | IR::IREmitter& ir) { | ||
| 50 | const auto add_immediate_predecessor = [&](Flow::BlockId label) { | ||
| 51 | block_map[label]->AddImmediatePredecessor(&ir.block); | ||
| 52 | }; | ||
| 53 | if (flow_block.cond == true) { | ||
| 54 | add_immediate_predecessor(flow_block.branch_true); | ||
| 55 | return ir.Branch(block_map[flow_block.branch_true]); | ||
| 56 | } | ||
| 57 | if (flow_block.cond == false) { | ||
| 58 | add_immediate_predecessor(flow_block.branch_false); | ||
| 59 | return ir.Branch(block_map[flow_block.branch_false]); | ||
| 60 | } | ||
| 61 | add_immediate_predecessor(flow_block.branch_true); | ||
| 62 | add_immediate_predecessor(flow_block.branch_false); | ||
| 63 | return ir.BranchConditional(GetCond(flow_block.cond, ir), block_map[flow_block.branch_true], | ||
| 64 | block_map[flow_block.branch_false]); | ||
| 65 | } | ||
| 66 | |||
| 67 | void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map) { | ||
| 68 | IR::Block* const block{block_map[flow_block.id]}; | ||
| 69 | IR::IREmitter ir(*block); | ||
| 70 | switch (flow_block.end_class) { | ||
| 71 | case Flow::EndClass::Branch: | ||
| 72 | EmitBranch(flow_block, block_map, ir); | ||
| 73 | break; | ||
| 74 | case Flow::EndClass::Exit: | ||
| 75 | EmitExit(ir); | ||
| 76 | break; | ||
| 77 | case Flow::EndClass::Return: | ||
| 78 | ir.Return(); | ||
| 79 | break; | ||
| 80 | case Flow::EndClass::Unreachable: | ||
| 81 | ir.Unreachable(); | ||
| 82 | break; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | } // namespace Shader::Maxwell | ||
diff --git a/src/shader_recompiler/frontend/maxwell/termination_code.h b/src/shader_recompiler/frontend/maxwell/termination_code.h deleted file mode 100644 index 04e044534..000000000 --- a/src/shader_recompiler/frontend/maxwell/termination_code.h +++ /dev/null | |||
| @@ -1,17 +0,0 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "shader_recompiler/frontend/ir/basic_block.h" | ||
| 10 | #include "shader_recompiler/frontend/maxwell/control_flow.h" | ||
| 11 | |||
| 12 | namespace Shader::Maxwell { | ||
| 13 | |||
| 14 | /// Emit termination instructions and collect immediate predecessors | ||
| 15 | void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map); | ||
| 16 | |||
| 17 | } // namespace Shader::Maxwell | ||
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp index d4b417d14..b752785d4 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/integer_shift_left.cpp | |||
| @@ -28,7 +28,7 @@ void SHL(TranslatorVisitor& v, u64 insn, const IR::U32& unsafe_shift) { | |||
| 28 | IR::U32 result; | 28 | IR::U32 result; |
| 29 | if (shl.w != 0) { | 29 | if (shl.w != 0) { |
| 30 | // When .W is set, the shift value is wrapped | 30 | // When .W is set, the shift value is wrapped |
| 31 | // To emulate this we just have to clamp it ourselves. | 31 | // To emulate this we just have to wrap it ourselves. |
| 32 | const IR::U32 shift{v.ir.BitwiseAnd(unsafe_shift, v.ir.Imm32(31))}; | 32 | const IR::U32 shift{v.ir.BitwiseAnd(unsafe_shift, v.ir.Imm32(31))}; |
| 33 | result = v.ir.ShiftLeftLogical(base, shift); | 33 | result = v.ir.ShiftLeftLogical(base, shift); |
| 34 | } else { | 34 | } else { |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.cpp b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp index 7e6bb07a2..f1230f58f 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/translate.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp | |||
| @@ -23,14 +23,13 @@ static void Invoke(TranslatorVisitor& visitor, Location pc, u64 insn) { | |||
| 23 | } | 23 | } |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | IR::Block Translate(ObjectPool<IR::Inst>& inst_pool, Environment& env, | 26 | void Translate(Environment& env, IR::Block* block) { |
| 27 | const Flow::Block& flow_block) { | 27 | if (block->IsVirtual()) { |
| 28 | IR::Block block{inst_pool, flow_block.begin.Offset(), flow_block.end.Offset()}; | 28 | return; |
| 29 | TranslatorVisitor visitor{env, block}; | 29 | } |
| 30 | 30 | TranslatorVisitor visitor{env, *block}; | |
| 31 | const Location pc_end{flow_block.end}; | 31 | const Location pc_end{block->LocationEnd()}; |
| 32 | Location pc{flow_block.begin}; | 32 | for (Location pc = block->LocationBegin(); pc != pc_end; ++pc) { |
| 33 | while (pc != pc_end) { | ||
| 34 | const u64 insn{env.ReadInstruction(pc.Offset())}; | 33 | const u64 insn{env.ReadInstruction(pc.Offset())}; |
| 35 | const Opcode opcode{Decode(insn)}; | 34 | const Opcode opcode{Decode(insn)}; |
| 36 | switch (opcode) { | 35 | switch (opcode) { |
| @@ -43,9 +42,7 @@ IR::Block Translate(ObjectPool<IR::Inst>& inst_pool, Environment& env, | |||
| 43 | default: | 42 | default: |
| 44 | throw LogicError("Invalid opcode {}", opcode); | 43 | throw LogicError("Invalid opcode {}", opcode); |
| 45 | } | 44 | } |
| 46 | ++pc; | ||
| 47 | } | 45 | } |
| 48 | return block; | ||
| 49 | } | 46 | } |
| 50 | 47 | ||
| 51 | } // namespace Shader::Maxwell | 48 | } // namespace Shader::Maxwell |
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.h b/src/shader_recompiler/frontend/maxwell/translate/translate.h index c1c21b278..e1aa2e0f4 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/translate.h +++ b/src/shader_recompiler/frontend/maxwell/translate/translate.h | |||
| @@ -6,14 +6,9 @@ | |||
| 6 | 6 | ||
| 7 | #include "shader_recompiler/environment.h" | 7 | #include "shader_recompiler/environment.h" |
| 8 | #include "shader_recompiler/frontend/ir/basic_block.h" | 8 | #include "shader_recompiler/frontend/ir/basic_block.h" |
| 9 | #include "shader_recompiler/frontend/ir/microinstruction.h" | ||
| 10 | #include "shader_recompiler/frontend/maxwell/control_flow.h" | ||
| 11 | #include "shader_recompiler/frontend/maxwell/location.h" | ||
| 12 | #include "shader_recompiler/object_pool.h" | ||
| 13 | 9 | ||
| 14 | namespace Shader::Maxwell { | 10 | namespace Shader::Maxwell { |
| 15 | 11 | ||
| 16 | [[nodiscard]] IR::Block Translate(ObjectPool<IR::Inst>& inst_pool, Environment& env, | 12 | void Translate(Environment& env, IR::Block* block); |
| 17 | const Flow::Block& flow_block); | ||
| 18 | 13 | ||
| 19 | } // namespace Shader::Maxwell | 14 | } // namespace Shader::Maxwell |