Ver Fonte

LibJS: Start fleshing out a bytecode for the JavaScript engine :^)

This patch begins the work of implementing JavaScript execution in a
bytecode VM instead of an AST tree-walk interpreter.

It's probably quite naive, but we have to start somewhere.

The basic idea is that you call Bytecode::Generator::generate() on an
AST node and it hands you back a Bytecode::Block filled with
instructions that can then be interpreted by a Bytecode::Interpreter.

This first version only implements two instructions: Load and Add. :^)

Each bytecode block has infinity registers, and the interpreter resizes
its register file to fit the block being executed.

Two new `js` options are added in this patch as well:

`-d` will dump the generated bytecode
`-b` will execute the generated bytecode

Note that unless `-d` and/or `-b` are specified, none of the bytecode
related stuff in LibJS runs at all. This is implemented in parallel
with the existing AST interpreter. :^)
Andreas Kling há 4 anos atrás
pai
commit
69dddd4ef5

+ 49 - 1
Userland/Libraries/LibJS/AST.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -13,6 +13,9 @@
 #include <AK/TemporaryChange.h>
 #include <LibCrypto/BigInt/SignedBigInteger.h>
 #include <LibJS/AST.h>
+#include <LibJS/Bytecode/Generator.h>
+#include <LibJS/Bytecode/Op.h>
+#include <LibJS/Bytecode/Register.h>
 #include <LibJS/Interpreter.h>
 #include <LibJS/Runtime/Accessor.h>
 #include <LibJS/Runtime/Array.h>
@@ -2234,4 +2237,49 @@ void ScopeNode::add_functions(NonnullRefPtrVector<FunctionDeclaration> functions
     m_functions.append(move(functions));
 }
 
+Optional<Bytecode::Register> ASTNode::generate_bytecode(Bytecode::Generator&) const
+{
+    dbgln("Missing generate_bytecode()");
+    TODO();
+}
+
+Optional<Bytecode::Register> ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
+{
+    for (auto& child : children()) {
+        [[maybe_unused]] auto reg = child.generate_bytecode(generator);
+    }
+    return {};
+}
+
+Optional<Bytecode::Register> ExpressionStatement::generate_bytecode(Bytecode::Generator& generator) const
+{
+    return m_expression->generate_bytecode(generator);
+}
+
+Optional<Bytecode::Register> 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<Bytecode::Op::Add>(dst_reg, *lhs_reg, *rhs_reg);
+        return dst_reg;
+    }
+    default:
+        TODO();
+    }
+}
+
+Optional<Bytecode::Register> NumericLiteral::generate_bytecode(Bytecode::Generator& generator) const
+{
+    auto dst = generator.allocate_register();
+    generator.emit<Bytecode::Op::Load>(dst, m_value);
+    return dst;
+}
+
 }

+ 6 - 1
Userland/Libraries/LibJS/AST.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2020, Linus Groh <linusg@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -37,6 +37,7 @@ class ASTNode : public RefCounted<ASTNode> {
 public:
     virtual ~ASTNode() { }
     virtual Value execute(Interpreter&, GlobalObject&) const = 0;
+    virtual Optional<Bytecode::Register> 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<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 
     const Expression& expression() const { return m_expression; };
 
@@ -120,6 +122,7 @@ public:
     const NonnullRefPtrVector<Statement>& children() const { return m_children; }
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
+    virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 
     void add_variables(NonnullRefPtrVector<VariableDeclaration>);
     void add_functions(NonnullRefPtrVector<FunctionDeclaration>);
@@ -522,6 +525,7 @@ public:
 
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
+    virtual Optional<Bytecode::Register> 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<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 
 private:
     Value m_value;

+ 38 - 0
Userland/Libraries/LibJS/Bytecode/Block.cpp

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/String.h>
+#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/Instruction.h>
+
+namespace JS::Bytecode {
+
+NonnullOwnPtr<Block> Block::create()
+{
+    return adopt_own(*new Block);
+}
+
+Block::Block()
+{
+}
+
+Block::~Block()
+{
+}
+
+void Block::append(Badge<Bytecode::Generator>, NonnullOwnPtr<Instruction> 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());
+    }
+}
+
+}

+ 35 - 0
Userland/Libraries/LibJS/Bytecode/Block.h

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Badge.h>
+#include <AK/NonnullOwnPtrVector.h>
+#include <LibJS/Forward.h>
+
+namespace JS::Bytecode {
+
+class Block {
+public:
+    static NonnullOwnPtr<Block> create();
+    ~Block();
+
+    NonnullOwnPtrVector<Instruction> const& instructions() const { return m_instructions; }
+    void dump() const;
+
+    size_t register_count() const { return m_register_count; }
+
+    void append(Badge<Bytecode::Generator>, NonnullOwnPtr<Instruction>);
+    void set_register_count(Badge<Bytecode::Generator>, size_t count) { m_register_count = count; }
+
+private:
+    Block();
+
+    size_t m_register_count { 0 };
+    NonnullOwnPtrVector<Instruction> m_instructions;
+};
+
+}

+ 45 - 0
Userland/Libraries/LibJS/Bytecode/Generator.cpp

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/OwnPtr.h>
+#include <LibJS/AST.h>
+#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/Generator.h>
+#include <LibJS/Bytecode/Instruction.h>
+#include <LibJS/Bytecode/Register.h>
+#include <LibJS/Forward.h>
+
+namespace JS::Bytecode {
+
+Generator::Generator()
+{
+    m_block = Block::create();
+}
+
+Generator::~Generator()
+{
+}
+
+OwnPtr<Block> 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> instruction)
+{
+    m_block->append({}, move(instruction));
+}
+
+Register Generator::allocate_register()
+{
+    VERIFY(m_next_register != NumericLimits<u32>::max());
+    return Register { m_next_register++ };
+}
+
+}

+ 37 - 0
Userland/Libraries/LibJS/Bytecode/Generator.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/OwnPtr.h>
+#include <LibJS/Forward.h>
+
+namespace JS::Bytecode {
+
+class Generator {
+public:
+    static OwnPtr<Block> generate(ASTNode const&);
+
+    Register allocate_register();
+
+    template<typename OpType, typename... Args>
+    void emit(Args&&... args)
+    {
+        auto instruction = make<OpType>(forward<Args>(args)...);
+        append(move(instruction));
+    }
+
+private:
+    Generator();
+    ~Generator();
+
+    void append(NonnullOwnPtr<Instruction>);
+
+    OwnPtr<Block> m_block;
+    u32 m_next_register { 1 };
+};
+
+}

+ 15 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.cpp

@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/Instruction.h>
+
+namespace JS::Bytecode {
+
+Instruction::~Instruction()
+{
+}
+
+}

+ 22 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Forward.h>
+#include <LibJS/Forward.h>
+
+namespace JS::Bytecode {
+
+class Instruction {
+public:
+    virtual ~Instruction();
+
+    virtual String to_string() const = 0;
+    virtual void execute(Bytecode::Interpreter&) const = 0;
+};
+
+}

+ 42 - 0
Userland/Libraries/LibJS/Bytecode/Interpreter.cpp

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/Instruction.h>
+#include <LibJS/Bytecode/Interpreter.h>
+
+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);
+    }
+}
+
+}

+ 32 - 0
Userland/Libraries/LibJS/Bytecode/Interpreter.h

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Bytecode/Register.h>
+#include <LibJS/Forward.h>
+#include <LibJS/Heap/Cell.h>
+#include <LibJS/Runtime/Value.h>
+
+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<Value> m_registers;
+};
+
+}

+ 33 - 0
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Bytecode/Interpreter.h>
+#include <LibJS/Bytecode/Op.h>
+#include <LibJS/Runtime/Value.h>
+
+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());
+}
+
+}

+ 52 - 0
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Bytecode/Instruction.h>
+#include <LibJS/Bytecode/Register.h>
+#include <LibJS/Heap/Cell.h>
+#include <LibJS/Runtime/Value.h>
+
+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;
+};
+
+}

+ 26 - 0
Userland/Libraries/LibJS/Bytecode/Register.h

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace JS::Bytecode {
+
+class Register {
+public:
+    explicit Register(u32 index)
+        : m_index(index)
+    {
+    }
+
+    u32 index() const { return m_index; }
+
+private:
+    u32 m_index { 0 };
+};
+
+}

+ 26 - 21
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

+ 8 - 0
Userland/Libraries/LibJS/Forward.h

@@ -162,4 +162,12 @@ JS_ENUMERATE_TYPED_ARRAYS
 template<class T>
 class Handle;
 
+namespace Bytecode {
+class Block;
+class Generator;
+class Instruction;
+class Interpreter;
+class Register;
+}
+
 }

+ 21 - 1
Userland/Utilities/js.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
@@ -14,6 +14,9 @@
 #include <LibCore/File.h>
 #include <LibCore/StandardPaths.h>
 #include <LibJS/AST.h>
+#include <LibJS/Bytecode/Block.h>
+#include <LibJS/Bytecode/Generator.h>
+#include <LibJS/Bytecode/Interpreter.h>
 #include <LibJS/Console.h>
 #include <LibJS/Interpreter.h>
 #include <LibJS/Parser.h>
@@ -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<Line::Editor> 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');