diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp index 60037c941d..dbaa66a04c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.cpp @@ -31,6 +31,16 @@ void NodeSubtreePointer::replace_subtree(Badge, NullableTre }); } +Vector ControlFlowJump::references() +{ + return { &m_block }; +} + +Vector ControlFlowBranch::references() +{ + return { &m_then, &m_else }; +} + Vector BinaryOperation::subtrees() { return { { &m_left }, { &m_right } }; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h index 879c3bab31..0c17446c3c 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/AST.h @@ -80,7 +80,11 @@ protected: // ```. class Statement : public Node { }; class Expression : public Node { }; -class ControlFlowOperator : public Statement { }; + +class ControlFlowOperator : public Statement { +public: + virtual Vector references() = 0; +}; class ErrorNode : public Expression { public: @@ -106,6 +110,8 @@ public: VariableRef m_return_value; + Vector references() override { return {}; } + protected: void dump_tree(StringBuilder& builder) override; }; @@ -117,6 +123,8 @@ public: { } + Vector references() override; + BasicBlockRef m_block; protected: @@ -135,6 +143,8 @@ public: { } + Vector references() override; + Tree m_condition; BasicBlockRef m_then; BasicBlockRef m_else; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt index 6f7d7023ee..431bd1ada1 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES Compiler/ControlFlowGraph.cpp Compiler/GenericASTPass.cpp Compiler/Passes/CFGBuildingPass.cpp + Compiler/Passes/CFGSimplificationPass.cpp Compiler/Passes/FunctionCallCanonicalizationPass.cpp Compiler/Passes/IfBranchMergingPass.cpp Compiler/Passes/ReferenceResolvingPass.cpp diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.cpp new file mode 100644 index 0000000000..a9655ba95f --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Compiler/Passes/CFGSimplificationPass.h" +#include "AST/AST.h" +#include "Function.h" + +namespace JSSpecCompiler { + +void CFGSimplificationPass::process_function() +{ + auto& graph = *m_function->m_cfg; + + m_replacement.clear(); + m_replacement.resize(graph.blocks_count()); + m_state.clear(); + m_state.resize(graph.blocks_count()); + + for (auto const& block : graph.blocks) { + m_replacement[block->m_index] = block; + if (block->m_expressions.size() == 0) + if (auto jump = as(block->m_continuation); jump) + m_replacement[block->m_index] = jump->m_block; + } + + for (size_t i = 0; i < graph.blocks_count(); ++i) + if (m_state[i] == State::NotUsed) + VERIFY(compute_replacement_block(i)); + + // Fixing references + graph.start_block = m_replacement[graph.start_block->m_index]; + for (auto const& block : graph.blocks) { + for (auto* next_block : block->m_continuation->references()) + *next_block = m_replacement[(*next_block)->m_index]; + } + + // Removing unused nodes + m_state.span().fill(State::NotUsed); + compute_referenced_blocks(graph.start_block); + + size_t j = 0; + for (size_t i = 0; i < graph.blocks_count(); ++i) { + if (m_state[graph.blocks[i]->m_index] == State::Used) { + graph.blocks[j] = graph.blocks[i]; + graph.blocks[j]->m_index = j; + ++j; + } + } + graph.blocks.shrink(j); +} + +bool CFGSimplificationPass::compute_replacement_block(size_t i) +{ + if (m_state[i] == State::CurrentlyInside) + return false; + VERIFY(m_state[i] == State::NotUsed); + m_state[i] = State::CurrentlyInside; + + size_t j = m_replacement[i]->m_index; + + if (i == j) + return true; + if (m_state[j] == State::NotUsed) + if (!compute_replacement_block(j)) + return false; + m_replacement[i] = m_replacement[j]; + + m_state[i] = State::Used; + return true; +} + +void CFGSimplificationPass::compute_referenced_blocks(BasicBlockRef block) +{ + if (m_state[block->m_index] == State::Used) + return; + m_state[block->m_index] = State::Used; + + for (auto* next : block->m_continuation->references()) + compute_referenced_blocks(*next); +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.h new file mode 100644 index 0000000000..abd0c3a67b --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGSimplificationPass.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Compiler/CompilerPass.h" +#include "Compiler/ControlFlowGraph.h" + +namespace JSSpecCompiler { + +// CFGSimplificationPass removes empty `BasicBlock`s with an unconditional jump continuation. It +// also removes unreferenced blocks from the graph. +class CFGSimplificationPass : public IntraproceduralCompilerPass { +public: + inline static constexpr StringView name = "cfg-simplification"sv; + + using IntraproceduralCompilerPass::IntraproceduralCompilerPass; + +protected: + void process_function() override; + +private: + enum class State : char { + NotUsed, + CurrentlyInside, + Used, + }; + + bool compute_replacement_block(size_t i); + void compute_referenced_blocks(BasicBlockRef block); + + Vector m_replacement; + Vector m_state; +}; + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation index 82bc654f88..24a87bd3a6 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation @@ -182,3 +182,70 @@ BinaryOperation Assignment Error "" ControlFlowJump jump=1 +===== AST after cfg-simplification ===== +f(): +TreeList + IfElseIfChain + UnresolvedReference cond1 + TreeList + BinaryOperation Declaration + Var a + MathematicalConstant 1 + IfElseIfChain + UnresolvedReference cond2 + TreeList + BinaryOperation Declaration + Var b + Var a + TreeList + BinaryOperation Declaration + Var b + MathematicalConstant 3 + TreeList + BinaryOperation Declaration + Var b + MathematicalConstant 4 + ReturnNode + Var b + +===== CFG after cfg-simplification ===== +f(): +0: +ControlFlowBranch true=3 false=6 + UnresolvedReference cond1 + +1: +ControlFlowFunctionReturn + Var $return + +2: +BinaryOperation Assignment + Var $return + Var b +ControlFlowJump jump=1 + +3: +BinaryOperation Declaration + Var a + MathematicalConstant 1 +ControlFlowBranch true=4 false=5 + UnresolvedReference cond2 + +4: +BinaryOperation Declaration + Var b + Var a +ControlFlowJump jump=2 + +5: +BinaryOperation Declaration + Var b + MathematicalConstant 3 +ControlFlowJump jump=2 + +6: +BinaryOperation Declaration + Var b + MathematicalConstant 4 +ControlFlowJump jump=2 + diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp index e496d1fd79..3b8b097781 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp @@ -9,6 +9,7 @@ #include #include "Compiler/Passes/CFGBuildingPass.h" +#include "Compiler/Passes/CFGSimplificationPass.h" #include "Compiler/Passes/FunctionCallCanonicalizationPass.h" #include "Compiler/Passes/IfBranchMergingPass.h" #include "Compiler/Passes/ReferenceResolvingPass.h" @@ -106,6 +107,7 @@ ErrorOr serenity_main(Main::Arguments arguments) pipeline.add_compilation_pass(); pipeline.add_compilation_pass(); pipeline.add_compilation_pass(); + pipeline.add_compilation_pass(); pipeline.for_each_step_in(passes_to_dump_ast, [](CompilationStepWithDumpOptions& step) { step.dump_ast = true;