|
@@ -17,38 +17,49 @@
|
|
|
#include "shader_recompiler/frontend/maxwell/location.h"
|
|
#include "shader_recompiler/frontend/maxwell/location.h"
|
|
|
|
|
|
|
|
namespace Shader::Maxwell::Flow {
|
|
namespace Shader::Maxwell::Flow {
|
|
|
|
|
+namespace {
|
|
|
|
|
+struct Compare {
|
|
|
|
|
+ bool operator()(const Block& lhs, Location rhs) const noexcept {
|
|
|
|
|
+ return lhs.begin < rhs;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool operator()(Location lhs, const Block& rhs) const noexcept {
|
|
|
|
|
+ return lhs < rhs.begin;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool operator()(const Block& lhs, const Block& rhs) const noexcept {
|
|
|
|
|
+ return lhs.begin < rhs.begin;
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+} // Anonymous namespace
|
|
|
|
|
|
|
|
static u32 BranchOffset(Location pc, Instruction inst) {
|
|
static u32 BranchOffset(Location pc, Instruction inst) {
|
|
|
return pc.Offset() + inst.branch.Offset() + 8;
|
|
return pc.Offset() + inst.branch.Offset() + 8;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static std::array<Block, 2> Split(Block&& block, Location pc, BlockId new_id) {
|
|
|
|
|
- if (pc <= block.begin || pc >= block.end) {
|
|
|
|
|
|
|
+static void Split(Block* old_block, Block* new_block, Location pc) {
|
|
|
|
|
+ if (pc <= old_block->begin || pc >= old_block->end) {
|
|
|
throw InvalidArgument("Invalid address to split={}", pc);
|
|
throw InvalidArgument("Invalid address to split={}", pc);
|
|
|
}
|
|
}
|
|
|
- return {
|
|
|
|
|
- Block{
|
|
|
|
|
- .begin{block.begin},
|
|
|
|
|
- .end{pc},
|
|
|
|
|
- .end_class{EndClass::Branch},
|
|
|
|
|
- .id{block.id},
|
|
|
|
|
- .stack{block.stack},
|
|
|
|
|
- .cond{true},
|
|
|
|
|
- .branch_true{new_id},
|
|
|
|
|
- .branch_false{UNREACHABLE_BLOCK_ID},
|
|
|
|
|
- .imm_predecessors{},
|
|
|
|
|
- },
|
|
|
|
|
- Block{
|
|
|
|
|
- .begin{pc},
|
|
|
|
|
- .end{block.end},
|
|
|
|
|
- .end_class{block.end_class},
|
|
|
|
|
- .id{new_id},
|
|
|
|
|
- .stack{std::move(block.stack)},
|
|
|
|
|
- .cond{block.cond},
|
|
|
|
|
- .branch_true{block.branch_true},
|
|
|
|
|
- .branch_false{block.branch_false},
|
|
|
|
|
- .imm_predecessors{},
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ *new_block = Block{
|
|
|
|
|
+ .begin{pc},
|
|
|
|
|
+ .end{old_block->end},
|
|
|
|
|
+ .end_class{old_block->end_class},
|
|
|
|
|
+ .stack{old_block->stack},
|
|
|
|
|
+ .cond{old_block->cond},
|
|
|
|
|
+ .branch_true{old_block->branch_true},
|
|
|
|
|
+ .branch_false{old_block->branch_false},
|
|
|
|
|
+ .ir{nullptr},
|
|
|
|
|
+ };
|
|
|
|
|
+ *old_block = Block{
|
|
|
|
|
+ .begin{old_block->begin},
|
|
|
|
|
+ .end{pc},
|
|
|
|
|
+ .end_class{EndClass::Branch},
|
|
|
|
|
+ .stack{std::move(old_block->stack)},
|
|
|
|
|
+ .cond{IR::Condition{true}},
|
|
|
|
|
+ .branch_true{new_block},
|
|
|
|
|
+ .branch_false{nullptr},
|
|
|
|
|
+ .ir{nullptr},
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -112,7 +123,7 @@ static bool HasFlowTest(Opcode opcode) {
|
|
|
|
|
|
|
|
static std::string NameOf(const Block& block) {
|
|
static std::string NameOf(const Block& block) {
|
|
|
if (block.begin.IsVirtual()) {
|
|
if (block.begin.IsVirtual()) {
|
|
|
- return fmt::format("\"Virtual {}\"", block.id);
|
|
|
|
|
|
|
+ return fmt::format("\"Virtual {}\"", block.begin);
|
|
|
} else {
|
|
} else {
|
|
|
return fmt::format("\"{}\"", block.begin);
|
|
return fmt::format("\"{}\"", block.begin);
|
|
|
}
|
|
}
|
|
@@ -158,126 +169,23 @@ bool Block::Contains(Location pc) const noexcept {
|
|
|
Function::Function(Location start_address)
|
|
Function::Function(Location start_address)
|
|
|
: entrypoint{start_address}, labels{{
|
|
: entrypoint{start_address}, labels{{
|
|
|
.address{start_address},
|
|
.address{start_address},
|
|
|
- .block_id{0},
|
|
|
|
|
|
|
+ .block{nullptr},
|
|
|
.stack{},
|
|
.stack{},
|
|
|
}} {}
|
|
}} {}
|
|
|
|
|
|
|
|
-void Function::BuildBlocksMap() {
|
|
|
|
|
- const size_t num_blocks{NumBlocks()};
|
|
|
|
|
- blocks_map.resize(num_blocks);
|
|
|
|
|
- for (size_t block_index = 0; block_index < num_blocks; ++block_index) {
|
|
|
|
|
- Block& block{blocks_data[block_index]};
|
|
|
|
|
- blocks_map[block.id] = █
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void Function::BuildImmediatePredecessors() {
|
|
|
|
|
- for (const Block& block : blocks_data) {
|
|
|
|
|
- if (block.branch_true != UNREACHABLE_BLOCK_ID) {
|
|
|
|
|
- blocks_map[block.branch_true]->imm_predecessors.push_back(block.id);
|
|
|
|
|
- }
|
|
|
|
|
- if (block.branch_false != UNREACHABLE_BLOCK_ID) {
|
|
|
|
|
- blocks_map[block.branch_false]->imm_predecessors.push_back(block.id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void Function::BuildPostOrder() {
|
|
|
|
|
- boost::container::small_vector<BlockId, 0x110> block_stack;
|
|
|
|
|
- post_order_map.resize(NumBlocks());
|
|
|
|
|
-
|
|
|
|
|
- Block& first_block{blocks_data[blocks.front()]};
|
|
|
|
|
- first_block.post_order_visited = true;
|
|
|
|
|
- block_stack.push_back(first_block.id);
|
|
|
|
|
-
|
|
|
|
|
- const auto visit_branch = [&](BlockId block_id, BlockId branch_id) {
|
|
|
|
|
- if (branch_id == UNREACHABLE_BLOCK_ID) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- if (blocks_map[branch_id]->post_order_visited) {
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
- blocks_map[branch_id]->post_order_visited = true;
|
|
|
|
|
-
|
|
|
|
|
- // Calling push_back twice is faster than insert on msvc
|
|
|
|
|
- block_stack.push_back(block_id);
|
|
|
|
|
- block_stack.push_back(branch_id);
|
|
|
|
|
- return true;
|
|
|
|
|
- };
|
|
|
|
|
- while (!block_stack.empty()) {
|
|
|
|
|
- const Block* const block{blocks_map[block_stack.back()]};
|
|
|
|
|
- block_stack.pop_back();
|
|
|
|
|
-
|
|
|
|
|
- if (!visit_branch(block->id, block->branch_true) &&
|
|
|
|
|
- !visit_branch(block->id, block->branch_false)) {
|
|
|
|
|
- post_order_map[block->id] = static_cast<u32>(post_order_blocks.size());
|
|
|
|
|
- post_order_blocks.push_back(block->id);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void Function::BuildImmediateDominators() {
|
|
|
|
|
- auto transform_block_id{std::views::transform([this](BlockId id) { return blocks_map[id]; })};
|
|
|
|
|
- auto reverse_order_but_first{std::views::reverse | std::views::drop(1) | transform_block_id};
|
|
|
|
|
- auto has_idom{std::views::filter([](Block* block) { return block->imm_dominator; })};
|
|
|
|
|
- auto intersect{[this](Block* finger1, Block* finger2) {
|
|
|
|
|
- while (finger1 != finger2) {
|
|
|
|
|
- while (post_order_map[finger1->id] < post_order_map[finger2->id]) {
|
|
|
|
|
- finger1 = finger1->imm_dominator;
|
|
|
|
|
- }
|
|
|
|
|
- while (post_order_map[finger2->id] < post_order_map[finger1->id]) {
|
|
|
|
|
- finger2 = finger2->imm_dominator;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return finger1;
|
|
|
|
|
- }};
|
|
|
|
|
- for (Block& block : blocks_data) {
|
|
|
|
|
- block.imm_dominator = nullptr;
|
|
|
|
|
- }
|
|
|
|
|
- Block* const start_block{&blocks_data[blocks.front()]};
|
|
|
|
|
- start_block->imm_dominator = start_block;
|
|
|
|
|
-
|
|
|
|
|
- bool changed{true};
|
|
|
|
|
- while (changed) {
|
|
|
|
|
- changed = false;
|
|
|
|
|
- for (Block* const block : post_order_blocks | reverse_order_but_first) {
|
|
|
|
|
- Block* new_idom{};
|
|
|
|
|
- for (Block* predecessor : block->imm_predecessors | transform_block_id | has_idom) {
|
|
|
|
|
- new_idom = new_idom ? intersect(predecessor, new_idom) : predecessor;
|
|
|
|
|
- }
|
|
|
|
|
- changed |= block->imm_dominator != new_idom;
|
|
|
|
|
- block->imm_dominator = new_idom;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void Function::BuildDominanceFrontier() {
|
|
|
|
|
- auto transform_block_id{std::views::transform([this](BlockId id) { return blocks_map[id]; })};
|
|
|
|
|
- auto has_enough_predecessors{[](Block& block) { return block.imm_predecessors.size() >= 2; }};
|
|
|
|
|
- for (Block& block : blocks_data | std::views::filter(has_enough_predecessors)) {
|
|
|
|
|
- for (Block* current : block.imm_predecessors | transform_block_id) {
|
|
|
|
|
- while (current != block.imm_dominator) {
|
|
|
|
|
- current->dominance_frontiers.push_back(current->id);
|
|
|
|
|
- current = current->imm_dominator;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-CFG::CFG(Environment& env_, Location start_address) : env{env_} {
|
|
|
|
|
- VisitFunctions(start_address);
|
|
|
|
|
-
|
|
|
|
|
- for (Function& function : functions) {
|
|
|
|
|
- function.BuildBlocksMap();
|
|
|
|
|
- function.BuildImmediatePredecessors();
|
|
|
|
|
- function.BuildPostOrder();
|
|
|
|
|
- function.BuildImmediateDominators();
|
|
|
|
|
- function.BuildDominanceFrontier();
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-void CFG::VisitFunctions(Location start_address) {
|
|
|
|
|
|
|
+CFG::CFG(Environment& env_, ObjectPool<Block>& block_pool_, Location start_address)
|
|
|
|
|
+ : env{env_}, block_pool{block_pool_} {
|
|
|
functions.emplace_back(start_address);
|
|
functions.emplace_back(start_address);
|
|
|
|
|
+ functions.back().labels.back().block = block_pool.Create(Block{
|
|
|
|
|
+ .begin{start_address},
|
|
|
|
|
+ .end{start_address},
|
|
|
|
|
+ .end_class{EndClass::Branch},
|
|
|
|
|
+ .stack{},
|
|
|
|
|
+ .cond{IR::Condition{true}},
|
|
|
|
|
+ .branch_true{nullptr},
|
|
|
|
|
+ .branch_false{nullptr},
|
|
|
|
|
+ .ir{nullptr},
|
|
|
|
|
+ });
|
|
|
for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
|
|
for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
|
|
|
while (!functions[function_id].labels.empty()) {
|
|
while (!functions[function_id].labels.empty()) {
|
|
|
Function& function{functions[function_id]};
|
|
Function& function{functions[function_id]};
|
|
@@ -294,35 +202,16 @@ void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
// Try to find the next block
|
|
// Try to find the next block
|
|
|
- Function* function{&functions[function_id]};
|
|
|
|
|
|
|
+ Function* const function{&functions[function_id]};
|
|
|
Location pc{label.address};
|
|
Location pc{label.address};
|
|
|
- const auto next{std::upper_bound(function->blocks.begin(), function->blocks.end(), pc,
|
|
|
|
|
- [function](Location pc, u32 block_index) {
|
|
|
|
|
- return pc < function->blocks_data[block_index].begin;
|
|
|
|
|
- })};
|
|
|
|
|
- const auto next_index{std::distance(function->blocks.begin(), next)};
|
|
|
|
|
- const bool is_last{next == function->blocks.end()};
|
|
|
|
|
- Location next_pc;
|
|
|
|
|
- BlockId next_id{UNREACHABLE_BLOCK_ID};
|
|
|
|
|
- if (!is_last) {
|
|
|
|
|
- next_pc = function->blocks_data[*next].begin;
|
|
|
|
|
- next_id = function->blocks_data[*next].id;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const auto next_it{function->blocks.upper_bound(pc, Compare{})};
|
|
|
|
|
+ const bool is_last{next_it == function->blocks.end()};
|
|
|
|
|
+ Block* const next{is_last ? nullptr : &*next_it};
|
|
|
// Insert before the next block
|
|
// Insert before the next block
|
|
|
- Block block{
|
|
|
|
|
- .begin{pc},
|
|
|
|
|
- .end{pc},
|
|
|
|
|
- .end_class{EndClass::Branch},
|
|
|
|
|
- .id{label.block_id},
|
|
|
|
|
- .stack{std::move(label.stack)},
|
|
|
|
|
- .cond{true},
|
|
|
|
|
- .branch_true{UNREACHABLE_BLOCK_ID},
|
|
|
|
|
- .branch_false{UNREACHABLE_BLOCK_ID},
|
|
|
|
|
- .imm_predecessors{},
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ Block* const block{label.block};
|
|
|
// Analyze instructions until it reaches an already visited block or there's a branch
|
|
// Analyze instructions until it reaches an already visited block or there's a branch
|
|
|
bool is_branch{false};
|
|
bool is_branch{false};
|
|
|
- while (is_last || pc < next_pc) {
|
|
|
|
|
|
|
+ while (!next || pc < next->begin) {
|
|
|
is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
|
|
is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
|
|
|
if (is_branch) {
|
|
if (is_branch) {
|
|
|
break;
|
|
break;
|
|
@@ -332,43 +221,36 @@ void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
|
|
|
if (!is_branch) {
|
|
if (!is_branch) {
|
|
|
// If the block finished without a branch,
|
|
// If the block finished without a branch,
|
|
|
// it means that the next instruction is already visited, jump to it
|
|
// it means that the next instruction is already visited, jump to it
|
|
|
- block.end = pc;
|
|
|
|
|
- block.cond = true;
|
|
|
|
|
- block.branch_true = next_id;
|
|
|
|
|
- block.branch_false = UNREACHABLE_BLOCK_ID;
|
|
|
|
|
|
|
+ block->end = pc;
|
|
|
|
|
+ block->cond = IR::Condition{true};
|
|
|
|
|
+ block->branch_true = next;
|
|
|
|
|
+ block->branch_false = nullptr;
|
|
|
}
|
|
}
|
|
|
// Function's pointer might be invalid, resolve it again
|
|
// Function's pointer might be invalid, resolve it again
|
|
|
- function = &functions[function_id];
|
|
|
|
|
- const u32 new_block_index = static_cast<u32>(function->blocks_data.size());
|
|
|
|
|
- function->blocks.insert(function->blocks.begin() + next_index, new_block_index);
|
|
|
|
|
- function->blocks_data.push_back(std::move(block));
|
|
|
|
|
|
|
+ // Insert the new block
|
|
|
|
|
+ functions[function_id].blocks.insert(*block);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
|
|
bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
|
|
|
const Location pc{label.address};
|
|
const Location pc{label.address};
|
|
|
Function& function{functions[function_id]};
|
|
Function& function{functions[function_id]};
|
|
|
- const auto it{std::ranges::find_if(function.blocks, [&function, pc](u32 block_index) {
|
|
|
|
|
- return function.blocks_data[block_index].Contains(pc);
|
|
|
|
|
- })};
|
|
|
|
|
|
|
+ const auto it{
|
|
|
|
|
+ std::ranges::find_if(function.blocks, [pc](auto& block) { return block.Contains(pc); })};
|
|
|
if (it == function.blocks.end()) {
|
|
if (it == function.blocks.end()) {
|
|
|
// Address has not been visited
|
|
// Address has not been visited
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
- Block& block{function.blocks_data[*it]};
|
|
|
|
|
- if (block.begin == pc) {
|
|
|
|
|
- throw LogicError("Dangling branch");
|
|
|
|
|
- }
|
|
|
|
|
- const u32 first_index{*it};
|
|
|
|
|
- const u32 second_index{static_cast<u32>(function.blocks_data.size())};
|
|
|
|
|
- const std::array new_indices{first_index, second_index};
|
|
|
|
|
- std::array split_blocks{Split(std::move(block), pc, label.block_id)};
|
|
|
|
|
- function.blocks_data[*it] = std::move(split_blocks[0]);
|
|
|
|
|
- function.blocks_data.push_back(std::move(split_blocks[1]));
|
|
|
|
|
- function.blocks.insert(function.blocks.erase(it), new_indices.begin(), new_indices.end());
|
|
|
|
|
|
|
+ Block* const visited_block{&*it};
|
|
|
|
|
+ if (visited_block->begin == pc) {
|
|
|
|
|
+ throw LogicError("Dangling block");
|
|
|
|
|
+ }
|
|
|
|
|
+ Block* const new_block{label.block};
|
|
|
|
|
+ Split(visited_block, new_block, pc);
|
|
|
|
|
+ function.blocks.insert(it, *new_block);
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Location pc) {
|
|
|
|
|
|
|
+CFG::AnalysisState CFG::AnalyzeInst(Block* block, FunctionId function_id, Location pc) {
|
|
|
const Instruction inst{env.ReadInstruction(pc.Offset())};
|
|
const Instruction inst{env.ReadInstruction(pc.Offset())};
|
|
|
const Opcode opcode{Decode(inst.raw)};
|
|
const Opcode opcode{Decode(inst.raw)};
|
|
|
switch (opcode) {
|
|
switch (opcode) {
|
|
@@ -390,12 +272,12 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati
|
|
|
AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode));
|
|
AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode));
|
|
|
break;
|
|
break;
|
|
|
case Opcode::RET:
|
|
case Opcode::RET:
|
|
|
- block.end_class = EndClass::Return;
|
|
|
|
|
|
|
+ block->end_class = EndClass::Return;
|
|
|
break;
|
|
break;
|
|
|
default:
|
|
default:
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
- block.end = pc;
|
|
|
|
|
|
|
+ block->end = pc;
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
case Opcode::BRK:
|
|
case Opcode::BRK:
|
|
|
case Opcode::CONT:
|
|
case Opcode::CONT:
|
|
@@ -404,9 +286,9 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati
|
|
|
if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
|
|
if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
|
|
|
return AnalysisState::Continue;
|
|
return AnalysisState::Continue;
|
|
|
}
|
|
}
|
|
|
- const auto [stack_pc, new_stack]{block.stack.Pop(OpcodeToken(opcode))};
|
|
|
|
|
- block.branch_true = AddLabel(block, new_stack, stack_pc, function_id);
|
|
|
|
|
- block.end = pc;
|
|
|
|
|
|
|
+ const auto [stack_pc, new_stack]{block->stack.Pop(OpcodeToken(opcode))};
|
|
|
|
|
+ block->branch_true = AddLabel(block, new_stack, stack_pc, function_id);
|
|
|
|
|
+ block->end = pc;
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
}
|
|
}
|
|
|
case Opcode::PBK:
|
|
case Opcode::PBK:
|
|
@@ -414,7 +296,7 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati
|
|
|
case Opcode::PEXIT:
|
|
case Opcode::PEXIT:
|
|
|
case Opcode::PLONGJMP:
|
|
case Opcode::PLONGJMP:
|
|
|
case Opcode::SSY:
|
|
case Opcode::SSY:
|
|
|
- block.stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
|
|
|
|
|
|
|
+ block->stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
|
|
|
return AnalysisState::Continue;
|
|
return AnalysisState::Continue;
|
|
|
case Opcode::EXIT:
|
|
case Opcode::EXIT:
|
|
|
return AnalyzeEXIT(block, function_id, pc, inst);
|
|
return AnalyzeEXIT(block, function_id, pc, inst);
|
|
@@ -444,51 +326,51 @@ CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Locati
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void CFG::AnalyzeCondInst(Block& block, FunctionId function_id, Location pc,
|
|
|
|
|
|
|
+void CFG::AnalyzeCondInst(Block* block, FunctionId function_id, Location pc,
|
|
|
EndClass insn_end_class, IR::Condition cond) {
|
|
EndClass insn_end_class, IR::Condition cond) {
|
|
|
- if (block.begin != pc) {
|
|
|
|
|
|
|
+ if (block->begin != pc) {
|
|
|
// If the block doesn't start in the conditional instruction
|
|
// If the block doesn't start in the conditional instruction
|
|
|
// mark it as a label to visit it later
|
|
// mark it as a label to visit it later
|
|
|
- block.end = pc;
|
|
|
|
|
- block.cond = true;
|
|
|
|
|
- block.branch_true = AddLabel(block, block.stack, pc, function_id);
|
|
|
|
|
- block.branch_false = UNREACHABLE_BLOCK_ID;
|
|
|
|
|
|
|
+ block->end = pc;
|
|
|
|
|
+ block->cond = IR::Condition{true};
|
|
|
|
|
+ block->branch_true = AddLabel(block, block->stack, pc, function_id);
|
|
|
|
|
+ block->branch_false = nullptr;
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- // Impersonate the visited block with a virtual block
|
|
|
|
|
- // Jump from this virtual to the real conditional instruction and the next instruction
|
|
|
|
|
- Function& function{functions[function_id]};
|
|
|
|
|
- const BlockId conditional_block_id{++function.current_block_id};
|
|
|
|
|
- function.blocks.push_back(static_cast<u32>(function.blocks_data.size()));
|
|
|
|
|
- Block& virtual_block{function.blocks_data.emplace_back(Block{
|
|
|
|
|
- .begin{}, // Virtual block
|
|
|
|
|
- .end{},
|
|
|
|
|
|
|
+ // Create a virtual block and a conditional block
|
|
|
|
|
+ Block* const conditional_block{block_pool.Create()};
|
|
|
|
|
+ Block virtual_block{
|
|
|
|
|
+ .begin{block->begin.Virtual()},
|
|
|
|
|
+ .end{block->begin.Virtual()},
|
|
|
.end_class{EndClass::Branch},
|
|
.end_class{EndClass::Branch},
|
|
|
- .id{block.id}, // Impersonating
|
|
|
|
|
- .stack{block.stack},
|
|
|
|
|
|
|
+ .stack{block->stack},
|
|
|
.cond{cond},
|
|
.cond{cond},
|
|
|
- .branch_true{conditional_block_id},
|
|
|
|
|
- .branch_false{UNREACHABLE_BLOCK_ID},
|
|
|
|
|
- .imm_predecessors{},
|
|
|
|
|
- })};
|
|
|
|
|
- // Set the end properties of the conditional instruction and give it a new identity
|
|
|
|
|
- Block& conditional_block{block};
|
|
|
|
|
- conditional_block.end = pc;
|
|
|
|
|
- conditional_block.end_class = insn_end_class;
|
|
|
|
|
- conditional_block.id = conditional_block_id;
|
|
|
|
|
|
|
+ .branch_true{conditional_block},
|
|
|
|
|
+ .branch_false{nullptr},
|
|
|
|
|
+ .ir{nullptr},
|
|
|
|
|
+ };
|
|
|
|
|
+ // Save the contents of the visited block in the conditional block
|
|
|
|
|
+ *conditional_block = std::move(*block);
|
|
|
|
|
+ // Impersonate the visited block with a virtual block
|
|
|
|
|
+ *block = std::move(virtual_block);
|
|
|
|
|
+ // Set the end properties of the conditional instruction
|
|
|
|
|
+ conditional_block->end = pc;
|
|
|
|
|
+ conditional_block->end_class = insn_end_class;
|
|
|
// Add a label to the instruction after the conditional instruction
|
|
// Add a label to the instruction after the conditional instruction
|
|
|
- const BlockId endif_block_id{AddLabel(conditional_block, block.stack, pc + 1, function_id)};
|
|
|
|
|
|
|
+ Block* const endif_block{AddLabel(conditional_block, block->stack, pc + 1, function_id)};
|
|
|
// Branch to the next instruction from the virtual block
|
|
// Branch to the next instruction from the virtual block
|
|
|
- virtual_block.branch_false = endif_block_id;
|
|
|
|
|
|
|
+ block->branch_false = endif_block;
|
|
|
// And branch to it from the conditional instruction if it is a branch
|
|
// And branch to it from the conditional instruction if it is a branch
|
|
|
if (insn_end_class == EndClass::Branch) {
|
|
if (insn_end_class == EndClass::Branch) {
|
|
|
- conditional_block.cond = true;
|
|
|
|
|
- conditional_block.branch_true = endif_block_id;
|
|
|
|
|
- conditional_block.branch_false = UNREACHABLE_BLOCK_ID;
|
|
|
|
|
|
|
+ conditional_block->cond = IR::Condition{true};
|
|
|
|
|
+ conditional_block->branch_true = endif_block;
|
|
|
|
|
+ conditional_block->branch_false = nullptr;
|
|
|
}
|
|
}
|
|
|
|
|
+ // Finally insert the condition block into the list of blocks
|
|
|
|
|
+ functions[function_id].blocks.insert(*conditional_block);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst,
|
|
|
|
|
|
|
+bool CFG::AnalyzeBranch(Block* block, FunctionId function_id, Location pc, Instruction inst,
|
|
|
Opcode opcode) {
|
|
Opcode opcode) {
|
|
|
if (inst.branch.is_cbuf) {
|
|
if (inst.branch.is_cbuf) {
|
|
|
throw NotImplementedException("Branch with constant buffer offset");
|
|
throw NotImplementedException("Branch with constant buffer offset");
|
|
@@ -500,21 +382,21 @@ bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instr
|
|
|
const bool has_flow_test{HasFlowTest(opcode)};
|
|
const bool has_flow_test{HasFlowTest(opcode)};
|
|
|
const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
|
|
const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
|
|
|
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
|
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
|
|
- block.cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
|
|
|
|
|
- block.branch_false = AddLabel(block, block.stack, pc + 1, function_id);
|
|
|
|
|
|
|
+ block->cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
|
|
|
|
|
+ block->branch_false = AddLabel(block, block->stack, pc + 1, function_id);
|
|
|
} else {
|
|
} else {
|
|
|
- block.cond = true;
|
|
|
|
|
|
|
+ block->cond = IR::Condition{true};
|
|
|
}
|
|
}
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void CFG::AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
|
|
|
|
|
|
|
+void CFG::AnalyzeBRA(Block* block, FunctionId function_id, Location pc, Instruction inst,
|
|
|
bool is_absolute) {
|
|
bool is_absolute) {
|
|
|
const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
|
|
const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
|
|
|
- block.branch_true = AddLabel(block, block.stack, bra_pc, function_id);
|
|
|
|
|
|
|
+ block->branch_true = AddLabel(block, block->stack, bra_pc, function_id);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) {
|
|
|
|
|
|
|
+void CFG::AnalyzeBRX(Block*, Location, Instruction, bool is_absolute) {
|
|
|
throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
|
|
throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -528,7 +410,7 @@ void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Location pc,
|
|
|
|
|
|
|
+CFG::AnalysisState CFG::AnalyzeEXIT(Block* block, FunctionId function_id, Location pc,
|
|
|
Instruction inst) {
|
|
Instruction inst) {
|
|
|
const IR::FlowTest flow_test{inst.branch.flow_test};
|
|
const IR::FlowTest flow_test{inst.branch.flow_test};
|
|
|
const Predicate pred{inst.Pred()};
|
|
const Predicate pred{inst.Pred()};
|
|
@@ -537,41 +419,52 @@ CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Locati
|
|
|
return AnalysisState::Continue;
|
|
return AnalysisState::Continue;
|
|
|
}
|
|
}
|
|
|
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
|
if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
|
|
|
- if (block.stack.Peek(Token::PEXIT).has_value()) {
|
|
|
|
|
|
|
+ if (block->stack.Peek(Token::PEXIT).has_value()) {
|
|
|
throw NotImplementedException("Conditional EXIT with PEXIT token");
|
|
throw NotImplementedException("Conditional EXIT with PEXIT token");
|
|
|
}
|
|
}
|
|
|
const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
|
|
const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
|
|
|
AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
|
|
AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
}
|
|
}
|
|
|
- if (const std::optional<Location> exit_pc{block.stack.Peek(Token::PEXIT)}) {
|
|
|
|
|
- const Stack popped_stack{block.stack.Remove(Token::PEXIT)};
|
|
|
|
|
- block.cond = true;
|
|
|
|
|
- block.branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
|
|
|
|
|
- block.branch_false = UNREACHABLE_BLOCK_ID;
|
|
|
|
|
|
|
+ if (const std::optional<Location> exit_pc{block->stack.Peek(Token::PEXIT)}) {
|
|
|
|
|
+ const Stack popped_stack{block->stack.Remove(Token::PEXIT)};
|
|
|
|
|
+ block->cond = IR::Condition{true};
|
|
|
|
|
+ block->branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
|
|
|
|
|
+ block->branch_false = nullptr;
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
}
|
|
}
|
|
|
- block.end = pc;
|
|
|
|
|
- block.end_class = EndClass::Exit;
|
|
|
|
|
|
|
+ block->end = pc;
|
|
|
|
|
+ block->end_class = EndClass::Exit;
|
|
|
return AnalysisState::Branch;
|
|
return AnalysisState::Branch;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-BlockId CFG::AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id) {
|
|
|
|
|
|
|
+Block* CFG::AddLabel(Block* block, Stack stack, Location pc, FunctionId function_id) {
|
|
|
Function& function{functions[function_id]};
|
|
Function& function{functions[function_id]};
|
|
|
- if (block.begin == pc) {
|
|
|
|
|
- return block.id;
|
|
|
|
|
|
|
+ if (block->begin == pc) {
|
|
|
|
|
+ // Jumps to itself
|
|
|
|
|
+ return block;
|
|
|
}
|
|
}
|
|
|
- const auto target{std::ranges::find(function.blocks_data, pc, &Block::begin)};
|
|
|
|
|
- if (target != function.blocks_data.end()) {
|
|
|
|
|
- return target->id;
|
|
|
|
|
|
|
+ if (const auto it{function.blocks.find(pc, Compare{})}; it != function.blocks.end()) {
|
|
|
|
|
+ // Block already exists and it has been visited
|
|
|
|
|
+ return &*it;
|
|
|
}
|
|
}
|
|
|
- const BlockId block_id{++function.current_block_id};
|
|
|
|
|
|
|
+ // TODO: FIX DANGLING BLOCKS
|
|
|
|
|
+ Block* const new_block{block_pool.Create(Block{
|
|
|
|
|
+ .begin{pc},
|
|
|
|
|
+ .end{pc},
|
|
|
|
|
+ .end_class{EndClass::Branch},
|
|
|
|
|
+ .stack{stack},
|
|
|
|
|
+ .cond{IR::Condition{true}},
|
|
|
|
|
+ .branch_true{nullptr},
|
|
|
|
|
+ .branch_false{nullptr},
|
|
|
|
|
+ .ir{nullptr},
|
|
|
|
|
+ })};
|
|
|
function.labels.push_back(Label{
|
|
function.labels.push_back(Label{
|
|
|
.address{pc},
|
|
.address{pc},
|
|
|
- .block_id{block_id},
|
|
|
|
|
|
|
+ .block{new_block},
|
|
|
.stack{std::move(stack)},
|
|
.stack{std::move(stack)},
|
|
|
});
|
|
});
|
|
|
- return block_id;
|
|
|
|
|
|
|
+ return new_block;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
std::string CFG::Dot() const {
|
|
std::string CFG::Dot() const {
|
|
@@ -581,18 +474,12 @@ std::string CFG::Dot() const {
|
|
|
for (const Function& function : functions) {
|
|
for (const Function& function : functions) {
|
|
|
dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
|
|
dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
|
|
|
dot += fmt::format("\t\tnode [style=filled];\n");
|
|
dot += fmt::format("\t\tnode [style=filled];\n");
|
|
|
- for (const u32 block_index : function.blocks) {
|
|
|
|
|
- const Block& block{function.blocks_data[block_index]};
|
|
|
|
|
|
|
+ for (const Block& block : function.blocks) {
|
|
|
const std::string name{NameOf(block)};
|
|
const std::string name{NameOf(block)};
|
|
|
- const auto add_branch = [&](BlockId branch_id, bool add_label) {
|
|
|
|
|
- const auto it{std::ranges::find(function.blocks_data, branch_id, &Block::id)};
|
|
|
|
|
- dot += fmt::format("\t\t{}->", name);
|
|
|
|
|
- if (it == function.blocks_data.end()) {
|
|
|
|
|
- dot += fmt::format("\"Unknown label {}\"", branch_id);
|
|
|
|
|
- } else {
|
|
|
|
|
- dot += NameOf(*it);
|
|
|
|
|
- };
|
|
|
|
|
- if (add_label && block.cond != true && block.cond != false) {
|
|
|
|
|
|
|
+ const auto add_branch = [&](Block* branch, bool add_label) {
|
|
|
|
|
+ dot += fmt::format("\t\t{}->{}", name, NameOf(*branch));
|
|
|
|
|
+ if (add_label && block.cond != IR::Condition{true} &&
|
|
|
|
|
+ block.cond != IR::Condition{false}) {
|
|
|
dot += fmt::format(" [label=\"{}\"]", block.cond);
|
|
dot += fmt::format(" [label=\"{}\"]", block.cond);
|
|
|
}
|
|
}
|
|
|
dot += '\n';
|
|
dot += '\n';
|
|
@@ -600,10 +487,10 @@ std::string CFG::Dot() const {
|
|
|
dot += fmt::format("\t\t{};\n", name);
|
|
dot += fmt::format("\t\t{};\n", name);
|
|
|
switch (block.end_class) {
|
|
switch (block.end_class) {
|
|
|
case EndClass::Branch:
|
|
case EndClass::Branch:
|
|
|
- if (block.cond != false) {
|
|
|
|
|
|
|
+ if (block.cond != IR::Condition{false}) {
|
|
|
add_branch(block.branch_true, true);
|
|
add_branch(block.branch_true, true);
|
|
|
}
|
|
}
|
|
|
- if (block.cond != true) {
|
|
|
|
|
|
|
+ if (block.cond != IR::Condition{true}) {
|
|
|
add_branch(block.branch_false, false);
|
|
add_branch(block.branch_false, false);
|
|
|
}
|
|
}
|
|
|
break;
|
|
break;
|
|
@@ -619,12 +506,6 @@ std::string CFG::Dot() const {
|
|
|
node_uid);
|
|
node_uid);
|
|
|
++node_uid;
|
|
++node_uid;
|
|
|
break;
|
|
break;
|
|
|
- case EndClass::Unreachable:
|
|
|
|
|
- dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
|
|
|
|
|
- dot += fmt::format(
|
|
|
|
|
- "\t\tN{} [label=\"Unreachable\"][shape=square][style=stripped];\n", node_uid);
|
|
|
|
|
- ++node_uid;
|
|
|
|
|
- break;
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if (function.entrypoint == 8) {
|
|
if (function.entrypoint == 8) {
|
|
@@ -635,10 +516,11 @@ std::string CFG::Dot() const {
|
|
|
dot += "\t}\n";
|
|
dot += "\t}\n";
|
|
|
}
|
|
}
|
|
|
if (!functions.empty()) {
|
|
if (!functions.empty()) {
|
|
|
- if (functions.front().blocks.empty()) {
|
|
|
|
|
|
|
+ auto& function{functions.front()};
|
|
|
|
|
+ if (function.blocks.empty()) {
|
|
|
dot += "Start;\n";
|
|
dot += "Start;\n";
|
|
|
} else {
|
|
} else {
|
|
|
- dot += fmt::format("\tStart -> {};\n", NameOf(functions.front().blocks_data.front()));
|
|
|
|
|
|
|
+ dot += fmt::format("\tStart -> {};\n", NameOf(*function.blocks.begin()));
|
|
|
}
|
|
}
|
|
|
dot += fmt::format("\tStart [shape=diamond];\n");
|
|
dot += fmt::format("\tStart [shape=diamond];\n");
|
|
|
}
|
|
}
|