Ver código fonte

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.
Andreas Kling 4 anos atrás
pai
commit
6ae9346cd3

+ 1 - 0
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<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 
 private:
     NonnullRefPtr<Expression> m_test;

+ 12 - 0
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -90,4 +90,16 @@ Optional<Bytecode::Register> AssignmentExpression::generate_bytecode(Bytecode::G
     TODO();
 }
 
+Optional<Bytecode::Register> 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<Bytecode::Op::JumpIfFalse>(*test_result_reg);
+    auto body_result_reg = m_body->generate_bytecode(generator);
+    generator.emit<Bytecode::Op::Jump>(test_label);
+    test_jump.set_target(generator.make_label());
+    return body_result_reg;
+}
+
 }

+ 5 - 0
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() };
+}
+
 }

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

@@ -7,6 +7,7 @@
 #pragma once
 
 #include <AK/OwnPtr.h>
+#include <LibJS/Bytecode/Label.h>
 #include <LibJS/Forward.h>
 
 namespace JS::Bytecode {
@@ -26,6 +27,8 @@ public:
         return *ptr;
     }
 
+    Label make_label() const;
+
 private:
     Generator();
     ~Generator();

+ 9 - 1
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) {

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

@@ -6,6 +6,7 @@
 
 #pragma once
 
+#include <LibJS/Bytecode/Label.h>
 #include <LibJS/Bytecode/Register.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Heap/Cell.h>
@@ -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<Value> m_registers;
+    Optional<size_t> m_pending_jump;
 };
 
 }

+ 34 - 0
Userland/Libraries/LibJS/Bytecode/Label.h

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+
+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<JS::Bytecode::Label> : AK::Formatter<FormatString> {
+    void format(FormatBuilder& builder, JS::Bytecode::Label const& value)
+    {
+        return AK::Formatter<FormatString>::format(builder, "@{}", value.address());
+    }
+};

+ 25 - 0
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:<empty>", m_result);
+}
+
 }

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

@@ -8,6 +8,7 @@
 
 #include <AK/FlyString.h>
 #include <LibJS/Bytecode/Instruction.h>
+#include <LibJS/Bytecode/Label.h>
 #include <LibJS/Bytecode/Register.h>
 #include <LibJS/Heap/Cell.h>
 #include <LibJS/Runtime/Value.h>
@@ -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<Label> target = {})
+        : m_result(result)
+        , m_target(move(target))
+    {
+    }
+
+    void set_target(Optional<Label> target) { m_target = move(target); }
+
+    virtual ~JumpIfFalse() override { }
+    virtual void execute(Bytecode::Interpreter&) const override;
+    virtual String to_string() const override;
+
+private:
+    Register m_result;
+    Optional<Label> m_target;
+};
+
 }