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. :^)
This commit is contained in:
Andreas Kling 2021-06-03 10:46:30 +02:00
parent f9395efaac
commit 69dddd4ef5
Notes: sideshowbarker 2024-07-18 12:42:51 +09:00
16 changed files with 487 additions and 24 deletions

View file

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

View file

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

View file

@ -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());
}
}
}

View file

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

View file

@ -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++ };
}
}

View file

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

View file

@ -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()
{
}
}

View file

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

View file

@ -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);
}
}
}

View file

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

View file

@ -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());
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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');