mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-20 00:08:55 +00:00
LibJS/Bytecode: Flatten bytecode to a contiguous representation
Instead of keeping bytecode as a set of disjoint basic blocks on the malloc heap, bytecode is now a contiguous sequence of bytes(!) The transformation happens at the end of Bytecode::Generator::generate() and the only really hairy part is rerouting jump labels. This required solving a few problems: - The interpreter execution loop had to change quite a bit, since we were storing BasicBlock pointers all over the place, and control transfer was done by redirecting the interpreter's current block. - Exception handlers & finalizers are now stored per-bytecode-range in a side table in Executable. - The interpreter now has a plain program counter instead of a stream iterator. This actually makes error stack generation a bit nicer since we just have to deal with a number instead of reaching into the iterator. This yields a 25% performance improvement on this microbenchmark: for (let i = 0; i < 1_000_000; ++i) { } But basically everything gets faster. :^)
This commit is contained in:
parent
c2d3d9d1d4
commit
f6aee2b9e8
Notes:
sideshowbarker
2024-07-17 02:22:23 +09:00
Author: https://github.com/awesomekling
Commit: f6aee2b9e8
Pull-request: https://github.com/SerenityOS/serenity/pull/24240
Reviewed-by: https://github.com/Hendiadyoin1
Reviewed-by: https://github.com/trflynn89 ✅
21 changed files with 392 additions and 172 deletions
|
@ -30,27 +30,6 @@ BasicBlock::~BasicBlock()
|
|||
}
|
||||
}
|
||||
|
||||
void BasicBlock::dump(Bytecode::Executable const& executable) const
|
||||
{
|
||||
Bytecode::InstructionStreamIterator it(instruction_stream());
|
||||
|
||||
if (!m_name.is_empty())
|
||||
warn("{}", m_name);
|
||||
if (m_handler || m_finalizer) {
|
||||
warn(" [");
|
||||
if (m_handler)
|
||||
warn(" Handler: {}", Label { *m_handler });
|
||||
if (m_finalizer)
|
||||
warn(" Finalizer: {}", Label { *m_finalizer });
|
||||
warn(" ]");
|
||||
}
|
||||
warnln(":");
|
||||
while (!it.at_end()) {
|
||||
warnln("[{:4x}] {}", it.offset(), (*it).to_byte_string(executable));
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void BasicBlock::grow(size_t additional_size)
|
||||
{
|
||||
m_buffer.grow_capacity(m_buffer.size() + additional_size);
|
||||
|
|
|
@ -27,7 +27,6 @@ public:
|
|||
static NonnullOwnPtr<BasicBlock> create(String name);
|
||||
~BasicBlock();
|
||||
|
||||
void dump(Executable const&) const;
|
||||
ReadonlyBytes instruction_stream() const { return m_buffer.span(); }
|
||||
u8* data() { return m_buffer.data(); }
|
||||
u8 const* data() const { return m_buffer.data(); }
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Bytecode/BasicBlock.h>
|
||||
#include <LibJS/Bytecode/Executable.h>
|
||||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Bytecode/RegexTable.h>
|
||||
#include <LibJS/SourceCode.h>
|
||||
|
||||
|
@ -14,6 +15,7 @@ namespace JS::Bytecode {
|
|||
JS_DEFINE_ALLOCATOR(Executable);
|
||||
|
||||
Executable::Executable(
|
||||
Vector<u8> bytecode,
|
||||
NonnullOwnPtr<IdentifierTable> identifier_table,
|
||||
NonnullOwnPtr<StringTable> string_table,
|
||||
NonnullOwnPtr<RegexTable> regex_table,
|
||||
|
@ -23,9 +25,8 @@ Executable::Executable(
|
|||
size_t number_of_global_variable_caches,
|
||||
size_t number_of_environment_variable_caches,
|
||||
size_t number_of_registers,
|
||||
Vector<NonnullOwnPtr<BasicBlock>> basic_blocks,
|
||||
bool is_strict_mode)
|
||||
: basic_blocks(move(basic_blocks))
|
||||
: bytecode(move(bytecode))
|
||||
, string_table(move(string_table))
|
||||
, identifier_table(move(identifier_table))
|
||||
, regex_table(move(regex_table))
|
||||
|
@ -44,8 +45,42 @@ Executable::~Executable() = default;
|
|||
void Executable::dump() const
|
||||
{
|
||||
warnln("\033[37;1mJS bytecode executable\033[0m \"{}\"", name);
|
||||
for (auto& block : basic_blocks)
|
||||
block->dump(*this);
|
||||
InstructionStreamIterator it(bytecode, this);
|
||||
|
||||
size_t basic_block_offset_index = 0;
|
||||
|
||||
while (!it.at_end()) {
|
||||
bool print_basic_block_marker = false;
|
||||
if (basic_block_offset_index < basic_block_start_offsets.size()
|
||||
&& it.offset() == basic_block_start_offsets[basic_block_offset_index]) {
|
||||
++basic_block_offset_index;
|
||||
print_basic_block_marker = true;
|
||||
}
|
||||
|
||||
StringBuilder builder;
|
||||
builder.appendff("[{:4x}] ", it.offset());
|
||||
if (print_basic_block_marker)
|
||||
builder.appendff("{:4}: ", basic_block_offset_index - 1);
|
||||
else
|
||||
builder.append(" "sv);
|
||||
builder.append((*it).to_byte_string(*this));
|
||||
|
||||
warnln("{}", builder.string_view());
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
if (!exception_handlers.is_empty()) {
|
||||
warnln("");
|
||||
warnln("Exception handlers:");
|
||||
for (auto& handlers : exception_handlers) {
|
||||
warnln(" from {:4x} to {:4x} handler {:4x} finalizer {:4x}",
|
||||
handlers.start_offset,
|
||||
handlers.end_offset,
|
||||
handlers.handler_offset.value_or(0),
|
||||
handlers.finalizer_offset.value_or(0));
|
||||
}
|
||||
}
|
||||
|
||||
warnln("");
|
||||
}
|
||||
|
@ -56,4 +91,27 @@ void Executable::visit_edges(Visitor& visitor)
|
|||
visitor.visit(constants);
|
||||
}
|
||||
|
||||
Optional<Executable::ExceptionHandlers const&> Executable::exception_handlers_for_offset(size_t offset) const
|
||||
{
|
||||
for (auto& handlers : exception_handlers) {
|
||||
if (handlers.start_offset <= offset && offset < handlers.end_offset)
|
||||
return handlers;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
UnrealizedSourceRange Executable::source_range_at(size_t offset) const
|
||||
{
|
||||
if (offset >= bytecode.size())
|
||||
return {};
|
||||
auto it = InstructionStreamIterator(bytecode.span().slice(offset), this);
|
||||
VERIFY(!it.at_end());
|
||||
auto& instruction = *it;
|
||||
return UnrealizedSourceRange {
|
||||
.source_code = source_code,
|
||||
.start_offset = instruction.source_record().source_start_offset,
|
||||
.end_offset = instruction.source_record().source_end_offset,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibJS/Heap/CellAllocator.h>
|
||||
#include <LibJS/Runtime/EnvironmentCoordinate.h>
|
||||
#include <LibJS/SourceRange.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
|
@ -45,6 +46,7 @@ class Executable final : public Cell {
|
|||
|
||||
public:
|
||||
Executable(
|
||||
Vector<u8> bytecode,
|
||||
NonnullOwnPtr<IdentifierTable>,
|
||||
NonnullOwnPtr<StringTable>,
|
||||
NonnullOwnPtr<RegexTable>,
|
||||
|
@ -54,16 +56,15 @@ public:
|
|||
size_t number_of_global_variable_caches,
|
||||
size_t number_of_environment_variable_caches,
|
||||
size_t number_of_registers,
|
||||
Vector<NonnullOwnPtr<BasicBlock>>,
|
||||
bool is_strict_mode);
|
||||
|
||||
virtual ~Executable() override;
|
||||
|
||||
DeprecatedFlyString name;
|
||||
Vector<u8> bytecode;
|
||||
Vector<PropertyLookupCache> property_lookup_caches;
|
||||
Vector<GlobalVariableCache> global_variable_caches;
|
||||
Vector<EnvironmentVariableCache> environment_variable_caches;
|
||||
Vector<NonnullOwnPtr<BasicBlock>> basic_blocks;
|
||||
NonnullOwnPtr<StringTable> string_table;
|
||||
NonnullOwnPtr<IdentifierTable> identifier_table;
|
||||
NonnullOwnPtr<RegexTable> regex_table;
|
||||
|
@ -73,6 +74,16 @@ public:
|
|||
size_t number_of_registers { 0 };
|
||||
bool is_strict_mode { false };
|
||||
|
||||
struct ExceptionHandlers {
|
||||
size_t start_offset;
|
||||
size_t end_offset;
|
||||
Optional<size_t> handler_offset;
|
||||
Optional<size_t> finalizer_offset;
|
||||
};
|
||||
|
||||
Vector<ExceptionHandlers> exception_handlers;
|
||||
Vector<size_t> basic_block_start_offsets;
|
||||
|
||||
ByteString const& get_string(StringTableIndex index) const { return string_table->get(index); }
|
||||
DeprecatedFlyString const& get_identifier(IdentifierTableIndex index) const { return identifier_table->get(index); }
|
||||
|
||||
|
@ -83,6 +94,10 @@ public:
|
|||
return get_identifier(*index);
|
||||
}
|
||||
|
||||
[[nodiscard]] Optional<ExceptionHandlers const&> exception_handlers_for_offset(size_t offset) const;
|
||||
|
||||
[[nodiscard]] UnrealizedSourceRange source_range_at(size_t offset) const;
|
||||
|
||||
void dump() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibJS/AST.h>
|
||||
#include <LibJS/Bytecode/BasicBlock.h>
|
||||
|
@ -73,7 +74,66 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTN
|
|||
else if (is<FunctionExpression>(node))
|
||||
is_strict_mode = static_cast<FunctionExpression const&>(node).is_strict_mode();
|
||||
|
||||
size_t size_needed = 0;
|
||||
for (auto& block : generator.m_root_basic_blocks) {
|
||||
size_needed += block->size();
|
||||
}
|
||||
|
||||
Vector<u8> bytecode;
|
||||
bytecode.ensure_capacity(size_needed);
|
||||
|
||||
Vector<size_t> basic_block_start_offsets;
|
||||
basic_block_start_offsets.ensure_capacity(generator.m_root_basic_blocks.size());
|
||||
|
||||
HashMap<BasicBlock const*, size_t> block_offsets;
|
||||
Vector<size_t> label_offsets;
|
||||
|
||||
struct UnlinkedExceptionHandlers {
|
||||
size_t start_offset;
|
||||
size_t end_offset;
|
||||
BasicBlock const* handler;
|
||||
BasicBlock const* finalizer;
|
||||
};
|
||||
Vector<UnlinkedExceptionHandlers> unlinked_exception_handlers;
|
||||
|
||||
for (auto& block : generator.m_root_basic_blocks) {
|
||||
basic_block_start_offsets.append(bytecode.size());
|
||||
if (block->handler() || block->finalizer()) {
|
||||
unlinked_exception_handlers.append({
|
||||
.start_offset = bytecode.size(),
|
||||
.end_offset = 0,
|
||||
.handler = block->handler(),
|
||||
.finalizer = block->finalizer(),
|
||||
});
|
||||
}
|
||||
|
||||
block_offsets.set(block.ptr(), bytecode.size());
|
||||
Bytecode::InstructionStreamIterator it(block->instruction_stream());
|
||||
while (!it.at_end()) {
|
||||
auto& instruction = const_cast<Instruction&>(*it);
|
||||
instruction.visit_labels([&](Label& label) {
|
||||
size_t label_offset = bytecode.size() + (bit_cast<FlatPtr>(&label) - bit_cast<FlatPtr>(&instruction));
|
||||
label_offsets.append(label_offset);
|
||||
});
|
||||
bytecode.append(reinterpret_cast<u8 const*>(&instruction), instruction.length());
|
||||
++it;
|
||||
}
|
||||
if (!block->is_terminated()) {
|
||||
Op::End end(generator.add_constant(js_undefined()));
|
||||
bytecode.append(reinterpret_cast<u8 const*>(&end), end.length());
|
||||
}
|
||||
if (block->handler() || block->finalizer()) {
|
||||
unlinked_exception_handlers.last().end_offset = bytecode.size();
|
||||
}
|
||||
}
|
||||
for (auto label_offset : label_offsets) {
|
||||
auto& label = *reinterpret_cast<Label*>(bytecode.data() + label_offset);
|
||||
auto* block = &label.block();
|
||||
label.set_address(block_offsets.get(block).value());
|
||||
}
|
||||
|
||||
auto executable = vm.heap().allocate_without_realm<Executable>(
|
||||
move(bytecode),
|
||||
move(generator.m_identifier_table),
|
||||
move(generator.m_string_table),
|
||||
move(generator.m_regex_table),
|
||||
|
@ -83,9 +143,25 @@ CodeGenerationErrorOr<NonnullGCPtr<Executable>> Generator::generate(VM& vm, ASTN
|
|||
generator.m_next_global_variable_cache,
|
||||
generator.m_next_environment_variable_cache,
|
||||
generator.m_next_register,
|
||||
move(generator.m_root_basic_blocks),
|
||||
is_strict_mode);
|
||||
|
||||
Vector<Executable::ExceptionHandlers> linked_exception_handlers;
|
||||
|
||||
for (auto& unlinked_handler : unlinked_exception_handlers) {
|
||||
auto start_offset = unlinked_handler.start_offset;
|
||||
auto end_offset = unlinked_handler.end_offset;
|
||||
auto handler_offset = unlinked_handler.handler ? block_offsets.get(unlinked_handler.handler).value() : Optional<size_t> {};
|
||||
auto finalizer_offset = unlinked_handler.finalizer ? block_offsets.get(unlinked_handler.finalizer).value() : Optional<size_t> {};
|
||||
linked_exception_handlers.append({ start_offset, end_offset, handler_offset, finalizer_offset });
|
||||
}
|
||||
|
||||
quick_sort(linked_exception_handlers, [](auto const& a, auto const& b) {
|
||||
return a.start_offset < b.start_offset;
|
||||
});
|
||||
|
||||
executable->exception_handlers = move(linked_exception_handlers);
|
||||
executable->basic_block_start_offsets = move(basic_block_start_offsets);
|
||||
|
||||
return executable;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,22 @@ void Instruction::destroy(Instruction& instruction)
|
|||
#undef __BYTECODE_OP
|
||||
}
|
||||
|
||||
void Instruction::visit_labels(Function<void(JS::Bytecode::Label&)> visitor)
|
||||
{
|
||||
#define __BYTECODE_OP(op) \
|
||||
case Type::op: \
|
||||
static_cast<Op::op&>(*this).visit_labels_impl(move(visitor)); \
|
||||
return;
|
||||
|
||||
switch (type()) {
|
||||
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
#undef __BYTECODE_OP
|
||||
}
|
||||
|
||||
UnrealizedSourceRange InstructionStreamIterator::source_range() const
|
||||
{
|
||||
VERIFY(m_executable);
|
||||
|
|
|
@ -137,6 +137,7 @@ public:
|
|||
size_t length() const { return m_length; }
|
||||
ByteString to_byte_string(Bytecode::Executable const&) const;
|
||||
ThrowCompletionOr<void> execute(Bytecode::Interpreter&) const;
|
||||
void visit_labels(Function<void(Label&)> visitor);
|
||||
static void destroy(Instruction&);
|
||||
|
||||
// FIXME: Find a better way to organize this information
|
||||
|
@ -150,6 +151,8 @@ protected:
|
|||
{
|
||||
}
|
||||
|
||||
void visit_labels_impl(Function<void(Label&)>) { }
|
||||
|
||||
private:
|
||||
SourceRecord m_source_record {};
|
||||
Type m_type {};
|
||||
|
|
|
@ -157,7 +157,7 @@ ALWAYS_INLINE Value Interpreter::get(Operand op) const
|
|||
case Operand::Type::Register:
|
||||
return reg(Register { op.index() });
|
||||
case Operand::Type::Local:
|
||||
return vm().running_execution_context().locals[op.index()];
|
||||
return m_locals.data()[op.index()];
|
||||
case Operand::Type::Constant:
|
||||
return current_executable().constants[op.index()];
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ ALWAYS_INLINE void Interpreter::set(Operand op, Value value)
|
|||
reg(Register { op.index() }) = value;
|
||||
return;
|
||||
case Operand::Type::Local:
|
||||
vm().running_execution_context().locals[op.index()] = value;
|
||||
m_locals.data()[op.index()] = value;
|
||||
return;
|
||||
case Operand::Type::Constant:
|
||||
VERIFY_NOT_REACHED();
|
||||
|
@ -244,7 +244,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Envir
|
|||
executable->dump();
|
||||
|
||||
// a. Set result to the result of evaluating script.
|
||||
auto result_or_error = run_executable(*executable, nullptr);
|
||||
auto result_or_error = run_executable(*executable, {}, {});
|
||||
if (result_or_error.value.is_error())
|
||||
result = result_or_error.value.release_error();
|
||||
else
|
||||
|
@ -304,79 +304,113 @@ ThrowCompletionOr<Value> Interpreter::run(SourceTextModule& module)
|
|||
return js_undefined();
|
||||
}
|
||||
|
||||
void Interpreter::run_bytecode()
|
||||
Interpreter::HandleExceptionResponse Interpreter::handle_exception(size_t& program_counter, Value exception)
|
||||
{
|
||||
auto* locals = vm().running_execution_context().locals.data();
|
||||
reg(Register::exception()) = exception;
|
||||
m_scheduled_jump = {};
|
||||
auto handlers = current_executable().exception_handlers_for_offset(program_counter);
|
||||
if (!handlers.has_value()) {
|
||||
return HandleExceptionResponse::ExitFromExecutable;
|
||||
}
|
||||
auto& handler = handlers->handler_offset;
|
||||
auto& finalizer = handlers->finalizer_offset;
|
||||
|
||||
VERIFY(!vm().running_execution_context().unwind_contexts.is_empty());
|
||||
auto& unwind_context = vm().running_execution_context().unwind_contexts.last();
|
||||
VERIFY(unwind_context.executable == m_current_executable);
|
||||
|
||||
if (handler.has_value()) {
|
||||
program_counter = handler.value();
|
||||
return HandleExceptionResponse::ContinueInThisExecutable;
|
||||
}
|
||||
if (finalizer.has_value()) {
|
||||
program_counter = finalizer.value();
|
||||
return HandleExceptionResponse::ContinueInThisExecutable;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
#define NEXT_INSTRUCTION() \
|
||||
program_counter += instruction.length(); \
|
||||
goto start;
|
||||
|
||||
void Interpreter::run_bytecode(size_t entry_point)
|
||||
{
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
auto* locals = running_execution_context.locals.data();
|
||||
auto& accumulator = this->accumulator();
|
||||
auto& executable = current_executable();
|
||||
auto const* bytecode = executable.bytecode.data();
|
||||
|
||||
size_t program_counter = entry_point;
|
||||
|
||||
TemporaryChange change(m_program_counter, Optional<size_t&>(program_counter));
|
||||
|
||||
for (;;) {
|
||||
start:
|
||||
auto pc = InstructionStreamIterator { m_current_block->instruction_stream(), m_current_executable };
|
||||
TemporaryChange temp_change { m_pc, Optional<InstructionStreamIterator&>(pc) };
|
||||
|
||||
bool will_return = false;
|
||||
bool will_yield = false;
|
||||
|
||||
ThrowCompletionOr<void> result;
|
||||
|
||||
while (!pc.at_end()) {
|
||||
auto& instruction = *pc;
|
||||
for (;;) {
|
||||
auto& instruction = *reinterpret_cast<Instruction const*>(&bytecode[program_counter]);
|
||||
|
||||
switch (instruction.type()) {
|
||||
case Instruction::Type::SetLocal:
|
||||
locals[static_cast<Op::SetLocal const&>(instruction).index()] = get(static_cast<Op::SetLocal const&>(instruction).src());
|
||||
break;
|
||||
NEXT_INSTRUCTION();
|
||||
case Instruction::Type::Mov:
|
||||
set(static_cast<Op::Mov const&>(instruction).dst(), get(static_cast<Op::Mov const&>(instruction).src()));
|
||||
break;
|
||||
NEXT_INSTRUCTION();
|
||||
case Instruction::Type::End:
|
||||
accumulator = get(static_cast<Op::End const&>(instruction).value());
|
||||
return;
|
||||
case Instruction::Type::Jump:
|
||||
m_current_block = &static_cast<Op::Jump const&>(instruction).target().block();
|
||||
program_counter = static_cast<Op::Jump const&>(instruction).target().address();
|
||||
goto start;
|
||||
case Instruction::Type::JumpIf: {
|
||||
auto& jump = static_cast<Op::JumpIf const&>(instruction);
|
||||
if (get(jump.condition()).to_boolean())
|
||||
m_current_block = &jump.true_target().block();
|
||||
program_counter = jump.true_target().address();
|
||||
else
|
||||
m_current_block = &jump.false_target().block();
|
||||
program_counter = jump.false_target().address();
|
||||
goto start;
|
||||
}
|
||||
case Instruction::Type::JumpNullish: {
|
||||
auto& jump = static_cast<Op::JumpNullish const&>(instruction);
|
||||
if (get(jump.condition()).is_nullish())
|
||||
m_current_block = &jump.true_target().block();
|
||||
program_counter = jump.true_target().address();
|
||||
else
|
||||
m_current_block = &jump.false_target().block();
|
||||
program_counter = jump.false_target().address();
|
||||
goto start;
|
||||
}
|
||||
case Instruction::Type::JumpUndefined: {
|
||||
auto& jump = static_cast<Op::JumpUndefined const&>(instruction);
|
||||
if (get(jump.condition()).is_undefined())
|
||||
m_current_block = &jump.true_target().block();
|
||||
program_counter = jump.true_target().address();
|
||||
else
|
||||
m_current_block = &jump.false_target().block();
|
||||
program_counter = jump.false_target().address();
|
||||
goto start;
|
||||
}
|
||||
case Instruction::Type::EnterUnwindContext:
|
||||
enter_unwind_context();
|
||||
m_current_block = &static_cast<Op::EnterUnwindContext const&>(instruction).entry_point().block();
|
||||
program_counter = static_cast<Op::EnterUnwindContext const&>(instruction).entry_point().address();
|
||||
goto start;
|
||||
case Instruction::Type::ContinuePendingUnwind: {
|
||||
if (auto exception = reg(Register::exception()); !exception.is_empty()) {
|
||||
result = throw_completion(exception);
|
||||
break;
|
||||
if (handle_exception(program_counter, exception) == HandleExceptionResponse::ExitFromExecutable)
|
||||
return;
|
||||
goto start;
|
||||
}
|
||||
if (!saved_return_value().is_empty()) {
|
||||
do_return(saved_return_value());
|
||||
break;
|
||||
goto may_return;
|
||||
}
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
auto const* old_scheduled_jump = running_execution_context.previously_scheduled_jumps.take_last();
|
||||
if (m_scheduled_jump) {
|
||||
m_current_block = exchange(m_scheduled_jump, nullptr);
|
||||
auto const old_scheduled_jump = running_execution_context.previously_scheduled_jumps.take_last();
|
||||
if (m_scheduled_jump.has_value()) {
|
||||
program_counter = m_scheduled_jump.value();
|
||||
m_scheduled_jump = {};
|
||||
} else {
|
||||
m_current_block = &static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target().block();
|
||||
program_counter = static_cast<Op::ContinuePendingUnwind const&>(instruction).resume_target().address();
|
||||
// set the scheduled jump to the old value if we continue
|
||||
// where we left it
|
||||
m_scheduled_jump = old_scheduled_jump;
|
||||
|
@ -384,42 +418,27 @@ void Interpreter::run_bytecode()
|
|||
goto start;
|
||||
}
|
||||
case Instruction::Type::ScheduleJump: {
|
||||
m_scheduled_jump = &static_cast<Op::ScheduleJump const&>(instruction).target().block();
|
||||
auto const* finalizer = m_current_block->finalizer();
|
||||
VERIFY(finalizer);
|
||||
m_current_block = finalizer;
|
||||
m_scheduled_jump = static_cast<Op::ScheduleJump const&>(instruction).target().address();
|
||||
auto finalizer = executable.exception_handlers_for_offset(program_counter).value().finalizer_offset;
|
||||
VERIFY(finalizer.has_value());
|
||||
program_counter = finalizer.value();
|
||||
goto start;
|
||||
}
|
||||
default:
|
||||
result = instruction.execute(*this);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.is_error()) [[unlikely]] {
|
||||
reg(Register::exception()) = *result.throw_completion().value();
|
||||
m_scheduled_jump = {};
|
||||
auto const* handler = m_current_block->handler();
|
||||
auto const* finalizer = m_current_block->finalizer();
|
||||
if (!handler && !finalizer)
|
||||
{
|
||||
auto result = instruction.execute(*this);
|
||||
|
||||
if (result.is_error()) {
|
||||
if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable)
|
||||
return;
|
||||
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
auto& unwind_context = running_execution_context.unwind_contexts.last();
|
||||
VERIFY(unwind_context.executable == m_current_executable);
|
||||
|
||||
if (handler) {
|
||||
m_current_block = handler;
|
||||
goto start;
|
||||
}
|
||||
if (finalizer) {
|
||||
m_current_block = finalizer;
|
||||
goto start;
|
||||
}
|
||||
// An unwind context with no handler or finalizer? We have nowhere to jump, and continuing on will make us crash on the next `Call` to a non-native function if there's an exception! So let's crash here instead.
|
||||
// If you run into this, you probably forgot to remove the current unwind_context somewhere.
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
may_return:
|
||||
if (!reg(Register::return_value()).is_empty()) {
|
||||
will_return = true;
|
||||
// Note: A `yield` statement will not go through a finally statement,
|
||||
|
@ -430,51 +449,55 @@ void Interpreter::run_bytecode()
|
|||
will_yield = (instruction.type() == Instruction::Type::Yield && static_cast<Op::Yield const&>(instruction).continuation().has_value()) || instruction.type() == Instruction::Type::Await;
|
||||
break;
|
||||
}
|
||||
++pc;
|
||||
|
||||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
||||
if (auto const* finalizer = m_current_block->finalizer(); finalizer && !will_yield) {
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
if (!will_yield) {
|
||||
if (auto handlers = executable.exception_handlers_for_offset(program_counter); handlers.has_value()) {
|
||||
if (auto finalizer = handlers.value().finalizer_offset; finalizer.has_value()) {
|
||||
VERIFY(!running_execution_context.unwind_contexts.is_empty());
|
||||
auto& unwind_context = running_execution_context.unwind_contexts.last();
|
||||
VERIFY(unwind_context.executable == m_current_executable);
|
||||
reg(Register::saved_return_value()) = reg(Register::return_value());
|
||||
reg(Register::return_value()) = {};
|
||||
m_current_block = finalizer;
|
||||
program_counter = finalizer.value();
|
||||
// the unwind_context will be pop'ed when entering the finally block
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pc.at_end())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (will_return)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& executable, BasicBlock const* entry_point)
|
||||
Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& executable, Optional<size_t> entry_point, Value initial_accumulator_value)
|
||||
{
|
||||
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable);
|
||||
|
||||
TemporaryChange restore_executable { m_current_executable, GCPtr { executable } };
|
||||
TemporaryChange restore_saved_jump { m_scheduled_jump, static_cast<BasicBlock const*>(nullptr) };
|
||||
TemporaryChange restore_saved_jump { m_scheduled_jump, Optional<size_t> {} };
|
||||
TemporaryChange restore_realm { m_realm, GCPtr { vm().current_realm() } };
|
||||
TemporaryChange restore_global_object { m_global_object, GCPtr { m_realm->global_object() } };
|
||||
TemporaryChange restore_global_declarative_environment { m_global_declarative_environment, GCPtr { m_realm->global_environment().declarative_record() } };
|
||||
|
||||
VERIFY(!vm().execution_context_stack().is_empty());
|
||||
|
||||
TemporaryChange restore_current_block { m_current_block, entry_point ?: executable.basic_blocks.first() };
|
||||
|
||||
auto& running_execution_context = vm().running_execution_context();
|
||||
if (running_execution_context.registers.size() < executable.number_of_registers)
|
||||
running_execution_context.registers.resize(executable.number_of_registers);
|
||||
|
||||
TemporaryChange restore_registers { m_registers, running_execution_context.registers.span() };
|
||||
TemporaryChange restore_locals { m_locals, running_execution_context.locals.span() };
|
||||
|
||||
reg(Register::accumulator()) = initial_accumulator_value;
|
||||
reg(Register::return_value()) = {};
|
||||
|
||||
running_execution_context.executable = &executable;
|
||||
|
||||
run_bytecode();
|
||||
run_bytecode(entry_point.value_or(0));
|
||||
|
||||
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &executable);
|
||||
|
||||
|
@ -514,7 +537,7 @@ void Interpreter::enter_unwind_context()
|
|||
m_current_executable,
|
||||
vm().running_execution_context().lexical_environment);
|
||||
running_execution_context.previously_scheduled_jumps.append(m_scheduled_jump);
|
||||
m_scheduled_jump = nullptr;
|
||||
m_scheduled_jump = {};
|
||||
}
|
||||
|
||||
void Interpreter::leave_unwind_context()
|
||||
|
@ -1465,9 +1488,9 @@ ThrowCompletionOr<void> Yield::execute_impl(Bytecode::Interpreter& interpreter)
|
|||
if (m_continuation_label.has_value())
|
||||
// FIXME: If we get a pointer, which is not accurately representable as a double
|
||||
// will cause this to explode
|
||||
object->define_direct_property("continuation", Value(static_cast<double>(reinterpret_cast<u64>(&m_continuation_label->block()))), JS::default_attributes);
|
||||
object->define_direct_property("continuation", Value(m_continuation_label->address()), JS::default_attributes);
|
||||
else
|
||||
object->define_direct_property("continuation", Value(0), JS::default_attributes);
|
||||
object->define_direct_property("continuation", js_null(), JS::default_attributes);
|
||||
|
||||
object->define_direct_property("isAwait", Value(false), JS::default_attributes);
|
||||
interpreter.do_return(object);
|
||||
|
@ -1482,7 +1505,7 @@ ThrowCompletionOr<void> Await::execute_impl(Bytecode::Interpreter& interpreter)
|
|||
object->define_direct_property("result", yielded_value, JS::default_attributes);
|
||||
// FIXME: If we get a pointer, which is not accurately representable as a double
|
||||
// will cause this to explode
|
||||
object->define_direct_property("continuation", Value(static_cast<double>(reinterpret_cast<u64>(&m_continuation_label.block()))), JS::default_attributes);
|
||||
object->define_direct_property("continuation", Value(m_continuation_label.address()), JS::default_attributes);
|
||||
object->define_direct_property("isAwait", Value(true), JS::default_attributes);
|
||||
interpreter.do_return(object);
|
||||
return {};
|
||||
|
@ -2074,8 +2097,8 @@ ByteString ContinuePendingUnwind::to_byte_string_impl(Bytecode::Executable const
|
|||
ByteString Yield::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
if (m_continuation_label.has_value()) {
|
||||
return ByteString::formatted("Yield continuation:@{}, {}",
|
||||
m_continuation_label->block().name(),
|
||||
return ByteString::formatted("Yield continuation:{}, {}",
|
||||
m_continuation_label.value(),
|
||||
format_operand("value"sv, m_value, executable));
|
||||
}
|
||||
return ByteString::formatted("Yield return {}",
|
||||
|
@ -2084,9 +2107,9 @@ ByteString Yield::to_byte_string_impl(Bytecode::Executable const& executable) co
|
|||
|
||||
ByteString Await::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return ByteString::formatted("Await {}, continuation:@{}",
|
||||
return ByteString::formatted("Await {}, continuation:{}",
|
||||
format_operand("argument"sv, m_argument, executable),
|
||||
m_continuation_label.block().name());
|
||||
m_continuation_label);
|
||||
}
|
||||
|
||||
ByteString GetByValue::to_byte_string_impl(Bytecode::Executable const& executable) const
|
||||
|
|
|
@ -33,9 +33,9 @@ public:
|
|||
ThrowCompletionOr<Value> run(Script&, JS::GCPtr<Environment> lexical_environment_override = nullptr);
|
||||
ThrowCompletionOr<Value> run(SourceTextModule&);
|
||||
|
||||
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr)
|
||||
ThrowCompletionOr<Value> run(Bytecode::Executable& executable, Optional<size_t> entry_point = {}, Value initial_accumulator_value = {})
|
||||
{
|
||||
auto result_and_return_register = run_executable(executable, entry_point);
|
||||
auto result_and_return_register = run_executable(executable, entry_point, initial_accumulator_value);
|
||||
return move(result_and_return_register.value);
|
||||
}
|
||||
|
||||
|
@ -43,17 +43,17 @@ public:
|
|||
ThrowCompletionOr<Value> value;
|
||||
Value return_register_value;
|
||||
};
|
||||
ResultAndReturnRegister run_executable(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point);
|
||||
ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional<size_t> entry_point, Value initial_accumulator_value = {});
|
||||
|
||||
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
|
||||
ALWAYS_INLINE Value& saved_return_value() { return reg(Register::saved_return_value()); }
|
||||
Value& reg(Register const& r)
|
||||
{
|
||||
return vm().running_execution_context().registers[r.index()];
|
||||
return m_registers.data()[r.index()];
|
||||
}
|
||||
Value reg(Register const& r) const
|
||||
{
|
||||
return vm().running_execution_context().registers[r.index()];
|
||||
return m_registers.data()[r.index()];
|
||||
}
|
||||
|
||||
[[nodiscard]] Value get(Operand) const;
|
||||
|
@ -75,23 +75,29 @@ public:
|
|||
|
||||
Executable& current_executable() { return *m_current_executable; }
|
||||
Executable const& current_executable() const { return *m_current_executable; }
|
||||
BasicBlock const& current_block() const { return *m_current_block; }
|
||||
Optional<InstructionStreamIterator const&> instruction_stream_iterator() const { return m_pc; }
|
||||
Optional<size_t> program_counter() const { return m_program_counter; }
|
||||
|
||||
Vector<Value>& registers() { return vm().running_execution_context().registers; }
|
||||
Vector<Value> const& registers() const { return vm().running_execution_context().registers; }
|
||||
|
||||
private:
|
||||
void run_bytecode();
|
||||
void run_bytecode(size_t entry_point);
|
||||
|
||||
enum class HandleExceptionResponse {
|
||||
ExitFromExecutable,
|
||||
ContinueInThisExecutable,
|
||||
};
|
||||
[[nodiscard]] HandleExceptionResponse handle_exception(size_t& program_counter, Value exception);
|
||||
|
||||
VM& m_vm;
|
||||
BasicBlock const* m_scheduled_jump { nullptr };
|
||||
Optional<size_t> m_scheduled_jump;
|
||||
GCPtr<Executable> m_current_executable { nullptr };
|
||||
BasicBlock const* m_current_block { nullptr };
|
||||
GCPtr<Realm> m_realm { nullptr };
|
||||
GCPtr<Object> m_global_object { nullptr };
|
||||
GCPtr<DeclarativeEnvironment> m_global_declarative_environment { nullptr };
|
||||
Optional<InstructionStreamIterator&> m_pc {};
|
||||
Optional<size_t&> m_program_counter;
|
||||
Span<Value> m_registers;
|
||||
Span<Value> m_locals;
|
||||
};
|
||||
|
||||
extern bool g_dump_bytecode;
|
||||
|
|
|
@ -18,18 +18,30 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
auto& block() const { return *m_block; }
|
||||
// Used while compiling.
|
||||
BasicBlock const& block() const { return *m_block; }
|
||||
|
||||
// Used after compiling.
|
||||
size_t address() const { return m_address; }
|
||||
|
||||
void set_address(size_t address) { m_address = address; }
|
||||
|
||||
private:
|
||||
union {
|
||||
// Relevant while compiling.
|
||||
BasicBlock const* m_block { nullptr };
|
||||
|
||||
// Relevant after compiling.
|
||||
size_t m_address;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<JS::Bytecode::Label> : AK::Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, JS::Bytecode::Label const& value)
|
||||
ErrorOr<void> format(FormatBuilder& builder, JS::Bytecode::Label const& label)
|
||||
{
|
||||
return AK::Formatter<FormatString>::format(builder, "@{}"sv, value.block().name());
|
||||
return AK::Formatter<FormatString>::format(builder, "@{:x}"sv, label.address());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1069,6 +1069,10 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_target);
|
||||
}
|
||||
|
||||
auto& target() const { return m_target; }
|
||||
|
||||
|
@ -1090,6 +1094,11 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_true_target);
|
||||
visitor(m_false_target);
|
||||
}
|
||||
|
||||
Operand condition() const { return m_condition; }
|
||||
auto& true_target() const { return m_true_target; }
|
||||
|
@ -1115,6 +1124,11 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_true_target);
|
||||
visitor(m_false_target);
|
||||
}
|
||||
|
||||
Operand condition() const { return m_condition; }
|
||||
auto& true_target() const { return m_true_target; }
|
||||
|
@ -1140,6 +1154,11 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_true_target);
|
||||
visitor(m_false_target);
|
||||
}
|
||||
|
||||
Operand condition() const { return m_condition; }
|
||||
auto& true_target() const { return m_true_target; }
|
||||
|
@ -1501,6 +1520,10 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_entry_point);
|
||||
}
|
||||
|
||||
auto& entry_point() const { return m_entry_point; }
|
||||
|
||||
|
@ -1531,6 +1554,10 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_target);
|
||||
}
|
||||
|
||||
private:
|
||||
Label m_target;
|
||||
|
@ -1570,6 +1597,10 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_resume_target);
|
||||
}
|
||||
|
||||
auto& resume_target() const { return m_resume_target; }
|
||||
|
||||
|
@ -1596,6 +1627,11 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
if (m_continuation_label.has_value())
|
||||
visitor(m_continuation_label.value());
|
||||
}
|
||||
|
||||
auto& continuation() const { return m_continuation_label; }
|
||||
Operand value() const { return m_value; }
|
||||
|
@ -1618,6 +1654,10 @@ public:
|
|||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||
void visit_labels_impl(Function<void(Label&)> visitor)
|
||||
{
|
||||
visitor(m_continuation_label);
|
||||
}
|
||||
|
||||
auto& continuation() const { return m_continuation_label; }
|
||||
Operand argument() const { return m_argument; }
|
||||
|
|
|
@ -693,7 +693,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
|
|||
executable->name = "eval"sv;
|
||||
if (Bytecode::g_dump_bytecode)
|
||||
executable->dump();
|
||||
auto result_or_error = vm.bytecode_interpreter().run_executable(*executable, nullptr);
|
||||
auto result_or_error = vm.bytecode_interpreter().run_executable(*executable, {});
|
||||
if (result_or_error.value.is_error())
|
||||
return result_or_error.value.release_error();
|
||||
|
||||
|
|
|
@ -161,12 +161,14 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
return value.is_empty() ? js_undefined() : value;
|
||||
};
|
||||
|
||||
auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
|
||||
auto generated_continuation = [&](Value value) -> Optional<size_t> {
|
||||
if (value.is_object()) {
|
||||
auto number_value = value.as_object().get_without_side_effects("continuation");
|
||||
return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
|
||||
if (number_value.is_null())
|
||||
return {};
|
||||
return static_cast<size_t>(number_value.as_double());
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
};
|
||||
|
||||
auto generated_is_await = [](Value value) -> bool {
|
||||
|
@ -182,16 +184,12 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
|
||||
auto& bytecode_interpreter = vm.bytecode_interpreter();
|
||||
|
||||
auto const* next_block = generated_continuation(m_previous_value);
|
||||
auto const continuation_address = generated_continuation(m_previous_value);
|
||||
|
||||
// We should never enter `execute` again after the generator is complete.
|
||||
VERIFY(next_block);
|
||||
VERIFY(continuation_address.has_value());
|
||||
|
||||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||
|
||||
bytecode_interpreter.accumulator() = completion_object;
|
||||
|
||||
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block);
|
||||
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), continuation_address, completion_object);
|
||||
|
||||
auto result_value = move(next_result.value);
|
||||
if (!result_value.is_throw_completion()) {
|
||||
|
@ -209,7 +207,7 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
}
|
||||
}
|
||||
|
||||
bool done = result_value.is_throw_completion() || generated_continuation(m_previous_value) == nullptr;
|
||||
bool done = result_value.is_throw_completion() || !generated_continuation(m_previous_value).has_value();
|
||||
if (!done) {
|
||||
// 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
|
||||
// 1. Let genContext be the running execution context.
|
||||
|
|
|
@ -384,7 +384,7 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
|
|||
|
||||
// Non-standard
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->instruction_stream_iterator = vm.bytecode_interpreter().instruction_stream_iterator();
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
|
||||
// 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
|
||||
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
|
||||
|
@ -455,7 +455,7 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ECMAScriptFunctionObject::internal_const
|
|||
|
||||
// Non-standard
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->instruction_stream_iterator = vm.bytecode_interpreter().instruction_stream_iterator();
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
|
||||
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
|
||||
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
|
||||
|
@ -726,7 +726,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
|
|||
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
|
||||
saved_registers[register_index] = {};
|
||||
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*m_default_parameter_bytecode_executables[default_parameter_index - 1], {});
|
||||
|
||||
for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index)
|
||||
running_execution_context.registers[register_index] = saved_registers[register_index];
|
||||
|
@ -1124,7 +1124,7 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro
|
|||
if (maybe_executable.is_error())
|
||||
result = maybe_executable.release_error();
|
||||
else
|
||||
result = vm.bytecode_interpreter().run_executable(*maybe_executable.value(), nullptr).value;
|
||||
result = vm.bytecode_interpreter().run_executable(*maybe_executable.value(), {}).value;
|
||||
}
|
||||
// b. Else,
|
||||
else {
|
||||
|
@ -1251,7 +1251,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
}
|
||||
}
|
||||
|
||||
auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, nullptr);
|
||||
auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, {});
|
||||
|
||||
if (result_and_frame.value.is_error())
|
||||
return result_and_frame.value.release_error();
|
||||
|
|
|
@ -36,7 +36,7 @@ NonnullOwnPtr<ExecutionContext> ExecutionContext::copy() const
|
|||
copy->lexical_environment = lexical_environment;
|
||||
copy->variable_environment = variable_environment;
|
||||
copy->private_environment = private_environment;
|
||||
copy->instruction_stream_iterator = instruction_stream_iterator;
|
||||
copy->program_counter = program_counter;
|
||||
copy->function_name = function_name;
|
||||
copy->this_value = this_value;
|
||||
copy->is_strict_mode = is_strict_mode;
|
||||
|
@ -60,8 +60,6 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(context_owner);
|
||||
visitor.visit(this_value);
|
||||
visitor.visit(executable);
|
||||
if (instruction_stream_iterator.has_value())
|
||||
visitor.visit(const_cast<Bytecode::Executable*>(instruction_stream_iterator.value().executable()));
|
||||
visitor.visit(function_name);
|
||||
visitor.visit(arguments);
|
||||
visitor.visit(locals);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <AK/DeprecatedFlyString.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibJS/Bytecode/BasicBlock.h>
|
||||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Module.h>
|
||||
#include <LibJS/Runtime/PrivateEnvironment.h>
|
||||
|
@ -51,7 +50,7 @@ public:
|
|||
// Non-standard: This points at something that owns this ExecutionContext, in case it needs to be protected from GC.
|
||||
GCPtr<Cell> context_owner;
|
||||
|
||||
Optional<Bytecode::InstructionStreamIterator> instruction_stream_iterator;
|
||||
Optional<size_t> program_counter;
|
||||
GCPtr<PrimitiveString> function_name;
|
||||
Value this_value;
|
||||
bool is_strict_mode { false };
|
||||
|
@ -78,7 +77,7 @@ public:
|
|||
Vector<Value> locals;
|
||||
Vector<Value> registers;
|
||||
Vector<Bytecode::UnwindInfo> unwind_contexts;
|
||||
Vector<Bytecode::BasicBlock const*> previously_scheduled_jumps;
|
||||
Vector<Optional<size_t>> previously_scheduled_jumps;
|
||||
Vector<GCPtr<Environment>> saved_lexical_environments;
|
||||
};
|
||||
|
||||
|
|
|
@ -88,12 +88,14 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
|
|||
return value.is_empty() ? js_undefined() : value;
|
||||
};
|
||||
|
||||
auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
|
||||
auto generated_continuation = [&](Value value) -> Optional<size_t> {
|
||||
if (value.is_object()) {
|
||||
auto number_value = value.as_object().get_without_side_effects("continuation");
|
||||
return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
|
||||
if (number_value.is_null())
|
||||
return {};
|
||||
return static_cast<u64>(number_value.as_double());
|
||||
}
|
||||
return nullptr;
|
||||
return {};
|
||||
};
|
||||
|
||||
auto& realm = *vm.current_realm();
|
||||
|
@ -103,16 +105,12 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
|
|||
|
||||
auto& bytecode_interpreter = vm.bytecode_interpreter();
|
||||
|
||||
auto const* next_block = generated_continuation(m_previous_value);
|
||||
auto const next_block = generated_continuation(m_previous_value);
|
||||
|
||||
// We should never enter `execute` again after the generator is complete.
|
||||
VERIFY(next_block);
|
||||
VERIFY(next_block.has_value());
|
||||
|
||||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||
|
||||
bytecode_interpreter.registers()[0] = completion_object;
|
||||
|
||||
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block);
|
||||
auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block, completion_object);
|
||||
|
||||
vm.pop_execution_context();
|
||||
|
||||
|
@ -123,7 +121,7 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
|
|||
return result_value;
|
||||
}
|
||||
m_previous_value = result_value.release_value();
|
||||
bool done = generated_continuation(m_previous_value) == nullptr;
|
||||
bool done = !generated_continuation(m_previous_value).has_value();
|
||||
|
||||
m_generator_state = done ? GeneratorState::Completed : GeneratorState::SuspendedYield;
|
||||
return create_iterator_result_object(vm, generated_value(m_previous_value), done);
|
||||
|
|
|
@ -147,7 +147,7 @@ ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, Read
|
|||
// 8. Perform any necessary implementation-defined initialization of calleeContext.
|
||||
callee_context->this_value = this_argument;
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->instruction_stream_iterator = vm.bytecode_interpreter().instruction_stream_iterator();
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
|
||||
callee_context->lexical_environment = caller_context.lexical_environment;
|
||||
callee_context->variable_environment = caller_context.variable_environment;
|
||||
|
@ -210,7 +210,7 @@ ThrowCompletionOr<NonnullGCPtr<Object>> NativeFunction::internal_construct(Reado
|
|||
|
||||
// 8. Perform any necessary implementation-defined initialization of calleeContext.
|
||||
callee_context->arguments.append(arguments_list.data(), arguments_list.size());
|
||||
callee_context->instruction_stream_iterator = vm.bytecode_interpreter().instruction_stream_iterator();
|
||||
callee_context->program_counter = vm.bytecode_interpreter().program_counter();
|
||||
|
||||
callee_context->lexical_environment = caller_context.lexical_environment;
|
||||
callee_context->variable_environment = caller_context.variable_environment;
|
||||
|
|
|
@ -180,7 +180,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, StringView source_tex
|
|||
else {
|
||||
auto executable = maybe_executable.release_value();
|
||||
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, nullptr);
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, {});
|
||||
if (result_and_return_register.value.is_error()) {
|
||||
result = result_and_return_register.value.release_error();
|
||||
} else {
|
||||
|
|
|
@ -299,7 +299,7 @@ ThrowCompletionOr<Value> VM::execute_ast_node(ASTNode const& node)
|
|||
for (size_t i = 0; i < saved_registers.size(); ++i)
|
||||
running_execution_context.registers[i] = {};
|
||||
|
||||
auto result_or_error = bytecode_interpreter().run_executable(*executable, nullptr);
|
||||
auto result_or_error = bytecode_interpreter().run_executable(*executable, {});
|
||||
|
||||
for (size_t i = 0; i < saved_registers.size(); ++i)
|
||||
running_execution_context.registers[i] = saved_registers[i];
|
||||
|
@ -705,8 +705,8 @@ void VM::dump_backtrace() const
|
|||
{
|
||||
for (ssize_t i = m_execution_context_stack.size() - 1; i >= 0; --i) {
|
||||
auto& frame = m_execution_context_stack[i];
|
||||
if (frame->instruction_stream_iterator.has_value() && frame->instruction_stream_iterator->source_code()) {
|
||||
auto source_range = frame->instruction_stream_iterator->source_range().realize();
|
||||
if (frame->program_counter.has_value()) {
|
||||
auto source_range = frame->executable->source_range_at(frame->program_counter.value()).realize();
|
||||
dbgln("-> {} @ {}:{},{}", frame->function_name ? frame->function_name->utf8_string() : ""_string, source_range.filename(), source_range.start.line, source_range.start.column);
|
||||
} else {
|
||||
dbgln("-> {}", frame->function_name ? frame->function_name->utf8_string() : ""_string);
|
||||
|
@ -971,7 +971,7 @@ void VM::load_imported_module(ImportedModuleReferrer referrer, ModuleRequest con
|
|||
void VM::push_execution_context(ExecutionContext& context)
|
||||
{
|
||||
if (!m_execution_context_stack.is_empty())
|
||||
m_execution_context_stack.last()->instruction_stream_iterator = bytecode_interpreter().instruction_stream_iterator();
|
||||
m_execution_context_stack.last()->program_counter = bytecode_interpreter().program_counter();
|
||||
m_execution_context_stack.append(&context);
|
||||
}
|
||||
|
||||
|
@ -995,10 +995,10 @@ static Optional<UnrealizedSourceRange> get_source_range(ExecutionContext const*
|
|||
if (!context->executable)
|
||||
return {};
|
||||
|
||||
// Interpreter frame
|
||||
if (context->instruction_stream_iterator.has_value())
|
||||
return context->instruction_stream_iterator->source_range();
|
||||
if (!context->program_counter.has_value())
|
||||
return {};
|
||||
|
||||
return context->executable->source_range_at(context->program_counter.value());
|
||||
}
|
||||
|
||||
Vector<StackTraceElement> VM::stack_trace() const
|
||||
|
|
|
@ -717,7 +717,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GCPtr<PromiseCa
|
|||
else {
|
||||
auto executable = maybe_executable.release_value();
|
||||
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, nullptr);
|
||||
auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, {});
|
||||
if (result_and_return_register.value.is_error()) {
|
||||
result = result_and_return_register.value.release_error();
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue