mirror of
https://github.com/RGBCube/serenity
synced 2025-05-24 06:25:06 +00:00
LibJS: Add bytecode instruction handles
This change removes the mmap inside of Block in favor of a growing vector of bytes. This is favorable for two reasons: - We don't take more space than we need - There is no limit to the growth of the vector (previously, if the Block overstepped its 64kb boundary, it would just crash) However, if that vector happens to resize, any pointer pointing into that vector would become invalid. To avoid this, this commit adds an InstructionHandle<Op> class which just stores a block and an offset into that block.
This commit is contained in:
parent
83be39c91a
commit
a01bd35c67
8 changed files with 81 additions and 79 deletions
|
@ -119,16 +119,16 @@ void LogicalExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
{
|
{
|
||||||
m_lhs->generate_bytecode(generator);
|
m_lhs->generate_bytecode(generator);
|
||||||
|
|
||||||
Bytecode::Op::Jump* test_instr;
|
Bytecode::InstructionHandle<Bytecode::Op::Jump> test_instr;
|
||||||
switch (m_op) {
|
switch (m_op) {
|
||||||
case LogicalOp::And:
|
case LogicalOp::And:
|
||||||
test_instr = &generator.emit<Bytecode::Op::JumpIfFalse>();
|
test_instr = generator.emit<Bytecode::Op::JumpIfFalse>();
|
||||||
break;
|
break;
|
||||||
case LogicalOp::Or:
|
case LogicalOp::Or:
|
||||||
test_instr = &generator.emit<Bytecode::Op::JumpIfTrue>();
|
test_instr = generator.emit<Bytecode::Op::JumpIfTrue>();
|
||||||
break;
|
break;
|
||||||
case LogicalOp::NullishCoalescing:
|
case LogicalOp::NullishCoalescing:
|
||||||
test_instr = &generator.emit<Bytecode::Op::JumpIfNotNullish>();
|
test_instr = generator.emit<Bytecode::Op::JumpIfNotNullish>();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
|
@ -285,10 +285,10 @@ void WhileStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
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);
|
||||||
m_test->generate_bytecode(generator);
|
m_test->generate_bytecode(generator);
|
||||||
auto& test_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
auto test_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
||||||
m_body->generate_bytecode(generator);
|
m_body->generate_bytecode(generator);
|
||||||
generator.emit<Bytecode::Op::Jump>(test_label);
|
generator.emit<Bytecode::Op::Jump>(test_label);
|
||||||
test_jump.set_target(generator.make_label());
|
test_jump->set_target(generator.make_label());
|
||||||
generator.end_continuable_scope();
|
generator.end_continuable_scope();
|
||||||
generator.emit<Bytecode::Op::Load>(result_reg);
|
generator.emit<Bytecode::Op::Load>(result_reg);
|
||||||
}
|
}
|
||||||
|
@ -309,7 +309,7 @@ void DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
|
|
||||||
void ForStatement::generate_bytecode(Bytecode::Generator& generator) const
|
void ForStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
{
|
{
|
||||||
Bytecode::Op::Jump* test_jump { nullptr };
|
Bytecode::InstructionHandle<Bytecode::Op::Jump> test_jump;
|
||||||
|
|
||||||
if (m_init)
|
if (m_init)
|
||||||
m_init->generate_bytecode(generator);
|
m_init->generate_bytecode(generator);
|
||||||
|
@ -321,7 +321,7 @@ void ForStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
generator.emit<Bytecode::Op::Store>(result_reg);
|
generator.emit<Bytecode::Op::Store>(result_reg);
|
||||||
if (m_test) {
|
if (m_test) {
|
||||||
m_test->generate_bytecode(generator);
|
m_test->generate_bytecode(generator);
|
||||||
test_jump = &generator.emit<Bytecode::Op::JumpIfFalse>();
|
test_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_body->generate_bytecode(generator);
|
m_body->generate_bytecode(generator);
|
||||||
|
@ -387,16 +387,16 @@ 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);
|
m_predicate->generate_bytecode(generator);
|
||||||
auto& else_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
auto else_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
||||||
|
|
||||||
m_consequent->generate_bytecode(generator);
|
m_consequent->generate_bytecode(generator);
|
||||||
if (m_alternate) {
|
if (m_alternate) {
|
||||||
auto& if_jump = generator.emit<Bytecode::Op::Jump>();
|
auto if_jump = generator.emit<Bytecode::Op::Jump>();
|
||||||
else_jump.set_target(generator.make_label());
|
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_jump->set_target(generator.make_label());
|
||||||
} else {
|
} else {
|
||||||
else_jump.set_target(generator.make_label());
|
else_jump->set_target(generator.make_label());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,15 +412,15 @@ void DebuggerStatement::generate_bytecode(Bytecode::Generator&) const
|
||||||
void ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const
|
void ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
{
|
{
|
||||||
m_test->generate_bytecode(generator);
|
m_test->generate_bytecode(generator);
|
||||||
auto& alternate_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
auto alternate_jump = generator.emit<Bytecode::Op::JumpIfFalse>();
|
||||||
|
|
||||||
m_consequent->generate_bytecode(generator);
|
m_consequent->generate_bytecode(generator);
|
||||||
auto& end_jump = generator.emit<Bytecode::Op::Jump>();
|
auto end_jump = generator.emit<Bytecode::Op::Jump>();
|
||||||
|
|
||||||
alternate_jump.set_target(generator.make_label());
|
alternate_jump->set_target(generator.make_label());
|
||||||
m_alternate->generate_bytecode(generator);
|
m_alternate->generate_bytecode(generator);
|
||||||
|
|
||||||
end_jump.set_target(generator.make_label());
|
end_jump->set_target(generator.make_label());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const
|
void SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||||
|
|
|
@ -4,10 +4,8 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/String.h>
|
|
||||||
#include <LibJS/Bytecode/Block.h>
|
#include <LibJS/Bytecode/Block.h>
|
||||||
#include <LibJS/Bytecode/Op.h>
|
#include <LibJS/Bytecode/Op.h>
|
||||||
#include <sys/mman.h>
|
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
|
||||||
|
@ -16,16 +14,6 @@ NonnullOwnPtr<Block> Block::create()
|
||||||
return adopt_own(*new Block);
|
return adopt_own(*new Block);
|
||||||
}
|
}
|
||||||
|
|
||||||
Block::Block()
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
// during code generation due to dynamic buffer resizing. Otherwise we could just use a Vector.
|
|
||||||
m_buffer_capacity = 64 * KiB;
|
|
||||||
m_buffer = (u8*)mmap(nullptr, m_buffer_capacity, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
|
|
||||||
VERIFY(m_buffer != MAP_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
Block::~Block()
|
Block::~Block()
|
||||||
{
|
{
|
||||||
Bytecode::InstructionStreamIterator it(instruction_stream());
|
Bytecode::InstructionStreamIterator it(instruction_stream());
|
||||||
|
@ -34,16 +22,6 @@ Block::~Block()
|
||||||
++it;
|
++it;
|
||||||
Instruction::destroy(const_cast<Instruction&>(to_destroy));
|
Instruction::destroy(const_cast<Instruction&>(to_destroy));
|
||||||
}
|
}
|
||||||
|
|
||||||
munmap(m_buffer, m_buffer_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Block::seal()
|
|
||||||
{
|
|
||||||
// FIXME: mprotect the instruction stream as PROT_READ
|
|
||||||
// This is currently not possible because instructions can have destructors (that clean up strings)
|
|
||||||
// Instructions should instead be destructor-less and refer to strings in a string table on the Bytecode::Block.
|
|
||||||
// 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 Block::dump() const
|
||||||
|
@ -55,12 +33,6 @@ void Block::dump() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Block::grow(size_t additional_size)
|
|
||||||
{
|
|
||||||
m_buffer_size += additional_size;
|
|
||||||
VERIFY(m_buffer_size <= m_buffer_capacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InstructionStreamIterator::operator++()
|
void InstructionStreamIterator::operator++()
|
||||||
{
|
{
|
||||||
m_offset += dereference().length();
|
m_offset += dereference().length();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <AK/Badge.h>
|
#include <AK/Badge.h>
|
||||||
#include <AK/NonnullOwnPtrVector.h>
|
#include <AK/NonnullOwnPtrVector.h>
|
||||||
|
#include <LibJS/Bytecode/Register.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
@ -42,25 +43,20 @@ public:
|
||||||
static NonnullOwnPtr<Block> create();
|
static NonnullOwnPtr<Block> create();
|
||||||
~Block();
|
~Block();
|
||||||
|
|
||||||
void seal();
|
|
||||||
|
|
||||||
void dump() const;
|
void dump() const;
|
||||||
ReadonlyBytes instruction_stream() const { return ReadonlyBytes { m_buffer, m_buffer_size }; }
|
ReadonlyBytes instruction_stream() const { return m_buffer.span(); }
|
||||||
|
|
||||||
size_t register_count() const { return m_register_count; }
|
size_t register_count() const { return m_register_count; }
|
||||||
|
|
||||||
void set_register_count(Badge<Bytecode::Generator>, size_t count) { m_register_count = count; }
|
void set_register_count(Badge<Bytecode::Generator>, size_t count) { m_register_count = count; }
|
||||||
|
|
||||||
void* next_slot() { return m_buffer + m_buffer_size; }
|
Vector<u8>& buffer() { return m_buffer; }
|
||||||
void grow(size_t additional_size);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Block();
|
Block() = default;
|
||||||
|
|
||||||
size_t m_register_count { 0 };
|
size_t m_register_count { 0 };
|
||||||
u8* m_buffer { nullptr };
|
Vector<u8> m_buffer;
|
||||||
size_t m_buffer_capacity { 0 };
|
|
||||||
size_t m_buffer_size { 0 };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#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>
|
||||||
#include <LibJS/Forward.h>
|
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
|
||||||
|
@ -28,20 +27,9 @@ OwnPtr<Block> Generator::generate(ASTNode const& node)
|
||||||
Generator generator;
|
Generator generator;
|
||||||
node.generate_bytecode(generator);
|
node.generate_bytecode(generator);
|
||||||
generator.m_block->set_register_count({}, generator.m_next_register);
|
generator.m_block->set_register_count({}, generator.m_next_register);
|
||||||
generator.m_block->seal();
|
|
||||||
return move(generator.m_block);
|
return move(generator.m_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Generator::grow(size_t additional_size)
|
|
||||||
{
|
|
||||||
m_block->grow(additional_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* Generator::next_slot()
|
|
||||||
{
|
|
||||||
return m_block->next_slot();
|
|
||||||
}
|
|
||||||
|
|
||||||
Register Generator::allocate_register()
|
Register Generator::allocate_register()
|
||||||
{
|
{
|
||||||
VERIFY(m_next_register != NumericLimits<u32>::max());
|
VERIFY(m_next_register != NumericLimits<u32>::max());
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
|
#include <LibJS/Bytecode/Block.h>
|
||||||
#include <LibJS/Bytecode/Label.h>
|
#include <LibJS/Bytecode/Label.h>
|
||||||
#include <LibJS/Bytecode/Register.h>
|
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
|
||||||
namespace JS::Bytecode {
|
namespace JS::Bytecode {
|
||||||
|
@ -20,21 +20,15 @@ public:
|
||||||
Register allocate_register();
|
Register allocate_register();
|
||||||
|
|
||||||
template<typename OpType, typename... Args>
|
template<typename OpType, typename... Args>
|
||||||
OpType& emit(Args&&... args)
|
InstructionHandle<OpType> emit(Args&&... args)
|
||||||
{
|
{
|
||||||
void* slot = next_slot();
|
return make_instruction<OpType>(0, forward<Args>(args)...);
|
||||||
grow(sizeof(OpType));
|
|
||||||
new (slot) OpType(forward<Args>(args)...);
|
|
||||||
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)
|
InstructionHandle<OpType> emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args)
|
||||||
{
|
{
|
||||||
void* slot = next_slot();
|
return make_instruction<OpType>(extra_register_slots, forward<Args>(args)...);
|
||||||
grow(sizeof(OpType) + extra_register_slots * sizeof(Register));
|
|
||||||
new (slot) OpType(forward<Args>(args)...);
|
|
||||||
return *static_cast<OpType*>(slot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label make_label() const;
|
Label make_label() const;
|
||||||
|
@ -48,8 +42,15 @@ private:
|
||||||
Generator();
|
Generator();
|
||||||
~Generator();
|
~Generator();
|
||||||
|
|
||||||
void grow(size_t);
|
template<typename OpType, typename... Args>
|
||||||
void* next_slot();
|
InstructionHandle<OpType> make_instruction(size_t extra_register_slots, Args&&... args)
|
||||||
|
{
|
||||||
|
auto& buffer = m_block->buffer();
|
||||||
|
auto offset = buffer.size();
|
||||||
|
buffer.resize(buffer.size() + sizeof(OpType) + extra_register_slots * sizeof(Register));
|
||||||
|
new (buffer.data() + offset) OpType(forward<Args>(args)...);
|
||||||
|
return InstructionHandle<OpType>(offset, m_block);
|
||||||
|
}
|
||||||
|
|
||||||
OwnPtr<Block> m_block;
|
OwnPtr<Block> m_block;
|
||||||
u32 m_next_register { 1 };
|
u32 m_next_register { 1 };
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Bytecode/Block.h>
|
||||||
#include <LibJS/Bytecode/Instruction.h>
|
#include <LibJS/Bytecode/Instruction.h>
|
||||||
#include <LibJS/Bytecode/Op.h>
|
#include <LibJS/Bytecode/Op.h>
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/Forward.h>
|
#include <AK/Forward.h>
|
||||||
|
#include <LibJS/Bytecode/Block.h>
|
||||||
#include <LibJS/Forward.h>
|
#include <LibJS/Forward.h>
|
||||||
|
|
||||||
#define ENUMERATE_BYTECODE_OPS(O) \
|
#define ENUMERATE_BYTECODE_OPS(O) \
|
||||||
|
@ -83,4 +84,45 @@ private:
|
||||||
Type m_type {};
|
Type m_type {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename OpType>
|
||||||
|
class InstructionHandle {
|
||||||
|
public:
|
||||||
|
InstructionHandle() = default;
|
||||||
|
|
||||||
|
InstructionHandle(size_t offset, Block* block)
|
||||||
|
: m_offset(offset)
|
||||||
|
, m_block(block)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
OpType* operator->() const
|
||||||
|
{
|
||||||
|
VERIFY(m_block);
|
||||||
|
return reinterpret_cast<OpType*>(m_block->buffer().data() + m_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpType& operator*() const
|
||||||
|
{
|
||||||
|
VERIFY(m_block);
|
||||||
|
return *reinterpret_cast<OpType*>(m_block->buffer().data() + m_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
InstructionHandle<OpType>& operator=(InstructionHandle<T> const& other) requires(IsBaseOf<OpType, T>)
|
||||||
|
{
|
||||||
|
m_offset = other.offset();
|
||||||
|
m_block = other.block();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset() const { return m_offset; }
|
||||||
|
Block* block() const { return m_block; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Block;
|
||||||
|
|
||||||
|
size_t m_offset { 0 };
|
||||||
|
Block* m_block { nullptr };
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,8 @@ namespace Bytecode {
|
||||||
class Block;
|
class Block;
|
||||||
class Generator;
|
class Generator;
|
||||||
class Instruction;
|
class Instruction;
|
||||||
|
template<typename OpType>
|
||||||
|
class InstructionHandle;
|
||||||
class Interpreter;
|
class Interpreter;
|
||||||
class Register;
|
class Register;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue