diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 43fb32dc9d..c5277d7979 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -2234,4 +2237,49 @@ void ScopeNode::add_functions(NonnullRefPtrVector functions m_functions.append(move(functions)); } +Optional ASTNode::generate_bytecode(Bytecode::Generator&) const +{ + dbgln("Missing generate_bytecode()"); + TODO(); +} + +Optional ScopeNode::generate_bytecode(Bytecode::Generator& generator) const +{ + for (auto& child : children()) { + [[maybe_unused]] auto reg = child.generate_bytecode(generator); + } + return {}; +} + +Optional ExpressionStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + return m_expression->generate_bytecode(generator); +} + +Optional BinaryExpression::generate_bytecode(Bytecode::Generator& generator) const +{ + auto lhs_reg = m_lhs->generate_bytecode(generator); + auto rhs_reg = m_rhs->generate_bytecode(generator); + + VERIFY(lhs_reg.has_value()); + VERIFY(rhs_reg.has_value()); + + switch (m_op) { + case BinaryOp::Addition: { + auto dst_reg = generator.allocate_register(); + generator.emit(dst_reg, *lhs_reg, *rhs_reg); + return dst_reg; + } + default: + TODO(); + } +} + +Optional NumericLiteral::generate_bytecode(Bytecode::Generator& generator) const +{ + auto dst = generator.allocate_register(); + generator.emit(dst, m_value); + return dst; +} + } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 74471caa4c..198063d0a5 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -37,6 +37,7 @@ class ASTNode : public RefCounted { public: virtual ~ASTNode() { } virtual Value execute(Interpreter&, GlobalObject&) const = 0; + virtual Optional generate_bytecode(Bytecode::Generator&) const; virtual void dump(int indent) const; const SourceRange& source_range() const { return m_source_range; } @@ -96,6 +97,7 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual Optional generate_bytecode(Bytecode::Generator&) const override; const Expression& expression() const { return m_expression; }; @@ -120,6 +122,7 @@ public: const NonnullRefPtrVector& children() const { return m_children; } virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual Optional generate_bytecode(Bytecode::Generator&) const override; void add_variables(NonnullRefPtrVector); void add_functions(NonnullRefPtrVector); @@ -522,6 +525,7 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual Optional generate_bytecode(Bytecode::Generator&) const override; private: BinaryOp m_op; @@ -630,6 +634,7 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual Optional generate_bytecode(Bytecode::Generator&) const override; private: Value m_value; diff --git a/Userland/Libraries/LibJS/Bytecode/Block.cpp b/Userland/Libraries/LibJS/Bytecode/Block.cpp new file mode 100644 index 0000000000..0fa452d98a --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Block.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Bytecode { + +NonnullOwnPtr Block::create() +{ + return adopt_own(*new Block); +} + +Block::Block() +{ +} + +Block::~Block() +{ +} + +void Block::append(Badge, NonnullOwnPtr instruction) +{ + m_instructions.append(move(instruction)); +} + +void Block::dump() const +{ + for (size_t i = 0; i < m_instructions.size(); ++i) { + warnln("[{:3}] {}", i, m_instructions[i].to_string()); + } +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Block.h b/Userland/Libraries/LibJS/Bytecode/Block.h new file mode 100644 index 0000000000..b96b199ab0 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Block.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS::Bytecode { + +class Block { +public: + static NonnullOwnPtr create(); + ~Block(); + + NonnullOwnPtrVector const& instructions() const { return m_instructions; } + void dump() const; + + size_t register_count() const { return m_register_count; } + + void append(Badge, NonnullOwnPtr); + void set_register_count(Badge, size_t count) { m_register_count = count; } + +private: + Block(); + + size_t m_register_count { 0 }; + NonnullOwnPtrVector m_instructions; +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp new file mode 100644 index 0000000000..89f8106eb3 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace JS::Bytecode { + +Generator::Generator() +{ + m_block = Block::create(); +} + +Generator::~Generator() +{ +} + +OwnPtr Generator::generate(ASTNode const& node) +{ + Generator generator; + [[maybe_unused]] auto dummy = node.generate_bytecode(generator); + generator.m_block->set_register_count({}, generator.m_next_register); + return move(generator.m_block); +} + +void Generator::append(NonnullOwnPtr instruction) +{ + m_block->append({}, move(instruction)); +} + +Register Generator::allocate_register() +{ + VERIFY(m_next_register != NumericLimits::max()); + return Register { m_next_register++ }; +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h new file mode 100644 index 0000000000..ed4fa05818 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Bytecode { + +class Generator { +public: + static OwnPtr generate(ASTNode const&); + + Register allocate_register(); + + template + void emit(Args&&... args) + { + auto instruction = make(forward(args)...); + append(move(instruction)); + } + +private: + Generator(); + ~Generator(); + + void append(NonnullOwnPtr); + + OwnPtr m_block; + u32 m_next_register { 1 }; +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.cpp b/Userland/Libraries/LibJS/Bytecode/Instruction.cpp new file mode 100644 index 0000000000..1397915eb5 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.cpp @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS::Bytecode { + +Instruction::~Instruction() +{ +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h new file mode 100644 index 0000000000..9ce3b3566c --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Bytecode { + +class Instruction { +public: + virtual ~Instruction(); + + virtual String to_string() const = 0; + virtual void execute(Bytecode::Interpreter&) const = 0; +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp new file mode 100644 index 0000000000..df4ead07ac --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Bytecode { + +Interpreter::Interpreter(GlobalObject& global_object) + : m_global_object(global_object) +{ +} + +Interpreter::~Interpreter() +{ +} + +void Interpreter::run(Bytecode::Block const& block) +{ + dbgln("Bytecode::Interpreter will run block {:p}", &block); + + m_registers.resize(block.register_count()); + + for (auto& instruction : block.instructions()) + instruction.execute(*this); + + dbgln("Bytecode::Interpreter did run block {:p}", &block); + for (size_t i = 0; i < m_registers.size(); ++i) { + String value_string; + if (m_registers[i].is_empty()) + value_string = "(empty)"; + else + value_string = m_registers[i].to_string_without_side_effects(); + dbgln("[{:3}] {}", i, value_string); + } +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h new file mode 100644 index 0000000000..0a08ea9c69 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS::Bytecode { + +class Interpreter { +public: + explicit Interpreter(GlobalObject&); + ~Interpreter(); + + GlobalObject& global_object() { return m_global_object; } + + void run(Bytecode::Block const&); + + Value& reg(Register const& r) { return m_registers[r.index()]; } + +private: + GlobalObject& m_global_object; + Vector m_registers; +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp new file mode 100644 index 0000000000..52564e6be8 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Bytecode::Op { + +void Load::execute(Bytecode::Interpreter& interpreter) const +{ + interpreter.reg(m_dst) = m_value; +} + +void Add::execute(Bytecode::Interpreter& interpreter) const +{ + interpreter.reg(m_dst) = add(interpreter.global_object(), interpreter.reg(m_src1), interpreter.reg(m_src2)); +} + +String Load::to_string() const +{ + return String::formatted("Load dst:r{}, value:{}", m_dst.index(), m_value.to_string_without_side_effects()); +} + +String Add::to_string() const +{ + return String::formatted("Add dst:r{}, src1:r{}, src2:r{}", m_dst.index(), m_src1.index(), m_src2.index()); +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h new file mode 100644 index 0000000000..872a65c026 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace JS::Bytecode::Op { + +class Load final : public Instruction { +public: + Load(Register dst, Value value) + : m_dst(dst) + , m_value(value) + { + } + + virtual ~Load() override { } + virtual void execute(Bytecode::Interpreter&) const override; + virtual String to_string() const override; + +private: + Register m_dst; + Value m_value; +}; + +class Add final : public Instruction { +public: + Add(Register dst, Register src1, Register src2) + : m_dst(dst) + , m_src1(src1) + , m_src2(src2) + { + } + + virtual ~Add() override { } + virtual void execute(Bytecode::Interpreter&) const override; + virtual String to_string() const override; + +private: + Register m_dst; + Register m_src1; + Register m_src2; +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Register.h b/Userland/Libraries/LibJS/Bytecode/Register.h new file mode 100644 index 0000000000..b3c8681fd9 --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Register.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Bytecode { + +class Register { +public: + explicit Register(u32 index) + : m_index(index) + { + } + + u32 index() const { return m_index; } + +private: + u32 m_index { 0 }; +}; + +} diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 98459d1b6a..da9a199f63 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -1,25 +1,30 @@ set(SOURCES - AST.cpp - Console.cpp - Heap/CellAllocator.cpp - Heap/BlockAllocator.cpp - Heap/Handle.cpp - Heap/HeapBlock.cpp - Heap/Heap.cpp - Interpreter.cpp - Lexer.cpp - MarkupGenerator.cpp - Parser.cpp - Runtime/Array.cpp - Runtime/ArrayBuffer.cpp - Runtime/ArrayBufferConstructor.cpp - Runtime/ArrayBufferPrototype.cpp - Runtime/ArrayConstructor.cpp - Runtime/ArrayIterator.cpp - Runtime/ArrayIteratorPrototype.cpp - Runtime/ArrayPrototype.cpp - Runtime/BigInt.cpp - Runtime/BigIntConstructor.cpp + AST.cpp + Bytecode/Block.cpp + Bytecode/Generator.cpp + Bytecode/Instruction.cpp + Bytecode/Interpreter.cpp + Bytecode/Op.cpp + Console.cpp + Heap/CellAllocator.cpp + Heap/BlockAllocator.cpp + Heap/Handle.cpp + Heap/HeapBlock.cpp + Heap/Heap.cpp + Interpreter.cpp + Lexer.cpp + MarkupGenerator.cpp + Parser.cpp + Runtime/Array.cpp + Runtime/ArrayBuffer.cpp + Runtime/ArrayBufferConstructor.cpp + Runtime/ArrayBufferPrototype.cpp + Runtime/ArrayConstructor.cpp + Runtime/ArrayIterator.cpp + Runtime/ArrayIteratorPrototype.cpp + Runtime/ArrayPrototype.cpp + Runtime/BigInt.cpp + Runtime/BigIntConstructor.cpp Runtime/BigIntObject.cpp Runtime/BigIntPrototype.cpp Runtime/BooleanConstructor.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 727560b3ff..0a2665cc42 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -162,4 +162,12 @@ JS_ENUMERATE_TYPED_ARRAYS template class Handle; +namespace Bytecode { +class Block; +class Generator; +class Instruction; +class Interpreter; +class Register; +} + } diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index d4b748b6a6..9cdb691eb2 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -73,6 +76,8 @@ private: }; static bool s_dump_ast = false; +static bool s_dump_bytecode = false; +static bool s_run_bytecode = false; static bool s_print_last_result = false; static RefPtr s_editor; static String s_history_path = String::formatted("{}/.js-history", Core::StandardPaths::home_directory()); @@ -489,6 +494,19 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source if (s_dump_ast) program->dump(0); + if (s_dump_bytecode || s_run_bytecode) { + auto block = JS::Bytecode::Generator::generate(*program); + VERIFY(block); + + if (s_dump_bytecode) + block->dump(); + + if (s_run_bytecode) { + JS::Bytecode::Interpreter bytecode_interpreter(interpreter.global_object()); + bytecode_interpreter.run(*block); + } + } + if (parser.has_errors()) { auto error = parser.errors()[0]; auto hint = error.source_location_hint(source); @@ -744,6 +762,8 @@ int main(int argc, char** argv) Core::ArgsParser args_parser; args_parser.set_general_help("This is a JavaScript interpreter."); 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_run_bytecode, "Run the bytecode", "run-bytecode", 'b'); 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(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's');