1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 18:05:07 +00:00

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.
This commit is contained in:
Andreas Kling 2021-06-04 12:07:38 +02:00
parent 91640d0727
commit 6ae9346cd3
9 changed files with 128 additions and 1 deletions

View file

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

View file

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

View file

@ -42,4 +42,9 @@ Register Generator::allocate_register()
return Register { m_next_register++ };
}
Label Generator::make_label() const
{
return Label { m_block->instructions().size() };
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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