From 6ae9346cd39bf6fb01a971dea85159cdd842cece Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 4 Jun 2021 12:07:38 +0200 Subject: [PATCH] LibJS: Add basic support for while loops in the bytecode engine This introduces two new instructions: Jump and JumpIfFalse. Jumps are made to a Bytecode::Label, which is a simple object that represents a location in the bytecode stream. Note that you may not always know the target of a jump when adding the jump instruction itself, but we can just update the instruction later on during codegen once we know where the jump target is. The Bytecode::Interpreter now implements jumping via a jump slot that gets checked after each instruction to see if a jump is pending. If not, we just increment the PC as usual. --- Userland/Libraries/LibJS/AST.h | 1 + .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 12 +++++++ .../Libraries/LibJS/Bytecode/Generator.cpp | 5 +++ Userland/Libraries/LibJS/Bytecode/Generator.h | 3 ++ .../Libraries/LibJS/Bytecode/Interpreter.cpp | 10 +++++- .../Libraries/LibJS/Bytecode/Interpreter.h | 4 +++ Userland/Libraries/LibJS/Bytecode/Label.h | 34 ++++++++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.cpp | 25 +++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 35 +++++++++++++++++++ 9 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Bytecode/Label.h diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 80261010a2..175b2fd824 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -370,6 +370,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: NonnullRefPtr m_test; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index b6adc70c21..c9e715b0ef 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -90,4 +90,16 @@ Optional AssignmentExpression::generate_bytecode(Bytecode::G TODO(); } +Optional WhileStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + auto test_label = generator.make_label(); + auto test_result_reg = m_test->generate_bytecode(generator); + VERIFY(test_result_reg.has_value()); + auto& test_jump = generator.emit(*test_result_reg); + auto body_result_reg = m_body->generate_bytecode(generator); + generator.emit(test_label); + test_jump.set_target(generator.make_label()); + return body_result_reg; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 89f8106eb3..824f9de063 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -42,4 +42,9 @@ Register Generator::allocate_register() return Register { m_next_register++ }; } +Label Generator::make_label() const +{ + return Label { m_block->instructions().size() }; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index f5dcee15d3..e0b4ab8509 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include namespace JS::Bytecode { @@ -26,6 +27,8 @@ public: return *ptr; } + Label make_label() const; + private: Generator(); ~Generator(); diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 0640176925..79124ec864 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -27,8 +27,16 @@ void Interpreter::run(Bytecode::Block const& block) m_registers.resize(block.register_count()); - for (auto& instruction : block.instructions()) + size_t pc = 0; + while (pc < block.instructions().size()) { + auto& instruction = block.instructions()[pc]; instruction.execute(*this); + if (m_pending_jump.has_value()) { + pc = m_pending_jump.release_value(); + continue; + } + ++pc; + } dbgln("Bytecode::Interpreter did run block {:p}", &block); for (size_t i = 0; i < m_registers.size(); ++i) { diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 70b65fdb6a..dcb74b7fc2 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -25,10 +26,13 @@ public: Value& reg(Register const& r) { return m_registers[r.index()]; } + void jump(Label const& label) { m_pending_jump = label.address(); } + private: VM& m_vm; GlobalObject& m_global_object; Vector m_registers; + Optional m_pending_jump; }; } diff --git a/Userland/Libraries/LibJS/Bytecode/Label.h b/Userland/Libraries/LibJS/Bytecode/Label.h new file mode 100644 index 0000000000..b9d9a1c82d --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Label.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Bytecode { + +class Label { +public: + explicit Label(size_t address) + : m_address(address) + { + } + + size_t address() const { return m_address; } + +private: + size_t m_address { 0 }; +}; + +} + +template<> +struct AK::Formatter : AK::Formatter { + void format(FormatBuilder& builder, JS::Bytecode::Label const& value) + { + return AK::Formatter::format(builder, "@{}", value.address()); + } +}; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 1d975150f7..6cb1ee0356 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -46,6 +46,19 @@ void SetVariable::execute(Bytecode::Interpreter& interpreter) const interpreter.vm().set_variable(m_identifier, interpreter.reg(m_src), interpreter.global_object()); } +void Jump::execute(Bytecode::Interpreter& interpreter) const +{ + interpreter.jump(m_target); +} + +void JumpIfFalse::execute(Bytecode::Interpreter& interpreter) const +{ + VERIFY(m_target.has_value()); + auto result = interpreter.reg(m_result); + if (!result.as_bool()) + interpreter.jump(m_target.value()); +} + String Load::to_string() const { return String::formatted("Load dst:{}, value:{}", m_dst, m_value.to_string_without_side_effects()); @@ -81,4 +94,16 @@ String SetVariable::to_string() const return String::formatted("SetVariable identifier:{}, src:{}", m_identifier, m_src); } +String Jump::to_string() const +{ + return String::formatted("Jump {}", m_target); +} + +String JumpIfFalse::to_string() const +{ + if (m_target.has_value()) + return String::formatted("JumpIfFalse result:{}, target:{}", m_result, m_target.value()); + return String::formatted("JumpIfFalse result:{}, target:", m_result); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 14a6ef1f8f..adfc42758e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -139,4 +140,38 @@ private: FlyString m_identifier; }; +class Jump final : public Instruction { +public: + explicit Jump(Label target) + : m_target(target) + { + } + + virtual ~Jump() override { } + virtual void execute(Bytecode::Interpreter&) const override; + virtual String to_string() const override; + +private: + Label m_target; +}; + +class JumpIfFalse final : public Instruction { +public: + explicit JumpIfFalse(Register result, Optional