diff --git a/CMakeLists.txt b/CMakeLists.txt index a890903..b426546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tests/parser_exact_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/parser_syntax_error_files_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/ir_constant_folding_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/ir_trivial_jump_block_elimination_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/ir_unreachable_block_elimination_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/symbol_table_test.cpp ) diff --git a/src/ir/passes/TrivialJumpBlockEliminationPass.cpp b/src/ir/passes/TrivialJumpBlockEliminationPass.cpp new file mode 100644 index 0000000..660d6a8 --- /dev/null +++ b/src/ir/passes/TrivialJumpBlockEliminationPass.cpp @@ -0,0 +1,221 @@ +#include "ir/passes/TrivialJumpBlockEliminationPass.hpp" + +#include +#include +#include +#include + +#include "ir/BBlock.hpp" +#include "ir/CFG.hpp" +#include "ir/Tac.hpp" + +namespace { + +using BlockNameMap = std::unordered_map; +using ResolveCache = std::unordered_map; + +[[nodiscard]] bool is_trampoline_block(const BBlock &block) { + const auto &instructions = block.getInstructions(); + if (instructions.size() != 1U) { + return false; + } + if (dynamic_cast(instructions.front().get()) == nullptr) { + return false; + } + return block.hasTrueBlock() && !block.hasFalseBlock(); +} + +void collect_method_blocks(BBlock *root, std::vector &blocks, + BlockNameMap &block_names) { + std::vector stack{root}; + std::unordered_set visited; + + while (!stack.empty()) { + auto *block = stack.back(); + stack.pop_back(); + + if (!visited.insert(block).second) { + continue; + } + + blocks.push_back(block); + block_names[block->getName()] = block; + + if (block->hasTrueBlock()) { + stack.push_back(block->getTrueBlock()); + } + if (block->hasFalseBlock()) { + stack.push_back(block->getFalseBlock()); + } + } +} + +[[nodiscard]] BBlock *resolve_target(BBlock *start, ResolveCache &cache) { + if (start == nullptr) { + return nullptr; + } + + if (const auto it = cache.find(start); it != cache.end()) { + return it->second; + } + + if (!is_trampoline_block(*start)) { + cache[start] = start; + return start; + } + + std::unordered_set seen; + seen.insert(start); + + auto *current = start; + while (is_trampoline_block(*current)) { + if (!current->hasTrueBlock()) { + cache[start] = start; + return start; + } + + auto *next = current->getTrueBlock(); + if (next == nullptr || !seen.insert(next).second) { + cache[start] = start; + return start; + } + + if (!is_trampoline_block(*next)) { + cache[start] = next; + return next; + } + + current = next; + } + + cache[start] = current; + return current; +} + +bool rewrite_cfg_edges(const std::vector &blocks, BBlock *root, + ResolveCache &cache) { + bool changed = false; + + for (auto *block : blocks) { + if (block == nullptr) { + continue; + } + + if (block == root && is_trampoline_block(*block)) { + continue; + } + + if (block->hasTrueBlock()) { + auto *target = block->getTrueBlock(); + auto *resolved = resolve_target(target, cache); + if (resolved != nullptr && resolved != target) { + block->setTrueBlock(resolved); + changed = true; + } + } + + if (block->hasFalseBlock()) { + auto *target = block->getFalseBlock(); + auto *resolved = resolve_target(target, cache); + if (resolved != nullptr && resolved != target) { + block->setFalseBlock(resolved); + changed = true; + } + } + } + + return changed; +} + +bool rewrite_jump_labels(const std::vector &blocks, BBlock *root, + const BlockNameMap &block_names, + ResolveCache &cache) { + bool changed = false; + + for (auto *block : blocks) { + if (block == nullptr) { + continue; + } + + if (block == root && is_trampoline_block(*block)) { + continue; + } + + auto &instructions = block->getInstructions(); + for (auto &instruction_ptr : instructions) { + if (instruction_ptr == nullptr) { + continue; + } + + if (auto *jump = dynamic_cast(instruction_ptr.get()); + jump != nullptr) { + const auto it = block_names.find(jump->getResult()); + if (it == block_names.end()) { + continue; + } + + auto *resolved = resolve_target(it->second, cache); + if (resolved != nullptr && resolved != it->second) { + jump->setResult(resolved->getName()); + changed = true; + } + continue; + } + + auto *cond_jump = + dynamic_cast(instruction_ptr.get()); + if (cond_jump == nullptr) { + continue; + } + + const auto *label = + std::get_if(&cond_jump->getRhsOperand()); + if (label == nullptr) { + continue; + } + + const auto it = block_names.find(*label); + if (it == block_names.end()) { + continue; + } + + auto *resolved = resolve_target(it->second, cache); + if (resolved != nullptr && resolved != it->second) { + cond_jump->setRhsOperand(resolved->getName()); + changed = true; + } + } + } + + return changed; +} + +bool process_method_root(BBlock *root) { + std::vector blocks; + BlockNameMap block_names; + collect_method_blocks(root, blocks, block_names); + + ResolveCache cache; + bool changed = false; + changed = rewrite_cfg_edges(blocks, root, cache) || changed; + changed = rewrite_jump_labels(blocks, root, block_names, cache) || changed; + return changed; +} + +} // namespace + +bool TrivialJumpBlockEliminationPass::run(CFG &graph) { + bool changed = false; + + for (auto *root : graph.getMethodRoots()) { + if (root == nullptr) { + continue; + } + + changed = process_method_root(root) || changed; + } + + const auto removed_blocks = graph.removeUnreachableBlocks(); + return changed || removed_blocks; +} + diff --git a/src/ir/passes/TrivialJumpBlockEliminationPass.hpp b/src/ir/passes/TrivialJumpBlockEliminationPass.hpp new file mode 100644 index 0000000..4ac3ce6 --- /dev/null +++ b/src/ir/passes/TrivialJumpBlockEliminationPass.hpp @@ -0,0 +1,18 @@ +#ifndef TRIVIAL_JUMP_BLOCK_ELIMINATION_PASS_HPP +#define TRIVIAL_JUMP_BLOCK_ELIMINATION_PASS_HPP + +#include + +#include "ir/passes/IRPass.hpp" + +class TrivialJumpBlockEliminationPass final : public IRPass { + public: + [[nodiscard]] std::string_view name() const override { + return "trivial-jump-block-elimination"; + } + + bool run(CFG &graph) override; +}; + +#endif + diff --git a/src/main.cpp b/src/main.cpp index 3fb435f..d956c08 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ namespace fs = std::filesystem; #include "ir/passes/ConditionalJumpFoldingPass.hpp" #include "ir/passes/ConstantFoldingPass.hpp" #include "ir/passes/IRPassManager.hpp" +#include "ir/passes/TrivialJumpBlockEliminationPass.hpp" #include "ir/passes/UnreachableBlockEliminationPass.hpp" #include "lexing/LegacyDiagnostics.hpp" #include "lexing/Lexer.hpp" @@ -323,6 +324,7 @@ int main(int argc, char **argv) { IRPassManager pass_manager; pass_manager.addPass(std::make_unique()); pass_manager.addPass(std::make_unique()); + pass_manager.addPass(std::make_unique()); pass_manager.addPass(std::make_unique()); (void)pass_manager.run(graph); diff --git a/tests/ir_trivial_jump_block_elimination_test.cpp b/tests/ir_trivial_jump_block_elimination_test.cpp new file mode 100644 index 0000000..1d1bc1b --- /dev/null +++ b/tests/ir_trivial_jump_block_elimination_test.cpp @@ -0,0 +1,144 @@ +#include + +#include + +#include "ir/CFG.hpp" +#include "ir/Tac.hpp" +#include "ir/passes/TrivialJumpBlockEliminationPass.hpp" + +TEST(IRTrivialJumpBlockElimination, RemovesTrampolineWithMultiplePredecessors) { + CFG graph; + + auto *root = graph.addMethodRootBlock("Main", "main"); + auto *pred_one = graph.newBlock(); + auto *pred_two = graph.newBlock(); + auto *trampoline = graph.newBlock(); + auto *target = graph.newBlock(); + + root->setTrueBlock(pred_one); + root->setFalseBlock(pred_two); + + pred_one->addInstruction(new CopyTac(1, "t1")); + pred_one->addInstruction(new JumpTac(trampoline->getName())); + pred_one->setTrueBlock(trampoline); + + pred_two->addInstruction(new CopyTac(2, "t2")); + pred_two->addInstruction(new JumpTac(trampoline->getName())); + pred_two->setTrueBlock(trampoline); + + trampoline->addInstruction(new JumpTac(target->getName())); + trampoline->setTrueBlock(target); + + EXPECT_EQ(graph.getBlockCount(), 5U); + + TrivialJumpBlockEliminationPass pass; + EXPECT_TRUE(pass.run(graph)); + + EXPECT_EQ(pred_one->getTrueBlock(), target); + EXPECT_EQ(pred_two->getTrueBlock(), target); + + auto *jump_one = + dynamic_cast(pred_one->getInstructions().back().get()); + ASSERT_NE(jump_one, nullptr); + EXPECT_EQ(jump_one->getResult(), target->getName()); + + auto *jump_two = + dynamic_cast(pred_two->getInstructions().back().get()); + ASSERT_NE(jump_two, nullptr); + EXPECT_EQ(jump_two->getResult(), target->getName()); + + EXPECT_EQ(graph.getBlockCount(), 4U); +} + +TEST(IRTrivialJumpBlockElimination, ThreadsChains) { + CFG graph; + + auto *root = graph.addMethodRootBlock("Main", "main"); + auto *pred = graph.newBlock(); + auto *trampoline_one = graph.newBlock(); + auto *trampoline_two = graph.newBlock(); + auto *target = graph.newBlock(); + + root->setTrueBlock(pred); + + pred->addInstruction(new CopyTac(1, "t")); + pred->addInstruction(new JumpTac(trampoline_one->getName())); + pred->setTrueBlock(trampoline_one); + + trampoline_one->addInstruction(new JumpTac(trampoline_two->getName())); + trampoline_one->setTrueBlock(trampoline_two); + + trampoline_two->addInstruction(new JumpTac(target->getName())); + trampoline_two->setTrueBlock(target); + + EXPECT_EQ(graph.getBlockCount(), 5U); + + TrivialJumpBlockEliminationPass pass; + EXPECT_TRUE(pass.run(graph)); + + EXPECT_EQ(pred->getTrueBlock(), target); + auto *jump = + dynamic_cast(pred->getInstructions().back().get()); + ASSERT_NE(jump, nullptr); + EXPECT_EQ(jump->getResult(), target->getName()); + + EXPECT_EQ(graph.getBlockCount(), 3U); +} + +TEST(IRTrivialJumpBlockElimination, UpdatesConditionalTargets) { + CFG graph; + + auto *root = graph.addMethodRootBlock("Main", "main"); + auto *pred = graph.newBlock(); + auto *trampoline = graph.newBlock(); + auto *false_target = graph.newBlock(); + auto *true_target = graph.newBlock(); + + root->setTrueBlock(pred); + + pred->addInstruction(new CondJumpTac(trampoline->getName(), "cond")); + pred->addInstruction(new JumpTac(true_target->getName())); + pred->setTrueBlock(true_target); + pred->setFalseBlock(trampoline); + + trampoline->addInstruction(new JumpTac(false_target->getName())); + trampoline->setTrueBlock(false_target); + + EXPECT_EQ(graph.getBlockCount(), 5U); + + TrivialJumpBlockEliminationPass pass; + EXPECT_TRUE(pass.run(graph)); + + EXPECT_EQ(pred->getFalseBlock(), false_target); + + auto *cond = + dynamic_cast(pred->getInstructions().front().get()); + ASSERT_NE(cond, nullptr); + const auto *label = std::get_if(&cond->getRhsOperand()); + ASSERT_NE(label, nullptr); + EXPECT_EQ(*label, false_target->getName()); + + EXPECT_EQ(graph.getBlockCount(), 4U); +} + +TEST(IRTrivialJumpBlockElimination, DoesNotRewriteJumpOnlyRoot) { + CFG graph; + + auto *root = graph.addMethodRootBlock("Main", "main"); + auto *trampoline = graph.newBlock(); + auto *target = graph.newBlock(); + + root->addInstruction(new JumpTac(trampoline->getName())); + root->setTrueBlock(trampoline); + + trampoline->addInstruction(new JumpTac(target->getName())); + trampoline->setTrueBlock(target); + + TrivialJumpBlockEliminationPass pass; + EXPECT_FALSE(pass.run(graph)); + + EXPECT_EQ(root->getTrueBlock(), trampoline); + auto *jump = dynamic_cast(root->getInstructions().front().get()); + ASSERT_NE(jump, nullptr); + EXPECT_EQ(jump->getResult(), trampoline->getName()); +}