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

LibJS: Generate bytecode in basic blocks instead of one big block

This limits the size of each block (currently set to 1K), and gets us
closer to a canonical, more easily analysable bytecode format.
As a result of this, "Labels" are now simply entries to basic blocks.
Since there is no more 'conditional' jump (as all jumps are always
taken), JumpIf{True,False} are unified to JumpConditional, and
JumpIfNullish is renamed to JumpNullish.
Also fixes #7914 as a result of reimplementing the loop logic.
This commit is contained in:
Ali Mohammad Pur 2021-06-09 06:49:58 +04:30 committed by Andreas Kling
parent d7a25cdb82
commit 01e8f0889a
16 changed files with 392 additions and 174 deletions

View file

@ -119,23 +119,43 @@ void LogicalExpression::generate_bytecode(Bytecode::Generator& generator) const
{ {
m_lhs->generate_bytecode(generator); m_lhs->generate_bytecode(generator);
Bytecode::Op::Jump* test_instr; // lhs
// jump op (true) end (false) rhs
// rhs
// jump always (true) end
// end
auto& rhs_block = generator.make_block();
auto& end_block = generator.make_block();
switch (m_op) { switch (m_op) {
case LogicalOp::And: case LogicalOp::And:
test_instr = &generator.emit<Bytecode::Op::JumpIfFalse>(); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { rhs_block },
Bytecode::Label { end_block });
break; break;
case LogicalOp::Or: case LogicalOp::Or:
test_instr = &generator.emit<Bytecode::Op::JumpIfTrue>(); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { end_block },
Bytecode::Label { rhs_block });
break; break;
case LogicalOp::NullishCoalescing: case LogicalOp::NullishCoalescing:
test_instr = &generator.emit<Bytecode::Op::JumpIfNotNullish>(); generator.emit<Bytecode::Op::JumpNullish>().set_targets(
Bytecode::Label { rhs_block },
Bytecode::Label { end_block });
break; break;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
generator.switch_to_basic_block(rhs_block);
m_rhs->generate_bytecode(generator); m_rhs->generate_bytecode(generator);
test_instr->set_target(generator.make_label());
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { end_block },
{});
generator.switch_to_basic_block(end_block);
} }
void UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const void UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const
@ -279,58 +299,154 @@ void AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) con
void WhileStatement::generate_bytecode(Bytecode::Generator& generator) const void WhileStatement::generate_bytecode(Bytecode::Generator& generator) const
{ {
// test
// jump if_false (true) end (false) body
// body
// jump always (true) test
// end
auto& test_block = generator.make_block();
auto& body_block = generator.make_block();
auto& end_block = generator.make_block();
// Init result register
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined()); generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.begin_continuable_scope();
auto test_label = generator.make_label();
auto result_reg = generator.allocate_register(); auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg); generator.emit<Bytecode::Op::Store>(result_reg);
// jump to the test block
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { test_block },
{});
generator.switch_to_basic_block(test_block);
m_test->generate_bytecode(generator); m_test->generate_bytecode(generator);
auto& test_jump = generator.emit<Bytecode::Op::JumpIfFalse>(); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { body_block },
Bytecode::Label { end_block });
generator.switch_to_basic_block(body_block);
generator.begin_continuable_scope(Bytecode::Label { test_block });
m_body->generate_bytecode(generator); m_body->generate_bytecode(generator);
generator.emit<Bytecode::Op::Jump>(test_label); generator.emit<Bytecode::Op::Jump>().set_targets(
test_jump.set_target(generator.make_label()); Bytecode::Label { test_block },
{});
generator.end_continuable_scope(); generator.end_continuable_scope();
generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg); generator.emit<Bytecode::Op::Load>(result_reg);
} }
void DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const void DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const
{ {
// jump always (true) body
// test
// jump if_false (true) end (false) body
// body
// jump always (true) test
// end
auto& test_block = generator.make_block();
auto& body_block = generator.make_block();
auto& end_block = generator.make_block();
// Init result register
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined()); generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.begin_continuable_scope();
auto head_label = generator.make_label();
m_body->generate_bytecode(generator);
generator.end_continuable_scope();
auto result_reg = generator.allocate_register(); auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg); generator.emit<Bytecode::Op::Store>(result_reg);
// jump to the body block
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { body_block },
{});
generator.switch_to_basic_block(test_block);
m_test->generate_bytecode(generator); m_test->generate_bytecode(generator);
generator.emit<Bytecode::Op::JumpIfTrue>(head_label); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { body_block },
Bytecode::Label { end_block });
generator.switch_to_basic_block(body_block);
generator.begin_continuable_scope(Bytecode::Label { test_block });
m_body->generate_bytecode(generator);
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { test_block },
{});
generator.end_continuable_scope();
generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg); generator.emit<Bytecode::Op::Load>(result_reg);
} }
void ForStatement::generate_bytecode(Bytecode::Generator& generator) const void ForStatement::generate_bytecode(Bytecode::Generator& generator) const
{ {
Bytecode::Op::Jump* test_jump { nullptr }; // init
// jump always (true) test
// test
// jump if_true (true) body (false) end
// body
// jump always (true) update
// update
// jump always (true) test
// end
// If 'test' is missing, fuse the 'test' and 'body' basic blocks
// If 'update' is missing, fuse the 'body' and 'update' basic blocks
Bytecode::BasicBlock* test_block_ptr { nullptr };
Bytecode::BasicBlock* body_block_ptr { nullptr };
Bytecode::BasicBlock* update_block_ptr { nullptr };
auto& end_block = generator.make_block();
if (m_init) if (m_init)
m_init->generate_bytecode(generator); m_init->generate_bytecode(generator);
body_block_ptr = &generator.make_block();
if (m_test)
test_block_ptr = &generator.make_block();
else
test_block_ptr = body_block_ptr;
if (m_update)
update_block_ptr = &generator.make_block();
else
update_block_ptr = body_block_ptr;
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined()); generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.begin_continuable_scope();
auto jump_label = generator.make_label();
auto result_reg = generator.allocate_register(); auto result_reg = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(result_reg); generator.emit<Bytecode::Op::Store>(result_reg);
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { *test_block_ptr },
{});
if (m_test) { if (m_test) {
generator.switch_to_basic_block(*test_block_ptr);
m_test->generate_bytecode(generator); m_test->generate_bytecode(generator);
test_jump = &generator.emit<Bytecode::Op::JumpIfFalse>(); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { *body_block_ptr },
Bytecode::Label { end_block });
} }
generator.switch_to_basic_block(*body_block_ptr);
generator.begin_continuable_scope(Bytecode::Label { *update_block_ptr });
m_body->generate_bytecode(generator); m_body->generate_bytecode(generator);
if (m_update)
m_update->generate_bytecode(generator);
generator.emit<Bytecode::Op::Jump>(jump_label);
if (m_test)
test_jump->set_target(generator.make_label());
generator.end_continuable_scope(); generator.end_continuable_scope();
if (m_update) {
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { *update_block_ptr },
{});
generator.switch_to_basic_block(*update_block_ptr);
m_update->generate_bytecode(generator);
}
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { *test_block_ptr },
{});
generator.switch_to_basic_block(end_block);
generator.emit<Bytecode::Op::Load>(result_reg); generator.emit<Bytecode::Op::Load>(result_reg);
} }
@ -408,23 +524,60 @@ void ReturnStatement::generate_bytecode(Bytecode::Generator& generator) const
void IfStatement::generate_bytecode(Bytecode::Generator& generator) const void IfStatement::generate_bytecode(Bytecode::Generator& generator) const
{ {
m_predicate->generate_bytecode(generator); // test
auto& else_jump = generator.emit<Bytecode::Op::JumpIfFalse>(); // jump if_true (true) true (false) false
// true
// jump always (true) end
// false
// jump always (true) end
// end
// If the 'false' branch doesn't exist, we're just gonna substitute it for 'end' and elide the last two entries above.
auto& true_block = generator.make_block();
auto& false_block = generator.make_block();
m_predicate->generate_bytecode(generator);
generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { true_block },
Bytecode::Label { false_block });
Bytecode::Op::Jump* true_block_jump { nullptr };
generator.switch_to_basic_block(true_block);
m_consequent->generate_bytecode(generator); m_consequent->generate_bytecode(generator);
if (!generator.is_current_block_terminated())
true_block_jump = &generator.emit<Bytecode::Op::Jump>();
generator.switch_to_basic_block(false_block);
if (m_alternate) { if (m_alternate) {
auto& if_jump = generator.emit<Bytecode::Op::Jump>(); auto& end_block = generator.make_block();
else_jump.set_target(generator.make_label());
m_alternate->generate_bytecode(generator); m_alternate->generate_bytecode(generator);
if_jump.set_target(generator.make_label()); if (!generator.is_current_block_terminated())
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { end_block },
{});
if (true_block_jump)
true_block_jump->set_targets(
Bytecode::Label { end_block },
{});
generator.switch_to_basic_block(end_block);
} else { } else {
else_jump.set_target(generator.make_label()); if (true_block_jump)
true_block_jump->set_targets(
Bytecode::Label { false_block },
{});
} }
} }
void ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const void ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const
{ {
generator.emit<Bytecode::Op::Jump>(generator.nearest_continuable_scope()); generator.emit<Bytecode::Op::Jump>().set_targets(
generator.nearest_continuable_scope(),
{});
} }
void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const
@ -433,16 +586,36 @@ void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const
void ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const void ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const
{ {
// test
// jump if_true (true) true (false) false
// true
// jump always (true) end
// false
// jump always (true) end
// end
auto& true_block = generator.make_block();
auto& false_block = generator.make_block();
auto& end_block = generator.make_block();
m_test->generate_bytecode(generator); m_test->generate_bytecode(generator);
auto& alternate_jump = generator.emit<Bytecode::Op::JumpIfFalse>(); generator.emit<Bytecode::Op::JumpConditional>().set_targets(
Bytecode::Label { true_block },
Bytecode::Label { false_block });
generator.switch_to_basic_block(true_block);
m_consequent->generate_bytecode(generator); m_consequent->generate_bytecode(generator);
auto& end_jump = generator.emit<Bytecode::Op::Jump>(); generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { end_block },
{});
alternate_jump.set_target(generator.make_label()); generator.switch_to_basic_block(false_block);
m_alternate->generate_bytecode(generator); m_alternate->generate_bytecode(generator);
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { end_block },
{});
end_jump.set_target(generator.make_label()); generator.switch_to_basic_block(end_block);
} }
void SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const void SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const
@ -464,5 +637,4 @@ void TemplateLiteral::generate_bytecode(Bytecode::Generator& generator) const
} }
} }
} }
} }

View file

@ -5,28 +5,29 @@
*/ */
#include <AK/String.h> #include <AK/String.h>
#include <LibJS/Bytecode/Block.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Op.h> #include <LibJS/Bytecode/Op.h>
#include <sys/mman.h> #include <sys/mman.h>
namespace JS::Bytecode { namespace JS::Bytecode {
NonnullOwnPtr<Block> Block::create() NonnullOwnPtr<BasicBlock> BasicBlock::create(String name)
{ {
return adopt_own(*new Block); return adopt_own(*new BasicBlock(move(name)));
} }
Block::Block() BasicBlock::BasicBlock(String name)
: m_name(move(name))
{ {
// FIXME: This is not the smartest solution ever. Find something cleverer! // FIXME: This is not the smartest solution ever. Find something cleverer!
// The main issue we're working around here is that we don't want pointers into the bytecode stream to become invalidated // The main issue we're working around here is that we don't want pointers into the bytecode stream to become invalidated
// during code generation due to dynamic buffer resizing. Otherwise we could just use a Vector. // during code generation due to dynamic buffer resizing. Otherwise we could just use a Vector.
m_buffer_capacity = 64 * KiB; m_buffer_capacity = 1 * KiB;
m_buffer = (u8*)mmap(nullptr, m_buffer_capacity, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); m_buffer = (u8*)mmap(nullptr, m_buffer_capacity, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
VERIFY(m_buffer != MAP_FAILED); VERIFY(m_buffer != MAP_FAILED);
} }
Block::~Block() BasicBlock::~BasicBlock()
{ {
Bytecode::InstructionStreamIterator it(instruction_stream()); Bytecode::InstructionStreamIterator it(instruction_stream());
while (!it.at_end()) { while (!it.at_end()) {
@ -38,7 +39,7 @@ Block::~Block()
munmap(m_buffer, m_buffer_capacity); munmap(m_buffer, m_buffer_capacity);
} }
void Block::seal() void BasicBlock::seal()
{ {
// FIXME: mprotect the instruction stream as PROT_READ // FIXME: mprotect the instruction stream as PROT_READ
// This is currently not possible because instructions can have destructors (that clean up strings) // This is currently not possible because instructions can have destructors (that clean up strings)
@ -46,16 +47,18 @@ void Block::seal()
// It also doesn't work because instructions that have String members use RefPtr internally which must be in writable memory. // It also doesn't work because instructions that have String members use RefPtr internally which must be in writable memory.
} }
void Block::dump() const void BasicBlock::dump() const
{ {
Bytecode::InstructionStreamIterator it(instruction_stream()); Bytecode::InstructionStreamIterator it(instruction_stream());
if (!m_name.is_empty())
warnln("{}:", m_name);
while (!it.at_end()) { while (!it.at_end()) {
warnln("[{:4x}] {}", it.offset(), (*it).to_string()); warnln("[{:4x}] {}", it.offset(), (*it).to_string());
++it; ++it;
} }
} }
void Block::grow(size_t additional_size) void BasicBlock::grow(size_t additional_size)
{ {
m_buffer_size += additional_size; m_buffer_size += additional_size;
VERIFY(m_buffer_size <= m_buffer_capacity); VERIFY(m_buffer_size <= m_buffer_capacity);

View file

@ -8,6 +8,7 @@
#include <AK/Badge.h> #include <AK/Badge.h>
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <AK/String.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
namespace JS::Bytecode { namespace JS::Bytecode {
@ -37,30 +38,32 @@ private:
size_t m_offset { 0 }; size_t m_offset { 0 };
}; };
class Block { class BasicBlock {
public: public:
static NonnullOwnPtr<Block> create(); static NonnullOwnPtr<BasicBlock> create(String name);
~Block(); ~BasicBlock();
void seal(); void seal();
void dump() const; void dump() const;
ReadonlyBytes instruction_stream() const { return ReadonlyBytes { m_buffer, m_buffer_size }; } ReadonlyBytes instruction_stream() const { return ReadonlyBytes { m_buffer, m_buffer_size }; }
size_t register_count() const { return m_register_count; }
void set_register_count(Badge<Bytecode::Generator>, size_t count) { m_register_count = count; }
void* next_slot() { return m_buffer + m_buffer_size; } void* next_slot() { return m_buffer + m_buffer_size; }
void grow(size_t additional_size); void grow(size_t additional_size);
private: void terminate(Badge<Generator>) { m_is_terminated = true; }
Block(); bool is_terminated() const { return m_is_terminated; }
String const& name() const { return m_name; }
private:
BasicBlock(String name);
size_t m_register_count { 0 };
u8* m_buffer { nullptr }; u8* m_buffer { nullptr };
size_t m_buffer_capacity { 0 }; size_t m_buffer_capacity { 0 };
size_t m_buffer_size { 0 }; size_t m_buffer_size { 0 };
bool m_is_terminated { false };
String m_name;
}; };
} }

View file

@ -4,9 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/OwnPtr.h>
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Bytecode/Block.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h> #include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Instruction.h> #include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Bytecode/Register.h> #include <LibJS/Bytecode/Register.h>
@ -16,30 +15,30 @@ namespace JS::Bytecode {
Generator::Generator() Generator::Generator()
{ {
m_block = Block::create();
} }
Generator::~Generator() Generator::~Generator()
{ {
} }
OwnPtr<Block> Generator::generate(ASTNode const& node) ExecutionUnit Generator::generate(ASTNode const& node)
{ {
Generator generator; Generator generator;
generator.switch_to_basic_block(generator.make_block());
node.generate_bytecode(generator); node.generate_bytecode(generator);
generator.m_block->set_register_count({}, generator.m_next_register); return { move(generator.m_root_basic_blocks), generator.m_next_register };
generator.m_block->seal();
return move(generator.m_block);
} }
void Generator::grow(size_t additional_size) void Generator::grow(size_t additional_size)
{ {
m_block->grow(additional_size); VERIFY(m_current_basic_block);
m_current_basic_block->grow(additional_size);
} }
void* Generator::next_slot() void* Generator::next_slot()
{ {
return m_block->next_slot(); VERIFY(m_current_basic_block);
return m_current_basic_block->next_slot();
} }
Register Generator::allocate_register() Register Generator::allocate_register()
@ -48,19 +47,14 @@ Register Generator::allocate_register()
return Register { m_next_register++ }; return Register { m_next_register++ };
} }
Label Generator::make_label() const
{
return Label { m_block->instruction_stream().size() };
}
Label Generator::nearest_continuable_scope() const Label Generator::nearest_continuable_scope() const
{ {
return m_continuable_scopes.last(); return m_continuable_scopes.last();
} }
void Generator::begin_continuable_scope() void Generator::begin_continuable_scope(Label continue_target)
{ {
m_continuable_scopes.append(make_label()); m_continuable_scopes.append(continue_target);
} }
void Generator::end_continuable_scope() void Generator::end_continuable_scope()

View file

@ -6,43 +6,73 @@
#pragma once #pragma once
#include <AK/NonnullOwnPtrVector.h>
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <AK/SinglyLinkedList.h>
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Label.h> #include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h> #include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
namespace JS::Bytecode { namespace JS::Bytecode {
struct ExecutionUnit {
NonnullOwnPtrVector<BasicBlock> basic_blocks;
size_t number_of_registers { 0 };
};
class Generator { class Generator {
public: public:
static OwnPtr<Block> generate(ASTNode const&); static ExecutionUnit generate(ASTNode const&);
Register allocate_register(); Register allocate_register();
template<typename OpType, typename... Args> template<typename OpType, typename... Args>
OpType& emit(Args&&... args) OpType& emit(Args&&... args)
{ {
VERIFY(!is_current_block_terminated());
void* slot = next_slot(); void* slot = next_slot();
grow(sizeof(OpType)); grow(sizeof(OpType));
new (slot) OpType(forward<Args>(args)...); new (slot) OpType(forward<Args>(args)...);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot); return *static_cast<OpType*>(slot);
} }
template<typename OpType, typename... Args> template<typename OpType, typename... Args>
OpType& emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args) OpType& emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args)
{ {
VERIFY(!is_current_block_terminated());
void* slot = next_slot(); void* slot = next_slot();
grow(sizeof(OpType) + extra_register_slots * sizeof(Register)); grow(sizeof(OpType) + extra_register_slots * sizeof(Register));
new (slot) OpType(forward<Args>(args)...); new (slot) OpType(forward<Args>(args)...);
if constexpr (OpType::IsTerminator)
m_current_basic_block->terminate({});
return *static_cast<OpType*>(slot); return *static_cast<OpType*>(slot);
} }
Label make_label() const; void begin_continuable_scope(Label continue_target);
void begin_continuable_scope();
void end_continuable_scope(); void end_continuable_scope();
Label nearest_continuable_scope() const; [[nodiscard]] Label nearest_continuable_scope() const;
void switch_to_basic_block(BasicBlock& block)
{
m_current_basic_block = &block;
}
BasicBlock& make_block(String name = {})
{
if (name.is_empty())
name = String::number(m_next_block++);
m_root_basic_blocks.append(BasicBlock::create(name));
return m_root_basic_blocks.last();
}
bool is_current_block_terminated() const
{
return m_current_basic_block->is_terminated();
}
private: private:
Generator(); Generator();
@ -51,8 +81,11 @@ private:
void grow(size_t); void grow(size_t);
void* next_slot(); void* next_slot();
OwnPtr<Block> m_block; BasicBlock* m_current_basic_block { nullptr };
NonnullOwnPtrVector<BasicBlock> m_root_basic_blocks;
u32 m_next_register { 1 }; u32 m_next_register { 1 };
u32 m_next_block { 1 };
Vector<Label> m_continuable_scopes; Vector<Label> m_continuable_scopes;
}; };

View file

@ -36,9 +36,8 @@
O(PutById) \ O(PutById) \
O(GetById) \ O(GetById) \
O(Jump) \ O(Jump) \
O(JumpIfFalse) \ O(JumpConditional) \
O(JumpIfTrue) \ O(JumpNullish) \
O(JumpIfNotNullish) \
O(Call) \ O(Call) \
O(EnterScope) \ O(EnterScope) \
O(Return) \ O(Return) \
@ -61,6 +60,8 @@ namespace JS::Bytecode {
class Instruction { class Instruction {
public: public:
constexpr static bool IsTerminator = false;
enum class Type { enum class Type {
#define __BYTECODE_OP(op) \ #define __BYTECODE_OP(op) \
op, op,

View file

@ -5,7 +5,7 @@
*/ */
#include <AK/Debug.h> #include <AK/Debug.h>
#include <LibJS/Bytecode/Block.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Instruction.h> #include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
@ -33,9 +33,9 @@ Interpreter::~Interpreter()
s_current = nullptr; s_current = nullptr;
} }
Value Interpreter::run(Bytecode::Block const& block) Value Interpreter::run(ExecutionUnit const& execution_unit)
{ {
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run block {:p}", &block); dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &execution_unit);
CallFrame global_call_frame; CallFrame global_call_frame;
if (vm().call_stack().is_empty()) { if (vm().call_stack().is_empty()) {
@ -50,23 +50,37 @@ Value Interpreter::run(Bytecode::Block const& block)
VERIFY(!vm().exception()); VERIFY(!vm().exception());
} }
auto block = &execution_unit.basic_blocks.first();
m_register_windows.append(make<RegisterWindow>()); m_register_windows.append(make<RegisterWindow>());
registers().resize(block.register_count()); registers().resize(execution_unit.number_of_registers);
Bytecode::InstructionStreamIterator pc(block.instruction_stream()); for (;;) {
Bytecode::InstructionStreamIterator pc(block->instruction_stream());
bool will_jump = false;
bool will_return = false;
while (!pc.at_end()) { while (!pc.at_end()) {
auto& instruction = *pc; auto& instruction = *pc;
instruction.execute(*this); instruction.execute(*this);
if (m_pending_jump.has_value()) { if (m_pending_jump.has_value()) {
pc.jump(m_pending_jump.release_value()); block = m_pending_jump.release_value();
continue; will_jump = true;
}
if (!m_return_value.is_empty())
break; break;
}
if (!m_return_value.is_empty()) {
will_return = true;
break;
}
++pc; ++pc;
} }
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run block {:p}", &block); if (will_return)
break;
if (pc.at_end() && !will_jump)
break;
}
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &execution_unit);
if constexpr (JS_BYTECODE_DEBUG) { if constexpr (JS_BYTECODE_DEBUG) {
for (size_t i = 0; i < registers().size(); ++i) { for (size_t i = 0; i < registers().size(); ++i) {

View file

@ -6,7 +6,7 @@
#pragma once #pragma once
#include <AK/NonnullOwnPtrVector.h> #include "Generator.h"
#include <LibJS/Bytecode/Label.h> #include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h> #include <LibJS/Bytecode/Register.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
@ -28,12 +28,15 @@ public:
GlobalObject& global_object() { return m_global_object; } GlobalObject& global_object() { return m_global_object; }
VM& vm() { return m_vm; } VM& vm() { return m_vm; }
Value run(Bytecode::Block const&); Value run(Bytecode::ExecutionUnit const&);
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); } ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
Value& reg(Register const& r) { return registers()[r.index()]; } Value& reg(Register const& r) { return registers()[r.index()]; }
void jump(Label const& label) { m_pending_jump = label.address(); } void jump(Label const& label)
{
m_pending_jump = &label.block();
}
void do_return(Value return_value) { m_return_value = return_value; } void do_return(Value return_value) { m_return_value = return_value; }
private: private:
@ -42,7 +45,7 @@ private:
VM& m_vm; VM& m_vm;
GlobalObject& m_global_object; GlobalObject& m_global_object;
NonnullOwnPtrVector<RegisterWindow> m_register_windows; NonnullOwnPtrVector<RegisterWindow> m_register_windows;
Optional<size_t> m_pending_jump; Optional<BasicBlock const*> m_pending_jump;
Value m_return_value; Value m_return_value;
}; };

View file

@ -7,20 +7,21 @@
#pragma once #pragma once
#include <AK/Format.h> #include <AK/Format.h>
#include <LibJS/Bytecode/BasicBlock.h>
namespace JS::Bytecode { namespace JS::Bytecode {
class Label { class Label {
public: public:
explicit Label(size_t address) explicit Label(BasicBlock const& block)
: m_address(address) : m_block(block)
{ {
} }
size_t address() const { return m_address; } auto& block() const { return m_block; }
private: private:
size_t m_address { 0 }; BasicBlock const& m_block;
}; };
} }
@ -29,6 +30,6 @@ template<>
struct AK::Formatter<JS::Bytecode::Label> : AK::Formatter<FormatString> { struct AK::Formatter<JS::Bytecode::Label> : AK::Formatter<FormatString> {
void format(FormatBuilder& builder, JS::Bytecode::Label const& value) void format(FormatBuilder& builder, JS::Bytecode::Label const& value)
{ {
return AK::Formatter<FormatString>::format(builder, "@{:x}", value.address()); return AK::Formatter<FormatString>::format(builder, "@{}", value.block().name());
} }
}; };

View file

@ -174,31 +174,29 @@ void PutById::execute(Bytecode::Interpreter& interpreter) const
void Jump::execute(Bytecode::Interpreter& interpreter) const void Jump::execute(Bytecode::Interpreter& interpreter) const
{ {
interpreter.jump(*m_target); interpreter.jump(*m_true_target);
} }
void JumpIfFalse::execute(Bytecode::Interpreter& interpreter) const void JumpConditional::execute(Bytecode::Interpreter& interpreter) const
{ {
VERIFY(m_target.has_value()); VERIFY(m_true_target.has_value());
auto result = interpreter.accumulator(); VERIFY(m_false_target.has_value());
if (!result.to_boolean())
interpreter.jump(m_target.value());
}
void JumpIfTrue::execute(Bytecode::Interpreter& interpreter) const
{
VERIFY(m_target.has_value());
auto result = interpreter.accumulator(); auto result = interpreter.accumulator();
if (result.to_boolean()) if (result.to_boolean())
interpreter.jump(m_target.value()); interpreter.jump(m_true_target.value());
else
interpreter.jump(m_false_target.value());
} }
void JumpIfNotNullish::execute(Bytecode::Interpreter& interpreter) const void JumpNullish::execute(Bytecode::Interpreter& interpreter) const
{ {
VERIFY(m_target.has_value()); VERIFY(m_true_target.has_value());
VERIFY(m_false_target.has_value());
auto result = interpreter.accumulator(); auto result = interpreter.accumulator();
if (!result.is_nullish()) if (result.is_nullish())
interpreter.jump(m_target.value()); interpreter.jump(m_true_target.value());
else
interpreter.jump(m_false_target.value());
} }
void Call::execute(Bytecode::Interpreter& interpreter) const void Call::execute(Bytecode::Interpreter& interpreter) const
@ -321,28 +319,23 @@ String GetById::to_string() const
String Jump::to_string() const String Jump::to_string() const
{ {
return String::formatted("Jump {}", *m_target); if (m_true_target.has_value())
return String::formatted("Jump {}", *m_true_target);
return String::formatted("Jump <empty>");
} }
String JumpIfFalse::to_string() const String JumpConditional::to_string() const
{ {
if (m_target.has_value()) auto true_string = m_true_target.has_value() ? String::formatted("{}", *m_true_target) : "<empty>";
return String::formatted("JumpIfFalse target:{}", m_target.value()); auto false_string = m_false_target.has_value() ? String::formatted("{}", *m_false_target) : "<empty>";
return "JumpIfFalse target:<empty>"; return String::formatted("JumpConditional true:{} false:{}", true_string, false_string);
} }
String JumpIfTrue::to_string() const String JumpNullish::to_string() const
{ {
if (m_target.has_value()) auto true_string = m_true_target.has_value() ? String::formatted("{}", *m_true_target) : "<empty>";
return String::formatted("JumpIfTrue target:{}", m_target.value()); auto false_string = m_false_target.has_value() ? String::formatted("{}", *m_false_target) : "<empty>";
return "JumpIfTrue result:{}, target:<empty>"; return String::formatted("JumpNullish null:{} nonnull:{}", true_string, false_string);
}
String JumpIfNotNullish::to_string() const
{
if (m_target.has_value())
return String::formatted("JumpIfNotNullish target:{}", m_target.value());
return "JumpIfNotNullish target:<empty>";
} }
String Call::to_string() const String Call::to_string() const

View file

@ -268,31 +268,40 @@ private:
class Jump : public Instruction { class Jump : public Instruction {
public: public:
explicit Jump(Type type, Optional<Label> target = {}) constexpr static bool IsTerminator = true;
explicit Jump(Type type, Optional<Label> taken_target = {}, Optional<Label> nontaken_target = {})
: Instruction(type) : Instruction(type)
, m_target(move(target)) , m_true_target(move(taken_target))
, m_false_target(move(nontaken_target))
{ {
} }
explicit Jump(Optional<Label> target = {}) explicit Jump(Optional<Label> taken_target = {}, Optional<Label> nontaken_target = {})
: Instruction(Type::Jump) : Instruction(Type::Jump)
, m_target(move(target)) , m_true_target(move(taken_target))
, m_false_target(move(nontaken_target))
{ {
} }
void set_target(Optional<Label> target) { m_target = move(target); } void set_targets(Optional<Label> true_target, Optional<Label> false_target)
{
m_true_target = move(true_target);
m_false_target = move(false_target);
}
void execute(Bytecode::Interpreter&) const; void execute(Bytecode::Interpreter&) const;
String to_string() const; String to_string() const;
protected: protected:
Optional<Label> m_target; Optional<Label> m_true_target;
Optional<Label> m_false_target;
}; };
class JumpIfFalse final : public Jump { class JumpConditional final : public Jump {
public: public:
explicit JumpIfFalse(Optional<Label> target = {}) explicit JumpConditional(Optional<Label> true_target = {}, Optional<Label> false_target = {})
: Jump(Type::JumpIfFalse, move(target)) : Jump(Type::JumpConditional, move(true_target), move(false_target))
{ {
} }
@ -300,21 +309,10 @@ public:
String to_string() const; String to_string() const;
}; };
class JumpIfTrue : public Jump { class JumpNullish final : public Jump {
public: public:
explicit JumpIfTrue(Optional<Label> target = {}) explicit JumpNullish(Optional<Label> true_target = {}, Optional<Label> false_target = {})
: Jump(Type::JumpIfTrue, move(target)) : Jump(Type::JumpNullish, move(true_target), move(false_target))
{
}
void execute(Bytecode::Interpreter&) const;
String to_string() const;
};
class JumpIfNotNullish final : public Jump {
public:
explicit JumpIfNotNullish(Optional<Label> target = {})
: Jump(Type::JumpIfNotNullish, move(target))
{ {
} }
@ -364,6 +362,8 @@ private:
class Return final : public Instruction { class Return final : public Instruction {
public: public:
constexpr static bool IsTerminator = true;
Return() Return()
: Instruction(Type::Return) : Instruction(Type::Return)
{ {

View file

@ -1,7 +1,7 @@
set(SOURCES set(SOURCES
AST.cpp AST.cpp
Bytecode/ASTCodegen.cpp Bytecode/ASTCodegen.cpp
Bytecode/Block.cpp Bytecode/BasicBlock.cpp
Bytecode/Generator.cpp Bytecode/Generator.cpp
Bytecode/Instruction.cpp Bytecode/Instruction.cpp
Bytecode/Interpreter.cpp Bytecode/Interpreter.cpp

View file

@ -163,7 +163,7 @@ template<class T>
class Handle; class Handle;
namespace Bytecode { namespace Bytecode {
class Block; class BasicBlock;
class Generator; class Generator;
class Instruction; class Instruction;
class Interpreter; class Interpreter;

View file

@ -7,7 +7,7 @@
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Bytecode/Block.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h> #include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
@ -151,15 +151,15 @@ Value ScriptFunction::execute_function_body()
if (bytecode_interpreter) { if (bytecode_interpreter) {
prepare_arguments(); prepare_arguments();
if (!m_bytecode_block) { if (!m_bytecode_execution_unit.has_value()) {
m_bytecode_block = Bytecode::Generator::generate(m_body); m_bytecode_execution_unit = Bytecode::Generator::generate(m_body);
VERIFY(m_bytecode_block);
if constexpr (JS_BYTECODE_DEBUG) { if constexpr (JS_BYTECODE_DEBUG) {
dbgln("Compiled Bytecode::Block for function '{}':", m_name); dbgln("Compiled Bytecode::Block for function '{}':", m_name);
m_bytecode_block->dump(); for (auto& block : m_bytecode_execution_unit->basic_blocks)
block.dump();
} }
} }
return bytecode_interpreter->run(*m_bytecode_block); return bytecode_interpreter->run(*m_bytecode_execution_unit);
} else { } else {
OwnPtr<Interpreter> local_interpreter; OwnPtr<Interpreter> local_interpreter;
ast_interpreter = vm.interpreter_if_exists(); ast_interpreter = vm.interpreter_if_exists();

View file

@ -7,6 +7,7 @@
#pragma once #pragma once
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Bytecode/Generator.h>
#include <LibJS/Runtime/Function.h> #include <LibJS/Runtime/Function.h>
namespace JS { namespace JS {
@ -47,7 +48,7 @@ private:
FlyString m_name; FlyString m_name;
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<FunctionNode::Parameter> m_parameters; const Vector<FunctionNode::Parameter> m_parameters;
OwnPtr<Bytecode::Block> m_bytecode_block; Optional<Bytecode::ExecutionUnit> m_bytecode_execution_unit;
ScopeObject* m_parent_scope { nullptr }; ScopeObject* m_parent_scope { nullptr };
i32 m_function_length { 0 }; i32 m_function_length { 0 };
bool m_is_strict { false }; bool m_is_strict { false };

View file

@ -14,7 +14,7 @@
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibCore/StandardPaths.h> #include <LibCore/StandardPaths.h>
#include <LibJS/AST.h> #include <LibJS/AST.h>
#include <LibJS/Bytecode/Block.h> #include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Generator.h> #include <LibJS/Bytecode/Generator.h>
#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Console.h> #include <LibJS/Console.h>
@ -494,15 +494,15 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source
program->dump(0); program->dump(0);
if (s_dump_bytecode || s_run_bytecode) { if (s_dump_bytecode || s_run_bytecode) {
auto block = JS::Bytecode::Generator::generate(*program); auto unit = JS::Bytecode::Generator::generate(*program);
VERIFY(block); if (s_dump_bytecode) {
for (auto& block : unit.basic_blocks)
if (s_dump_bytecode) block.dump();
block->dump(); }
if (s_run_bytecode) { if (s_run_bytecode) {
JS::Bytecode::Interpreter bytecode_interpreter(interpreter.global_object()); JS::Bytecode::Interpreter bytecode_interpreter(interpreter.global_object());
bytecode_interpreter.run(*block); bytecode_interpreter.run(unit);
} }
return true; return true;