diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp index c7cd9e5729..b22d0e80e7 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/AST/ASTPrinting.cpp @@ -8,6 +8,7 @@ #include #include "AST/AST.h" +#include "Compiler/ControlFlowGraph.h" #include "Function.h" namespace JSSpecCompiler { @@ -41,12 +42,12 @@ void ControlFlowFunctionReturn::dump_tree(StringBuilder& builder) void ControlFlowJump::dump_tree(StringBuilder& builder) { - dump_node(builder, "ControlFlowJump jump={:p}", m_block); + dump_node(builder, "ControlFlowJump jump={}", m_block->m_index); } void ControlFlowBranch::dump_tree(StringBuilder& builder) { - dump_node(builder, "ControlFlowBranch true={:p} false={:p}", m_then, m_else); + dump_node(builder, "ControlFlowBranch true={} false={}", m_then->m_index, m_else->m_index); m_condition->format_tree(builder); } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt index f54dc687f2..b0b49a1e2d 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES AST/ASTPrinting.cpp Compiler/CompilerPass.cpp Compiler/GenericASTPass.cpp + Compiler/Passes/CFGBuildingPass.cpp Compiler/Passes/FunctionCallCanonicalizationPass.cpp Compiler/Passes/IfBranchMergingPass.cpp Compiler/Passes/ReferenceResolvingPass.cpp diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/ControlFlowGraph.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/ControlFlowGraph.h new file mode 100644 index 0000000000..46874f39c9 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/ControlFlowGraph.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +#include "Forward.h" + +namespace JSSpecCompiler { + +class BasicBlock : public RefCounted { +public: + BasicBlock(size_t index, NonnullRefPtr continuation) + : m_index(index) + , m_continuation(move(continuation)) + { + } + + size_t m_index; + Vector m_expressions; + NonnullRefPtr m_continuation; +}; + +class ControlFlowGraph : public RefCounted { +public: + ControlFlowGraph() { } + + size_t blocks_count() const { return blocks.size(); } + + Vector> blocks; + BasicBlockRef start_block; + BasicBlockRef end_block; +}; + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.cpp new file mode 100644 index 0000000000..a66fb738dd --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "Compiler/Passes/CFGBuildingPass.h" +#include "AST/AST.h" +#include "Function.h" + +namespace JSSpecCompiler { + +void CFGBuildingPass::process_function() +{ + m_cfg = m_function->m_cfg = make_ref_counted(); + m_current_block = m_cfg->start_block = create_empty_block(); + m_cfg->end_block = create_empty_block(); + m_cfg->end_block->m_continuation = make_ref_counted( + make_ref_counted(m_function->m_return_value)); + m_is_expression_stack = { false }; + + run_in_subtree(m_function->m_ast); + + // FIXME: What should we do if control flow reached the end of the function? Returning + // error_tree will 100% confuse future passes. + m_current_block->m_expressions.append(make_ref_counted( + BinaryOperator::Assignment, + make_ref_counted(m_function->m_return_value), + error_tree)); + m_current_block->m_continuation = make_ref_counted(m_cfg->end_block); +} + +RecursionDecision CFGBuildingPass::on_entry(Tree tree) +{ + m_is_expression_stack.append(!as(tree).is_null()); + + if (auto if_else_if_chain = as(tree); if_else_if_chain) { + auto* end_block = create_empty_block(); + + for (size_t i = 0; i < if_else_if_chain->branches_count(); ++i) { + auto current_condition = if_else_if_chain->m_conditions[i]; + + run_in_subtree(current_condition); + will_be_used_as_expression(current_condition); + auto* condition_block = exchange_current_with_empty(); + + auto* branch_entry = m_current_block; + run_in_subtree(if_else_if_chain->m_branches[i]); + auto* branch_return = exchange_current_with_empty(); + branch_return->m_continuation = make_ref_counted(end_block); + + condition_block->m_continuation = make_ref_counted(current_condition, branch_entry, m_current_block); + } + + if (if_else_if_chain->m_else_branch) + run_in_const_subtree(if_else_if_chain->m_else_branch); + m_current_block->m_continuation = make_ref_counted(end_block); + + m_current_block = end_block; + return RecursionDecision::Continue; + } + + if (auto return_node = as(tree); return_node) { + Tree return_assignment = make_ref_counted( + BinaryOperator::Assignment, + make_ref_counted(m_function->m_return_value), + return_node->m_return_value); + run_in_subtree(return_assignment); + auto* return_block = exchange_current_with_empty(); + return_block->m_continuation = make_ref_counted(m_cfg->end_block); + + return RecursionDecision::Continue; + } + + return RecursionDecision::Recurse; +} + +void CFGBuildingPass::on_leave(Tree tree) +{ + (void)m_is_expression_stack.take_last(); + + if (!m_is_expression_stack.last() && as(tree)) + m_current_block->m_expressions.append(tree); +} + +BasicBlockRef CFGBuildingPass::create_empty_block() +{ + m_cfg->blocks.append(make_ref_counted(m_cfg->blocks_count(), invalid_continuation)); + return m_cfg->blocks.last(); +} + +BasicBlockRef CFGBuildingPass::exchange_current_with_empty() +{ + auto* new_block = create_empty_block(); + swap(new_block, m_current_block); + return new_block; +} + +void CFGBuildingPass::will_be_used_as_expression(Tree const& tree) +{ + if (m_current_block->m_expressions.is_empty()) + VERIFY(is(tree.ptr())); + else + VERIFY(m_current_block->m_expressions.take_last() == tree); +} + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.h new file mode 100644 index 0000000000..2586d24e79 --- /dev/null +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Compiler/Passes/CFGBuildingPass.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, Dan Klishch + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#include "Compiler/ControlFlowGraph.h" +#include "Compiler/GenericASTPass.h" + +namespace JSSpecCompiler { + +class CFGBuildingPass + : public IntraproceduralCompilerPass + , private RecursiveASTVisitor { +public: + inline static constexpr StringView name = "cfg-building"sv; + + using IntraproceduralCompilerPass::IntraproceduralCompilerPass; + +protected: + void process_function() override; + RecursionDecision on_entry(Tree tree) override; + void on_leave(Tree tree) override; + +private: + BasicBlockRef create_empty_block(); + BasicBlockRef exchange_current_with_empty(); + void will_be_used_as_expression(Tree const& tree); + + ControlFlowGraph* m_cfg; + BasicBlockRef m_current_block; + Vector m_is_expression_stack; +}; + +} diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h index 4880b159d1..a285af898f 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Forward.h @@ -49,6 +49,7 @@ using FunctionPointerRef = NonnullRefPtr; // Compiler/ControlFlowGraph.h class BasicBlock; using BasicBlockRef = BasicBlock*; +class ControlFlowGraph; // Compiler/GenericASTPass.h class RecursiveASTVisitor; diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.cpp index 1566933aac..cda60310f3 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.cpp @@ -6,6 +6,7 @@ #include "Function.h" #include "AST/AST.h" +#include "Compiler/ControlFlowGraph.h" namespace JSSpecCompiler { diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h index ea8472b12b..64e7b6131a 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Function.h @@ -40,6 +40,7 @@ public: Tree m_ast; VariableDeclarationRef m_return_value; HashMap m_local_variables; + RefPtr m_cfg; }; } diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation index 517370400d..051bfc7eda 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/Tests/simple.cpp.expectation @@ -106,3 +106,29 @@ TreeList ReturnNode Var b +===== AST after cfg-building ===== +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 + diff --git a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp index 0c52a75410..85dffdb326 100644 --- a/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/JSSpecCompiler/main.cpp @@ -8,6 +8,7 @@ #include #include +#include "Compiler/Passes/CFGBuildingPass.h" #include "Compiler/Passes/FunctionCallCanonicalizationPass.h" #include "Compiler/Passes/IfBranchMergingPass.h" #include "Compiler/Passes/ReferenceResolvingPass.h" @@ -100,6 +101,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;