JSSpecCompiler: Add control flow building pass

This commit is contained in:
Dan Klishch 2023-08-19 14:24:50 -04:00 committed by Andrew Kaster
parent c74e2d04d1
commit ff44aea917
Notes: sideshowbarker 2024-07-16 18:26:46 +09:00
10 changed files with 222 additions and 2 deletions

View file

@ -8,6 +8,7 @@
#include <AK/TemporaryChange.h>
#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);
}

View file

@ -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

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include "Forward.h"
namespace JSSpecCompiler {
class BasicBlock : public RefCounted<BasicBlock> {
public:
BasicBlock(size_t index, NonnullRefPtr<ControlFlowOperator> continuation)
: m_index(index)
, m_continuation(move(continuation))
{
}
size_t m_index;
Vector<Tree> m_expressions;
NonnullRefPtr<ControlFlowOperator> m_continuation;
};
class ControlFlowGraph : public RefCounted<ControlFlowGraph> {
public:
ControlFlowGraph() { }
size_t blocks_count() const { return blocks.size(); }
Vector<NonnullRefPtr<BasicBlock>> blocks;
BasicBlockRef start_block;
BasicBlockRef end_block;
};
}

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* 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<ControlFlowGraph>();
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<ControlFlowFunctionReturn>(
make_ref_counted<Variable>(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<BinaryOperation>(
BinaryOperator::Assignment,
make_ref_counted<Variable>(m_function->m_return_value),
error_tree));
m_current_block->m_continuation = make_ref_counted<ControlFlowJump>(m_cfg->end_block);
}
RecursionDecision CFGBuildingPass::on_entry(Tree tree)
{
m_is_expression_stack.append(!as<Expression>(tree).is_null());
if (auto if_else_if_chain = as<IfElseIfChain>(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<ControlFlowJump>(end_block);
condition_block->m_continuation = make_ref_counted<ControlFlowBranch>(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<ControlFlowJump>(end_block);
m_current_block = end_block;
return RecursionDecision::Continue;
}
if (auto return_node = as<ReturnNode>(tree); return_node) {
Tree return_assignment = make_ref_counted<BinaryOperation>(
BinaryOperator::Assignment,
make_ref_counted<Variable>(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<ControlFlowJump>(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<Expression>(tree))
m_current_block->m_expressions.append(tree);
}
BasicBlockRef CFGBuildingPass::create_empty_block()
{
m_cfg->blocks.append(make_ref_counted<BasicBlock>(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<Statement>(tree.ptr()));
else
VERIFY(m_current_block->m_expressions.take_last() == tree);
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023, Dan Klishch <danilklishch@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/TypeCasts.h>
#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<bool> m_is_expression_stack;
};
}

View file

@ -49,6 +49,7 @@ using FunctionPointerRef = NonnullRefPtr<FunctionPointer>;
// Compiler/ControlFlowGraph.h
class BasicBlock;
using BasicBlockRef = BasicBlock*;
class ControlFlowGraph;
// Compiler/GenericASTPass.h
class RecursiveASTVisitor;

View file

@ -6,6 +6,7 @@
#include "Function.h"
#include "AST/AST.h"
#include "Compiler/ControlFlowGraph.h"
namespace JSSpecCompiler {

View file

@ -40,6 +40,7 @@ public:
Tree m_ast;
VariableDeclarationRef m_return_value;
HashMap<StringView, VariableDeclarationRef> m_local_variables;
RefPtr<ControlFlowGraph> m_cfg;
};
}

View file

@ -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

View file

@ -8,6 +8,7 @@
#include <LibCore/ArgsParser.h>
#include <LibMain/Main.h>
#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<int> serenity_main(Main::Arguments arguments)
pipeline.add_compilation_pass<FunctionCallCanonicalizationPass>();
pipeline.add_compilation_pass<IfBranchMergingPass>();
pipeline.add_compilation_pass<ReferenceResolvingPass>();
pipeline.add_compilation_pass<CFGBuildingPass>();
pipeline.for_each_step_in(passes_to_dump_ast, [](CompilationStepWithDumpOptions& step) {
step.dump_ast = true;