1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 04:47:35 +00:00

LibJS: Add a basic pass manager and add some basic passes

This commit adds a bunch of passes, the most interesting of which is a
pass that merges blocks together, and a pass that places blocks that
flow into each other next to each other, and a very simply pass that
removes duplicate basic blocks.
Note that this does not remove the jump at the end of each block in that
pass to avoid scope creep in the passes.
This commit is contained in:
Ali Mohammad Pur 2021-06-13 20:40:20 +04:30 committed by Ali Mohammad Pur
parent e81fd7106b
commit 1414c7b049
17 changed files with 751 additions and 30 deletions

View file

@ -69,6 +69,7 @@ file(GLOB LIBMARKDOWN_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibMar
file(GLOB LIBX86_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibX86/*.cpp") file(GLOB LIBX86_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibX86/*.cpp")
file(GLOB LIBJS_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibJS/*.cpp") file(GLOB LIBJS_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibJS/*.cpp")
file(GLOB LIBJS_SUBDIR_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibJS/*/*.cpp") file(GLOB LIBJS_SUBDIR_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibJS/*/*.cpp")
file(GLOB LIBJS_SUBSUBDIR_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibJS/*/*/*.cpp")
file(GLOB LIBCOMPRESS_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibCompress/*.cpp") file(GLOB LIBCOMPRESS_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibCompress/*.cpp")
file(GLOB LIBCOMPRESS_TESTS CONFIGURE_DEPENDS "../../Tests/LibCompress/*.cpp") file(GLOB LIBCOMPRESS_TESTS CONFIGURE_DEPENDS "../../Tests/LibCompress/*.cpp")
file(GLOB LIBCRYPTO_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibCrypto/*.cpp") file(GLOB LIBCRYPTO_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibCrypto/*.cpp")
@ -92,7 +93,7 @@ file(GLOB LIBTEST_MAIN CONFIGURE_DEPENDS "../../Userland/Libraries/LibTest/TestM
set(LAGOM_REGEX_SOURCES ${LIBREGEX_LIBC_SOURCES} ${LIBREGEX_SOURCES}) set(LAGOM_REGEX_SOURCES ${LIBREGEX_LIBC_SOURCES} ${LIBREGEX_SOURCES})
set(LAGOM_CORE_SOURCES ${AK_SOURCES} ${LIBCORE_SOURCES}) set(LAGOM_CORE_SOURCES ${AK_SOURCES} ${LIBCORE_SOURCES})
set(LAGOM_MORE_SOURCES ${LIBARCHIVE_SOURCES} ${LIBAUDIO_SOURCES} ${LIBELF_SOURCES} ${LIBIPC_SOURCES} ${LIBLINE_SOURCES} ${LIBJS_SOURCES} ${LIBJS_SUBDIR_SOURCES} ${LIBX86_SOURCES} ${LIBCRYPTO_SOURCES} ${LIBCOMPRESS_SOURCES} ${LIBCRYPTO_SUBDIR_SOURCES} ${LIBCRYPTO_SUBSUBDIR_SOURCES} ${LIBTLS_SOURCES} ${LIBTTF_SOURCES} ${LIBTEXTCODEC_SOURCES} ${LIBMARKDOWN_SOURCES} ${LIBGEMINI_SOURCES} ${LIBGFX_SOURCES} ${LIBGUI_GML_SOURCES} ${LIBHTTP_SOURCES} ${LAGOM_REGEX_SOURCES} ${SHELL_SOURCES} ${LIBSQL_SOURCES} ${LIBWASM_SOURCES} ${LIBIMAP_SOURCES}) set(LAGOM_MORE_SOURCES ${LIBARCHIVE_SOURCES} ${LIBAUDIO_SOURCES} ${LIBELF_SOURCES} ${LIBIPC_SOURCES} ${LIBLINE_SOURCES} ${LIBJS_SOURCES} ${LIBJS_SUBDIR_SOURCES} ${LIBJS_SUBSUBDIR_SOURCES} ${LIBX86_SOURCES} ${LIBCRYPTO_SOURCES} ${LIBCOMPRESS_SOURCES} ${LIBCRYPTO_SUBDIR_SOURCES} ${LIBCRYPTO_SUBSUBDIR_SOURCES} ${LIBTLS_SOURCES} ${LIBTTF_SOURCES} ${LIBTEXTCODEC_SOURCES} ${LIBMARKDOWN_SOURCES} ${LIBGEMINI_SOURCES} ${LIBGFX_SOURCES} ${LIBGUI_GML_SOURCES} ${LIBHTTP_SOURCES} ${LAGOM_REGEX_SOURCES} ${SHELL_SOURCES} ${LIBSQL_SOURCES} ${LIBWASM_SOURCES} ${LIBIMAP_SOURCES})
set(LAGOM_TEST_SOURCES ${LIBTEST_SOURCES}) set(LAGOM_TEST_SOURCES ${LIBTEST_SOURCES})
# FIXME: This is a hack, because the lagom stuff can be build individually or # FIXME: This is a hack, because the lagom stuff can be build individually or

View file

@ -66,6 +66,7 @@ void BasicBlock::grow(size_t additional_size)
void InstructionStreamIterator::operator++() void InstructionStreamIterator::operator++()
{ {
VERIFY(!at_end());
m_offset += dereference().length(); m_offset += dereference().length();
} }

View file

@ -80,10 +80,12 @@ public:
#undef __BYTECODE_OP #undef __BYTECODE_OP
}; };
bool is_terminator() const;
Type type() const { return m_type; } Type type() const { return m_type; }
size_t length() const; size_t length() const;
String to_string(Bytecode::Executable const&) const; String to_string(Bytecode::Executable const&) const;
void execute(Bytecode::Interpreter&) const; void execute(Bytecode::Interpreter&) const;
void replace_references(BasicBlock const&, BasicBlock const&);
static void destroy(Instruction&); static void destroy(Instruction&);
protected: protected:

View file

@ -165,4 +165,38 @@ void Interpreter::continue_pending_unwind(Label const& resume_label)
jump(resume_label); jump(resume_label);
} }
} }
AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> Interpreter::s_optimization_pipelines {};
Bytecode::PassManager& Interpreter::optimization_pipeline(Interpreter::OptimizationLevel level)
{
auto underlying_level = to_underlying(level);
VERIFY(underlying_level <= to_underlying(Interpreter::OptimizationLevel::__Count));
auto& entry = s_optimization_pipelines[underlying_level];
if (entry)
return *entry;
auto pm = make<PassManager>();
if (level == OptimizationLevel::Default) {
pm->add<Passes::GenerateCFG>();
pm->add<Passes::UnifySameBlocks>();
pm->add<Passes::GenerateCFG>();
pm->add<Passes::MergeBlocks>();
pm->add<Passes::GenerateCFG>();
pm->add<Passes::UnifySameBlocks>();
pm->add<Passes::GenerateCFG>();
pm->add<Passes::MergeBlocks>();
pm->add<Passes::GenerateCFG>();
pm->add<Passes::PlaceBlocks>();
} else {
VERIFY_NOT_REACHED();
}
auto& passes = *pm;
entry = move(pm);
return passes;
}
} }

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include "Generator.h" #include "Generator.h"
#include "PassManager.h"
#include <LibJS/Bytecode/Label.h> #include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h> #include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
@ -60,9 +61,17 @@ public:
Executable const& current_executable() { return *m_current_executable; } Executable const& current_executable() { return *m_current_executable; }
enum class OptimizationLevel {
Default,
__Count,
};
static Bytecode::PassManager& optimization_pipeline(OptimizationLevel = OptimizationLevel::Default);
private: private:
RegisterWindow& registers() { return m_register_windows.last(); } RegisterWindow& registers() { return m_register_windows.last(); }
static AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> s_optimization_pipelines;
VM& m_vm; VM& m_vm;
GlobalObject& m_global_object; GlobalObject& m_global_object;
NonnullOwnPtrVector<RegisterWindow> m_register_windows; NonnullOwnPtrVector<RegisterWindow> m_register_windows;

View file

@ -14,14 +14,14 @@ namespace JS::Bytecode {
class Label { class Label {
public: public:
explicit Label(BasicBlock const& block) explicit Label(BasicBlock const& block)
: m_block(block) : m_block(&block)
{ {
} }
auto& block() const { return m_block; } auto& block() const { return *m_block; }
private: private:
BasicBlock const& m_block; BasicBlock const* m_block { nullptr };
}; };
} }

View file

@ -165,6 +165,14 @@ void Jump::execute_impl(Bytecode::Interpreter& interpreter) const
interpreter.jump(*m_true_target); interpreter.jump(*m_true_target);
} }
void Jump::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (m_true_target.has_value() && &m_true_target->block() == &from)
m_true_target = Label { to };
if (m_false_target.has_value() && &m_false_target->block() == &from)
m_false_target = Label { to };
}
void JumpConditional::execute_impl(Bytecode::Interpreter& interpreter) const void JumpConditional::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
VERIFY(m_true_target.has_value()); VERIFY(m_true_target.has_value());
@ -261,6 +269,16 @@ void EnterUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
interpreter.jump(m_entry_point); interpreter.jump(m_entry_point);
} }
void EnterUnwindContext::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (&m_entry_point.block() == &from)
m_entry_point = Label { to };
if (m_handler_target.has_value() && &m_handler_target->block() == &from)
m_handler_target = Label { to };
if (m_finalizer_target.has_value() && &m_finalizer_target->block() == &from)
m_finalizer_target = Label { to };
}
void LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const void LeaveUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
interpreter.leave_unwind_context(); interpreter.leave_unwind_context();
@ -271,6 +289,12 @@ void ContinuePendingUnwind::execute_impl(Bytecode::Interpreter& interpreter) con
interpreter.continue_pending_unwind(m_resume_target); interpreter.continue_pending_unwind(m_resume_target);
} }
void ContinuePendingUnwind::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (&m_resume_target.block() == &from)
m_resume_target = Label { to };
}
void PushLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const void PushLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
HashMap<FlyString, Variable> resolved_variables; HashMap<FlyString, Variable> resolved_variables;
@ -292,6 +316,12 @@ void Yield::execute_impl(Bytecode::Interpreter& interpreter) const
interpreter.do_return(object); interpreter.do_return(object);
} }
void Yield::replace_references_impl(BasicBlock const& from, BasicBlock const& to)
{
if (m_continuation_label.has_value() && &m_continuation_label->block() == &from)
m_continuation_label = Label { to };
}
void GetByValue::execute_impl(Bytecode::Interpreter& interpreter) const void GetByValue::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
if (auto* object = interpreter.reg(m_base).to_object(interpreter.global_object())) { if (auto* object = interpreter.reg(m_base).to_object(interpreter.global_object())) {

View file

@ -29,6 +29,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_src; Register m_src;
@ -44,6 +45,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Value m_value; Value m_value;
@ -59,6 +61,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_dst; Register m_dst;
@ -88,20 +91,21 @@ private:
O(RightShift, right_shift) \ O(RightShift, right_shift) \
O(UnsignedRightShift, unsigned_right_shift) O(UnsignedRightShift, unsigned_right_shift)
#define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ #define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \
class OpTitleCase final : public Instruction { \ class OpTitleCase final : public Instruction { \
public: \ public: \
explicit OpTitleCase(Register lhs_reg) \ explicit OpTitleCase(Register lhs_reg) \
: Instruction(Type::OpTitleCase) \ : Instruction(Type::OpTitleCase) \
, m_lhs_reg(lhs_reg) \ , m_lhs_reg(lhs_reg) \
{ \ { \
} \ } \
\ \
void execute_impl(Bytecode::Interpreter&) const; \ void execute_impl(Bytecode::Interpreter&) const; \
String to_string_impl(Bytecode::Executable const&) const; \ String to_string_impl(Bytecode::Executable const&) const; \
\ void replace_references_impl(BasicBlock const&, BasicBlock const&) { } \
private: \ \
Register m_lhs_reg; \ private: \
Register m_lhs_reg; \
}; };
JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP) JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP)
@ -114,16 +118,17 @@ JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP)
O(UnaryMinus, unary_minus) \ O(UnaryMinus, unary_minus) \
O(Typeof, typeof_) O(Typeof, typeof_)
#define JS_DECLARE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \ #define JS_DECLARE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \
class OpTitleCase final : public Instruction { \ class OpTitleCase final : public Instruction { \
public: \ public: \
OpTitleCase() \ OpTitleCase() \
: Instruction(Type::OpTitleCase) \ : Instruction(Type::OpTitleCase) \
{ \ { \
} \ } \
\ \
void execute_impl(Bytecode::Interpreter&) const; \ void execute_impl(Bytecode::Interpreter&) const; \
String to_string_impl(Bytecode::Executable const&) const; \ String to_string_impl(Bytecode::Executable const&) const; \
void replace_references_impl(BasicBlock const&, BasicBlock const&) { } \
}; };
JS_ENUMERATE_COMMON_UNARY_OPS(JS_DECLARE_COMMON_UNARY_OP) JS_ENUMERATE_COMMON_UNARY_OPS(JS_DECLARE_COMMON_UNARY_OP)
@ -139,6 +144,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
StringTableIndex m_string; StringTableIndex m_string;
@ -153,6 +159,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class NewBigInt final : public Instruction { class NewBigInt final : public Instruction {
@ -165,6 +172,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Crypto::SignedBigInteger m_bigint; Crypto::SignedBigInteger m_bigint;
@ -183,8 +191,12 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
size_t length_impl() const { return sizeof(*this) + sizeof(Register) * m_element_count; } size_t length_impl() const
{
return sizeof(*this) + sizeof(Register) * m_element_count;
}
private: private:
size_t m_element_count { 0 }; size_t m_element_count { 0 };
@ -201,6 +213,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_lhs; Register m_lhs;
@ -216,6 +229,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
StringTableIndex m_identifier; StringTableIndex m_identifier;
@ -231,6 +245,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
StringTableIndex m_identifier; StringTableIndex m_identifier;
@ -246,6 +261,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
StringTableIndex m_property; StringTableIndex m_property;
@ -262,6 +278,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_base; Register m_base;
@ -278,6 +295,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_base; Register m_base;
@ -294,6 +312,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
Register m_base; Register m_base;
@ -326,6 +345,10 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&);
auto& true_target() const { return m_true_target; }
auto& false_target() const { return m_false_target; }
protected: protected:
Optional<Label> m_true_target; Optional<Label> m_true_target;
@ -375,8 +398,12 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
size_t length_impl() const { return sizeof(*this) + sizeof(Register) * m_argument_count; } size_t length_impl() const
{
return sizeof(*this) + sizeof(Register) * m_argument_count;
}
private: private:
Register m_callee; Register m_callee;
@ -396,6 +423,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
FunctionNode const& m_function_node; FunctionNode const& m_function_node;
@ -412,6 +440,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class Increment final : public Instruction { class Increment final : public Instruction {
@ -423,6 +452,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class Decrement final : public Instruction { class Decrement final : public Instruction {
@ -434,6 +464,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class Throw final : public Instruction { class Throw final : public Instruction {
@ -447,6 +478,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class EnterUnwindContext final : public Instruction { class EnterUnwindContext final : public Instruction {
@ -463,6 +495,11 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&);
auto& entry_point() const { return m_entry_point; }
auto& handler_target() const { return m_handler_target; }
auto& finalizer_target() const { return m_finalizer_target; }
private: private:
Label m_entry_point; Label m_entry_point;
@ -479,6 +516,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
}; };
class ContinuePendingUnwind final : public Instruction { class ContinuePendingUnwind final : public Instruction {
@ -493,6 +531,9 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&);
auto& resume_target() const { return m_resume_target; }
private: private:
Label m_resume_target; Label m_resume_target;
@ -515,6 +556,9 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&);
auto& continuation() const { return m_continuation_label; }
private: private:
Optional<Label> m_continuation_label; Optional<Label> m_continuation_label;
@ -529,6 +573,7 @@ public:
} }
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
HashMap<u32, Variable> m_variables; HashMap<u32, Variable> m_variables;
@ -544,6 +589,7 @@ public:
void execute_impl(Bytecode::Interpreter&) const; void execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const; String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
private: private:
size_t m_index { 0 }; size_t m_index { 0 };
@ -568,6 +614,21 @@ ALWAYS_INLINE void Instruction::execute(Bytecode::Interpreter& interpreter) cons
#undef __BYTECODE_OP #undef __BYTECODE_OP
} }
ALWAYS_INLINE void Instruction::replace_references(BasicBlock const& from, BasicBlock const& to)
{
#define __BYTECODE_OP(op) \
case Instruction::Type::op: \
return static_cast<Bytecode::Op::op&>(*this).replace_references_impl(from, to);
switch (type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
ALWAYS_INLINE size_t Instruction::length() const ALWAYS_INLINE size_t Instruction::length() const
{ {
if (type() == Type::Call) if (type() == Type::Call)
@ -587,4 +648,18 @@ ALWAYS_INLINE size_t Instruction::length() const
#undef __BYTECODE_OP #undef __BYTECODE_OP
} }
ALWAYS_INLINE bool Instruction::is_terminator() const
{
#define __BYTECODE_OP(op) \
case Type::op: \
return Op::op::IsTerminator;
switch (type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
} }

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/PassManager.h>
#include <stdio.h>
namespace JS::Bytecode::Passes {
void DumpCFG::perform(PassPipelineExecutable& executable)
{
started();
VERIFY(executable.cfg.has_value());
outln(m_file, "CFG Dump for {} basic blocks:", executable.executable.basic_blocks.size());
for (auto& entry : executable.cfg.value()) {
for (auto& value : entry.value)
outln(m_file, "{} -> {}", entry.key->name(), value->name());
}
outln(m_file);
finished();
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/PassManager.h>
namespace JS::Bytecode::Passes {
void GenerateCFG::perform(PassPipelineExecutable& executable)
{
started();
executable.cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
executable.inverted_cfg = HashMap<BasicBlock const*, HashTable<BasicBlock const*>> {};
executable.exported_blocks = HashTable<BasicBlock const*> {};
Vector<InstructionStreamIterator> iterators;
Vector<BasicBlock const&> entered_blocks;
HashTable<BasicBlock const*> seen_blocks;
auto enter_label = [&](auto const& label, auto& entering_block, bool exported = false) {
auto& entry = executable.cfg->ensure(&entering_block);
entry.set(&label->block());
auto& inverse_entry = executable.inverted_cfg->ensure(&label->block());
inverse_entry.set(&entering_block);
if (exported)
executable.exported_blocks->set(&label->block());
if (!seen_blocks.contains(&label->block())) {
seen_blocks.set(&label->block());
entered_blocks.append(label->block());
iterators.empend(label->block().instruction_stream());
}
};
seen_blocks.set(&executable.executable.basic_blocks.first());
entered_blocks.append(executable.executable.basic_blocks.first());
iterators.empend(executable.executable.basic_blocks.first().instruction_stream());
while (!entered_blocks.is_empty()) {
if (iterators.last().at_end()) {
entered_blocks.take_last();
iterators.take_last();
continue;
}
auto& instruction = *iterators.last();
++iterators.last();
if (!instruction.is_terminator())
continue;
auto& current_block = entered_blocks.last();
if (instruction.type() == Instruction::Type::Jump) {
auto& true_target = static_cast<Op::Jump const&>(instruction).true_target();
enter_label(true_target, current_block);
continue;
}
if (instruction.type() == Instruction::Type::JumpConditional || instruction.type() == Instruction::Type::JumpNullish) {
auto& true_target = static_cast<Op::Jump const&>(instruction).true_target();
enter_label(true_target, current_block);
auto& false_target = static_cast<Op::Jump const&>(instruction).false_target();
enter_label(false_target, current_block);
continue;
}
if (instruction.type() == Instruction::Type::Yield) {
auto& continuation = static_cast<Op::Yield const&>(instruction).continuation();
if (continuation.has_value())
enter_label(continuation, current_block, true);
continue;
}
if (instruction.type() == Instruction::Type::EnterUnwindContext) {
auto& entry_point = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point();
auto& handler_target = static_cast<Op::EnterUnwindContext const&>(instruction).handler_target();
auto& finalizer_target = static_cast<Op::EnterUnwindContext const&>(instruction).finalizer_target();
enter_label(&entry_point, current_block);
if (handler_target.has_value())
enter_label(handler_target, current_block);
if (finalizer_target.has_value())
enter_label(finalizer_target, current_block);
continue;
}
if (instruction.type() == Instruction::Type::ContinuePendingUnwind) {
auto& resume_target = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target();
enter_label(&resume_target, current_block);
continue;
}
// Otherwise, pop the current block off, it doesn't jump anywhere.
iterators.take_last();
entered_blocks.take_last();
}
finished();
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/PassManager.h>
namespace JS::Bytecode::Passes {
void MergeBlocks::perform(PassPipelineExecutable& executable)
{
started();
VERIFY(executable.cfg.has_value());
VERIFY(executable.inverted_cfg.has_value());
auto cfg = executable.cfg.release_value();
auto inverted_cfg = executable.inverted_cfg.release_value();
// Figure out which blocks can be merged
HashTable<BasicBlock const*> blocks_to_merge;
HashMap<BasicBlock const*, BasicBlock const*> blocks_to_replace;
Vector<BasicBlock const*> blocks_to_remove;
Vector<size_t> boundaries;
for (auto& entry : cfg) {
if (entry.value.size() != 1)
continue;
if (executable.exported_blocks->contains(*entry.value.begin()))
continue;
{
InstructionStreamIterator it { entry.key->instruction_stream() };
auto& first_instruction = *it;
if (first_instruction.is_terminator()) {
if (first_instruction.type() == Instruction::Type::Jump) {
auto replacing_block = &static_cast<Op::Jump const&>(first_instruction).true_target()->block();
if (replacing_block != entry.key)
blocks_to_replace.set(entry.key, replacing_block);
continue;
}
}
}
if (auto cfg_entry = inverted_cfg.get(*entry.value.begin()); cfg_entry.has_value()) {
auto& predecssor_entry = *cfg_entry;
if (predecssor_entry.size() != 1)
continue;
}
// The two blocks are safe to merge.
blocks_to_merge.set(entry.key);
}
for (auto& entry : blocks_to_replace) {
auto replacement = entry.value;
for (;;) {
auto lookup = blocks_to_replace.get(replacement);
if (!lookup.has_value())
break;
if (replacement == *lookup)
break;
replacement = *lookup;
}
entry.value = replacement;
}
auto replace_blocks = [&](auto& blocks, auto& replacement) {
Optional<size_t> first_successor_position;
for (auto& entry : blocks) {
blocks_to_remove.append(entry);
auto it = executable.executable.basic_blocks.find_if([entry](auto& block) { return entry == block; });
VERIFY(!it.is_end());
if (!first_successor_position.has_value())
first_successor_position = it.index();
}
for (auto& block : executable.executable.basic_blocks) {
InstructionStreamIterator it { block.instruction_stream() };
while (!it.at_end()) {
auto& instruction = *it;
++it;
for (auto& entry : blocks)
const_cast<Instruction&>(instruction).replace_references(*entry, replacement);
}
}
return first_successor_position;
};
for (auto& entry : blocks_to_replace) {
AK::Array candidates { entry.key };
(void)replace_blocks(candidates, *entry.value);
}
while (!blocks_to_merge.is_empty()) {
auto it = blocks_to_merge.begin();
auto current_block = *it;
blocks_to_merge.remove(it);
Vector<BasicBlock const*> successors { current_block };
for (;;) {
auto last = successors.last();
auto entry = cfg.get(last);
if (!entry.has_value())
break;
auto& successor = *entry->begin();
successors.append(successor);
auto it = blocks_to_merge.find(successor);
if (it == blocks_to_merge.end())
break;
blocks_to_merge.remove(it);
}
auto blocks_to_merge_copy = blocks_to_merge;
for (auto& last : blocks_to_merge) {
auto entry = cfg.get(last);
if (!entry.has_value())
continue;
auto successor = *entry->begin();
if (auto it = successors.find(successor); !it.is_end()) {
successors.insert(it.index(), last);
blocks_to_merge_copy.remove(last);
}
}
blocks_to_merge = move(blocks_to_merge_copy);
size_t size = 0;
StringBuilder builder;
builder.append("merge");
for (auto& entry : successors) {
size += entry->size();
builder.append('.');
builder.append(entry->name());
}
auto new_block = BasicBlock::create(builder.build(), size);
auto& block = *new_block;
size_t last_successor_index = successors.size() - 1;
for (size_t i = 0; i < successors.size(); ++i) {
auto& entry = successors[i];
InstructionStreamIterator it { entry->instruction_stream() };
size_t copy_end = 0;
while (!it.at_end()) {
auto& instruction = *it;
++it;
if (instruction.is_terminator() && last_successor_index != i)
break;
copy_end = it.offset();
}
__builtin_memcpy(block.next_slot(), entry->instruction_stream().data(), copy_end);
block.grow(copy_end);
}
auto first_successor_position = replace_blocks(successors, *new_block);
VERIFY(first_successor_position.has_value());
executable.executable.basic_blocks.insert(*first_successor_position, move(new_block));
}
executable.executable.basic_blocks.remove_all_matching([&blocks_to_remove](auto& candidate) { return blocks_to_remove.contains_slow(candidate.ptr()); });
finished();
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/PassManager.h>
namespace JS::Bytecode::Passes {
void PlaceBlocks::perform(PassPipelineExecutable& executable)
{
started();
VERIFY(executable.cfg.has_value());
auto cfg = executable.cfg.release_value();
Vector<BasicBlock&> replaced_blocks;
HashTable<BasicBlock const*> reachable_blocks;
// Visit the blocks in CFG order
AK::Function<void(BasicBlock const*)> visit = [&](auto* block) {
if (reachable_blocks.contains(block))
return;
reachable_blocks.set(block);
replaced_blocks.append(*const_cast<BasicBlock*>(block));
for (auto& entry : cfg.get(block).value_or({}))
visit(entry);
};
// Make sure to visit the entry block first
visit(&executable.executable.basic_blocks.first());
for (auto& entry : cfg)
visit(entry.key);
// Put the unreferenced blocks back in at the end
for (auto& entry : static_cast<Vector<NonnullOwnPtr<BasicBlock>>&>(executable.executable.basic_blocks)) {
if (reachable_blocks.contains(entry.ptr()))
(void)entry.leak_ptr();
else
replaced_blocks.append(*entry.leak_ptr()); // Don't try to do DCE here.
}
executable.executable.basic_blocks.clear();
for (auto& block : replaced_blocks)
executable.executable.basic_blocks.append(adopt_own(block));
finished();
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/PassManager.h>
#include <string.h>
namespace JS::Bytecode::Passes {
void UnifySameBlocks::perform(PassPipelineExecutable& executable)
{
started();
VERIFY(executable.cfg.has_value());
VERIFY(executable.inverted_cfg.has_value());
auto cfg = executable.cfg.release_value();
auto inverted_cfg = executable.inverted_cfg.release_value();
HashMap<BasicBlock const*, BasicBlock const*> equal_blocks;
for (size_t i = 0; i < executable.executable.basic_blocks.size(); ++i) {
auto& block = executable.executable.basic_blocks[i];
auto block_bytes = block.instruction_stream();
for (auto& candidate_block : executable.executable.basic_blocks.span().slice(i + 1)) {
// FIXME: This can probably be relaxed a bit...
if (candidate_block->size() != block.size())
continue;
auto candidate_bytes = candidate_block->instruction_stream();
if (memcmp(candidate_bytes.data(), block_bytes.data(), candidate_block->size()) == 0)
equal_blocks.set(&*candidate_block, &block);
}
}
auto replace_blocks = [&](auto& match, auto& replacement) {
Optional<size_t> first_successor_position;
auto it = executable.executable.basic_blocks.find_if([match](auto& block) { return match == block; });
VERIFY(!it.is_end());
executable.executable.basic_blocks.remove(it.index());
if (!first_successor_position.has_value())
first_successor_position = it.index();
for (auto& block : executable.executable.basic_blocks) {
InstructionStreamIterator it { block.instruction_stream() };
while (!it.at_end()) {
auto& instruction = *it;
++it;
const_cast<Instruction&>(instruction).replace_references(*match, replacement);
}
}
return first_successor_position;
};
for (auto& entry : equal_blocks)
(void)replace_blocks(entry.key, *entry.value);
finished();
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h>
#include <sys/time.h>
#include <time.h>
namespace JS::Bytecode {
struct PassPipelineExecutable {
Executable& executable;
Optional<HashMap<BasicBlock const*, HashTable<BasicBlock const*>>> cfg {};
Optional<HashMap<BasicBlock const*, HashTable<BasicBlock const*>>> inverted_cfg {};
Optional<HashTable<BasicBlock const*>> exported_blocks {};
};
class Pass {
public:
Pass() = default;
virtual ~Pass() = default;
virtual void perform(PassPipelineExecutable&) = 0;
void started()
{
gettimeofday(&m_start_time, nullptr);
}
void finished()
{
struct timeval end_time {
0, 0
};
gettimeofday(&end_time, nullptr);
time_t interval_s = end_time.tv_sec - m_start_time.tv_sec;
suseconds_t interval_us = end_time.tv_usec;
if (interval_us < m_start_time.tv_usec) {
interval_s -= 1;
interval_us += 1000000;
}
interval_us -= m_start_time.tv_usec;
m_time_difference = interval_s * 1000000 + interval_us;
}
u64 elapsed() const { return m_time_difference; }
protected:
struct timeval m_start_time {
0, 0
};
u64 m_time_difference { 0 };
};
class PassManager : public Pass {
public:
PassManager() = default;
~PassManager() override = default;
void add(NonnullOwnPtr<Pass> pass) { m_passes.append(move(pass)); }
template<typename PassT, typename... Args>
void add(Args&&... args) { m_passes.append(make<PassT>(forward<Args>(args)...)); }
void perform(Executable& executable)
{
PassPipelineExecutable pipeline_executable { executable };
perform(pipeline_executable);
}
virtual void perform(PassPipelineExecutable& executable) override
{
started();
for (auto& pass : m_passes)
pass.perform(executable);
finished();
}
private:
NonnullOwnPtrVector<Pass> m_passes;
};
namespace Passes {
class GenerateCFG : public Pass {
public:
GenerateCFG() = default;
~GenerateCFG() override = default;
private:
virtual void perform(PassPipelineExecutable&) override;
};
class MergeBlocks : public Pass {
public:
MergeBlocks() = default;
~MergeBlocks() override = default;
private:
virtual void perform(PassPipelineExecutable&) override;
};
class PlaceBlocks : public Pass {
public:
PlaceBlocks() = default;
~PlaceBlocks() override = default;
private:
virtual void perform(PassPipelineExecutable&) override;
};
class UnifySameBlocks : public Pass {
public:
UnifySameBlocks() = default;
~UnifySameBlocks() override = default;
private:
virtual void perform(PassPipelineExecutable&) override;
};
class DumpCFG : public Pass {
public:
DumpCFG(FILE* file)
: m_file(file)
{
}
~DumpCFG() override = default;
private:
virtual void perform(PassPipelineExecutable&) override;
FILE* m_file { nullptr };
};
}
}

View file

@ -6,6 +6,11 @@ set(SOURCES
Bytecode/Instruction.cpp Bytecode/Instruction.cpp
Bytecode/Interpreter.cpp Bytecode/Interpreter.cpp
Bytecode/Op.cpp Bytecode/Op.cpp
Bytecode/Pass/DumpCFG.cpp
Bytecode/Pass/GenerateCFG.cpp
Bytecode/Pass/MergeBlocks.cpp
Bytecode/Pass/PlaceBlocks.cpp
Bytecode/Pass/UnifySameBlocks.cpp
Bytecode/StringTable.cpp Bytecode/StringTable.cpp
Console.cpp Console.cpp
Heap/CellAllocator.cpp Heap/CellAllocator.cpp

View file

@ -10,6 +10,7 @@
#include <LibJS/Bytecode/BasicBlock.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h> #include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/PassManager.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
@ -159,7 +160,10 @@ Value ScriptFunction::execute_function_body()
prepare_arguments(); prepare_arguments();
if (!m_bytecode_executable.has_value()) { if (!m_bytecode_executable.has_value()) {
m_bytecode_executable = Bytecode::Generator::generate(m_body, m_kind == FunctionKind::Generator); m_bytecode_executable = Bytecode::Generator::generate(m_body, m_kind == FunctionKind::Generator);
auto& passes = JS::Bytecode::Interpreter::optimization_pipeline();
passes.perform(*m_bytecode_executable);
if constexpr (JS_BYTECODE_DEBUG) { if constexpr (JS_BYTECODE_DEBUG) {
dbgln("Optimisation passes took {}us", passes.elapsed());
dbgln("Compiled Bytecode::Block for function '{}':", m_name); dbgln("Compiled Bytecode::Block for function '{}':", m_name);
for (auto& block : m_bytecode_executable->basic_blocks) for (auto& block : m_bytecode_executable->basic_blocks)
block.dump(*m_bytecode_executable); block.dump(*m_bytecode_executable);

View file

@ -17,6 +17,7 @@
#include <LibJS/Bytecode/BasicBlock.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h> #include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/PassManager.h>
#include <LibJS/Console.h> #include <LibJS/Console.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Parser.h> #include <LibJS/Parser.h>
@ -81,6 +82,7 @@ private:
static bool s_dump_ast = false; static bool s_dump_ast = false;
static bool s_dump_bytecode = false; static bool s_dump_bytecode = false;
static bool s_run_bytecode = false; static bool s_run_bytecode = false;
static bool s_opt_bytecode = false;
static bool s_print_last_result = false; static bool s_print_last_result = false;
static RefPtr<Line::Editor> s_editor; static RefPtr<Line::Editor> s_editor;
static String s_history_path = String::formatted("{}/.js-history", Core::StandardPaths::home_directory()); static String s_history_path = String::formatted("{}/.js-history", Core::StandardPaths::home_directory());
@ -558,6 +560,12 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source
} else { } else {
if (s_dump_bytecode || s_run_bytecode) { if (s_dump_bytecode || s_run_bytecode) {
auto unit = JS::Bytecode::Generator::generate(*program); auto unit = JS::Bytecode::Generator::generate(*program);
if (s_opt_bytecode) {
auto& passes = JS::Bytecode::Interpreter::optimization_pipeline();
passes.perform(unit);
dbgln("Optimisation passes took {}us", passes.elapsed());
}
if (s_dump_bytecode) { if (s_dump_bytecode) {
for (auto& block : unit.basic_blocks) for (auto& block : unit.basic_blocks)
block.dump(unit); block.dump(unit);
@ -825,6 +833,7 @@ int main(int argc, char** argv)
args_parser.add_option(s_dump_ast, "Dump the AST", "dump-ast", 'A'); args_parser.add_option(s_dump_ast, "Dump the AST", "dump-ast", 'A');
args_parser.add_option(s_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd'); args_parser.add_option(s_dump_bytecode, "Dump the bytecode", "dump-bytecode", 'd');
args_parser.add_option(s_run_bytecode, "Run the bytecode", "run-bytecode", 'b'); args_parser.add_option(s_run_bytecode, "Run the bytecode", "run-bytecode", 'b');
args_parser.add_option(s_opt_bytecode, "Optimize the bytecode", "optimize-bytecode", 'p');
args_parser.add_option(s_print_last_result, "Print last result", "print-last-result", 'l'); args_parser.add_option(s_print_last_result, "Print last result", "print-last-result", 'l');
args_parser.add_option(gc_on_every_allocation, "GC on every allocation", "gc-on-every-allocation", 'g'); args_parser.add_option(gc_on_every_allocation, "GC on every allocation", "gc-on-every-allocation", 'g');
args_parser.add_option(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's'); args_parser.add_option(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's');