mirror of
https://github.com/RGBCube/serenity
synced 2025-05-20 14:35: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:
parent
91640d0727
commit
6ae9346cd3
9 changed files with 128 additions and 1 deletions
|
@ -370,6 +370,7 @@ public:
|
||||||
|
|
||||||
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
virtual Value execute(Interpreter&, GlobalObject&) const override;
|
||||||
virtual void dump(int indent) const override;
|
virtual void dump(int indent) const override;
|
||||||
|
virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NonnullRefPtr<Expression> m_test;
|
NonnullRefPtr<Expression> m_test;
|
||||||
|
|
|
@ -90,4 +90,16 @@ Optional<Bytecode::Register> AssignmentExpression::generate_bytecode(Bytecode::G
|
||||||
TODO();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,4 +42,9 @@ Register Generator::allocate_register()
|
||||||
return Register { m_next_register++ };
|
return Register { m_next_register++ };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label Generator::make_label() const
|
||||||
|
{
|
||||||
|
return Label { m_block->instructions().size() };
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
|
#include <LibJS/Bytecode/Label.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
@ -26,6 +27,8 @@ public:
|
||||||
return *ptr;
|
return *ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label make_label() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Generator();
|
Generator();
|
||||||
~Generator();
|
~Generator();
|
||||||
|
|
|
@ -27,8 +27,16 @@ void Interpreter::run(Bytecode::Block const& block)
|
||||||
|
|
||||||
m_registers.resize(block.register_count());
|
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);
|
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);
|
dbgln("Bytecode::Interpreter did run block {:p}", &block);
|
||||||
for (size_t i = 0; i < m_registers.size(); ++i) {
|
for (size_t i = 0; i < m_registers.size(); ++i) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibJS/Bytecode/Label.h>
|
||||||
#include <LibJS/Bytecode/Register.h>
|
#include <LibJS/Bytecode/Register.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
#include <LibJS/Heap/Cell.h>
|
#include <LibJS/Heap/Cell.h>
|
||||||
|
@ -25,10 +26,13 @@ public:
|
||||||
|
|
||||||
Value& reg(Register const& r) { return m_registers[r.index()]; }
|
Value& reg(Register const& r) { return m_registers[r.index()]; }
|
||||||
|
|
||||||
|
void jump(Label const& label) { m_pending_jump = label.address(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VM& m_vm;
|
VM& m_vm;
|
||||||
GlobalObject& m_global_object;
|
GlobalObject& m_global_object;
|
||||||
Vector<Value> m_registers;
|
Vector<Value> m_registers;
|
||||||
|
Optional<size_t> m_pending_jump;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
34
Userland/Libraries/LibJS/Bytecode/Label.h
Normal file
34
Userland/Libraries/LibJS/Bytecode/Label.h
Normal 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());
|
||||||
|
}
|
||||||
|
};
|
|
@ -46,6 +46,19 @@ void SetVariable::execute(Bytecode::Interpreter& interpreter) const
|
||||||
interpreter.vm().set_variable(m_identifier, interpreter.reg(m_src), interpreter.global_object());
|
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
|
String Load::to_string() const
|
||||||
{
|
{
|
||||||
return String::formatted("Load dst:{}, value:{}", m_dst, m_value.to_string_without_side_effects());
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <AK/FlyString.h>
|
#include <AK/FlyString.h>
|
||||||
#include <LibJS/Bytecode/Instruction.h>
|
#include <LibJS/Bytecode/Instruction.h>
|
||||||
|
#include <LibJS/Bytecode/Label.h>
|
||||||
#include <LibJS/Bytecode/Register.h>
|
#include <LibJS/Bytecode/Register.h>
|
||||||
#include <LibJS/Heap/Cell.h>
|
#include <LibJS/Heap/Cell.h>
|
||||||
#include <LibJS/Runtime/Value.h>
|
#include <LibJS/Runtime/Value.h>
|
||||||
|
@ -139,4 +140,38 @@ private:
|
||||||
FlyString m_identifier;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue