diff --git a/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp b/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp index 8a30a111d45..0e78e963604 100644 --- a/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp +++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.cpp @@ -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); diff --git a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h index 61252a3eb06..07d401d96ce 100644 --- a/Userland/Libraries/LibJS/Bytecode/BasicBlock.h +++ b/Userland/Libraries/LibJS/Bytecode/BasicBlock.h @@ -27,7 +27,6 @@ public: static NonnullOwnPtr 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(); } diff --git a/Userland/Libraries/LibJS/Bytecode/Executable.cpp b/Userland/Libraries/LibJS/Bytecode/Executable.cpp index 59b9a1930d6..a2d90bf5e4b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Executable.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Executable.cpp @@ -1,11 +1,12 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include +#include #include #include @@ -14,6 +15,7 @@ namespace JS::Bytecode { JS_DEFINE_ALLOCATOR(Executable); Executable::Executable( + Vector bytecode, NonnullOwnPtr identifier_table, NonnullOwnPtr string_table, NonnullOwnPtr 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> 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::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, + }; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Executable.h b/Userland/Libraries/LibJS/Bytecode/Executable.h index 064ac0b50bc..ecdfc417c5a 100644 --- a/Userland/Libraries/LibJS/Bytecode/Executable.h +++ b/Userland/Libraries/LibJS/Bytecode/Executable.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace JS::Bytecode { @@ -45,6 +46,7 @@ class Executable final : public Cell { public: Executable( + Vector bytecode, NonnullOwnPtr, NonnullOwnPtr, NonnullOwnPtr, @@ -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>, bool is_strict_mode); virtual ~Executable() override; DeprecatedFlyString name; + Vector bytecode; Vector property_lookup_caches; Vector global_variable_caches; Vector environment_variable_caches; - Vector> basic_blocks; NonnullOwnPtr string_table; NonnullOwnPtr identifier_table; NonnullOwnPtr 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 handler_offset; + Optional finalizer_offset; + }; + + Vector exception_handlers; + Vector 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 exception_handlers_for_offset(size_t offset) const; + + [[nodiscard]] UnrealizedSourceRange source_range_at(size_t offset) const; + void dump() const; private: diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 89ccaec26b8..00c0d3d472b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -73,7 +74,66 @@ CodeGenerationErrorOr> Generator::generate(VM& vm, ASTN else if (is(node)) is_strict_mode = static_cast(node).is_strict_mode(); + size_t size_needed = 0; + for (auto& block : generator.m_root_basic_blocks) { + size_needed += block->size(); + } + + Vector bytecode; + bytecode.ensure_capacity(size_needed); + + Vector basic_block_start_offsets; + basic_block_start_offsets.ensure_capacity(generator.m_root_basic_blocks.size()); + + HashMap block_offsets; + Vector label_offsets; + + struct UnlinkedExceptionHandlers { + size_t start_offset; + size_t end_offset; + BasicBlock const* handler; + BasicBlock const* finalizer; + }; + Vector 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(*it); + instruction.visit_labels([&](Label& label) { + size_t label_offset = bytecode.size() + (bit_cast(&label) - bit_cast(&instruction)); + label_offsets.append(label_offset); + }); + bytecode.append(reinterpret_cast(&instruction), instruction.length()); + ++it; + } + if (!block->is_terminated()) { + Op::End end(generator.add_constant(js_undefined())); + bytecode.append(reinterpret_cast(&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(bytecode.data() + label_offset); + auto* block = &label.block(); + label.set_address(block_offsets.get(block).value()); + } + auto executable = vm.heap().allocate_without_realm( + move(bytecode), move(generator.m_identifier_table), move(generator.m_string_table), move(generator.m_regex_table), @@ -83,9 +143,25 @@ CodeGenerationErrorOr> 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 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 {}; + auto finalizer_offset = unlinked_handler.finalizer ? block_offsets.get(unlinked_handler.finalizer).value() : Optional {}; + 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; } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.cpp b/Userland/Libraries/LibJS/Bytecode/Instruction.cpp index 58e44ba1bb7..aa3d12c5308 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.cpp @@ -26,6 +26,22 @@ void Instruction::destroy(Instruction& instruction) #undef __BYTECODE_OP } +void Instruction::visit_labels(Function visitor) +{ +#define __BYTECODE_OP(op) \ + case Type::op: \ + static_cast(*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); diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 5efca24e15a..5d48a661255 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -137,6 +137,7 @@ public: size_t length() const { return m_length; } ByteString to_byte_string(Bytecode::Executable const&) const; ThrowCompletionOr execute(Bytecode::Interpreter&) const; + void visit_labels(Function visitor); static void destroy(Instruction&); // FIXME: Find a better way to organize this information @@ -150,6 +151,8 @@ protected: { } + void visit_labels_impl(Function) { } + private: SourceRecord m_source_record {}; Type m_type {}; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 6f7fa4dd75d..2b55780bad2 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -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 Interpreter::run(Script& script_record, JS::GCPtrdump(); // 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 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(program_counter)); + for (;;) { start: - auto pc = InstructionStreamIterator { m_current_block->instruction_stream(), m_current_executable }; - TemporaryChange temp_change { m_pc, Optional(pc) }; - bool will_return = false; bool will_yield = false; - ThrowCompletionOr result; - - while (!pc.at_end()) { - auto& instruction = *pc; + for (;;) { + auto& instruction = *reinterpret_cast(&bytecode[program_counter]); switch (instruction.type()) { case Instruction::Type::SetLocal: locals[static_cast(instruction).index()] = get(static_cast(instruction).src()); - break; + NEXT_INSTRUCTION(); case Instruction::Type::Mov: set(static_cast(instruction).dst(), get(static_cast(instruction).src())); - break; + NEXT_INSTRUCTION(); case Instruction::Type::End: accumulator = get(static_cast(instruction).value()); return; case Instruction::Type::Jump: - m_current_block = &static_cast(instruction).target().block(); + program_counter = static_cast(instruction).target().address(); goto start; case Instruction::Type::JumpIf: { auto& jump = static_cast(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(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(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(instruction).entry_point().block(); + program_counter = static_cast(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(instruction).resume_target().block(); + program_counter = static_cast(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(instruction).target().block(); - auto const* finalizer = m_current_block->finalizer(); - VERIFY(finalizer); - m_current_block = finalizer; + m_scheduled_jump = static_cast(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) - return; + { + auto result = instruction.execute(*this); - 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; + if (result.is_error()) { + if (handle_exception(program_counter, *result.throw_completion().value()) == HandleExceptionResponse::ExitFromExecutable) + return; 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(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(); - 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; - // the unwind_context will be pop'ed when entering the finally block - continue; + 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()) = {}; + 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 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(nullptr) }; + TemporaryChange restore_saved_jump { m_scheduled_jump, Optional {} }; 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 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(reinterpret_cast(&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 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(reinterpret_cast(&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 diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index faba4eba568..f983f4451b8 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -33,9 +33,9 @@ public: ThrowCompletionOr run(Script&, JS::GCPtr lexical_environment_override = nullptr); ThrowCompletionOr run(SourceTextModule&); - ThrowCompletionOr run(Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr) + ThrowCompletionOr run(Bytecode::Executable& executable, Optional 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 return_register_value; }; - ResultAndReturnRegister run_executable(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point); + ResultAndReturnRegister run_executable(Bytecode::Executable&, Optional 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 instruction_stream_iterator() const { return m_pc; } + Optional program_counter() const { return m_program_counter; } Vector& registers() { return vm().running_execution_context().registers; } Vector 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 m_scheduled_jump; GCPtr m_current_executable { nullptr }; - BasicBlock const* m_current_block { nullptr }; GCPtr m_realm { nullptr }; GCPtr m_global_object { nullptr }; GCPtr m_global_declarative_environment { nullptr }; - Optional m_pc {}; + Optional m_program_counter; + Span m_registers; + Span m_locals; }; extern bool g_dump_bytecode; diff --git a/Userland/Libraries/LibJS/Bytecode/Label.h b/Userland/Libraries/LibJS/Bytecode/Label.h index 44698f6704a..59bd9011512 100644 --- a/Userland/Libraries/LibJS/Bytecode/Label.h +++ b/Userland/Libraries/LibJS/Bytecode/Label.h @@ -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: - BasicBlock const* m_block { nullptr }; + union { + // Relevant while compiling. + BasicBlock const* m_block { nullptr }; + + // Relevant after compiling. + size_t m_address; + }; }; } template<> struct AK::Formatter : AK::Formatter { - ErrorOr format(FormatBuilder& builder, JS::Bytecode::Label const& value) + ErrorOr format(FormatBuilder& builder, JS::Bytecode::Label const& label) { - return AK::Formatter::format(builder, "@{}"sv, value.block().name()); + return AK::Formatter::format(builder, "@{:x}"sv, label.address()); } }; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index a2999cf4608..9b96325ddc5 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -1069,6 +1069,10 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function visitor) + { + visitor(m_target); + } auto& target() const { return m_target; } @@ -1090,6 +1094,11 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function 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 execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function 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 execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function 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 execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function visitor) + { + visitor(m_entry_point); + } auto& entry_point() const { return m_entry_point; } @@ -1531,6 +1554,10 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function visitor) + { + visitor(m_target); + } private: Label m_target; @@ -1570,6 +1597,10 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function visitor) + { + visitor(m_resume_target); + } auto& resume_target() const { return m_resume_target; } @@ -1596,6 +1627,11 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function 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 execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + void visit_labels_impl(Function visitor) + { + visitor(m_continuation_label); + } auto& continuation() const { return m_continuation_label; } Operand argument() const { return m_argument; } diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 0ab3521a8b0..07a8000c854 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -693,7 +693,7 @@ ThrowCompletionOr 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(); diff --git a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp index 684995525c7..94fdd774819 100644 --- a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp +++ b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp @@ -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 { if (value.is_object()) { auto number_value = value.as_object().get_without_side_effects("continuation"); - return reinterpret_cast(static_cast(number_value.as_double())); + if (number_value.is_null()) + return {}; + return static_cast(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. diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index fa446943a18..5e8a1068f22 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -384,7 +384,7 @@ ThrowCompletionOr 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> 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 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(); diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp index ec87f2ec34b..52ed6025763 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp @@ -36,7 +36,7 @@ NonnullOwnPtr 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(instruction_stream_iterator.value().executable())); visitor.visit(function_name); visitor.visit(arguments); visitor.visit(locals); diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h index f343df171be..f5c9780bf1c 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -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 context_owner; - Optional instruction_stream_iterator; + Optional program_counter; GCPtr function_name; Value this_value; bool is_strict_mode { false }; @@ -78,7 +77,7 @@ public: Vector locals; Vector registers; Vector unwind_contexts; - Vector previously_scheduled_jumps; + Vector> previously_scheduled_jumps; Vector> saved_lexical_environments; }; diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp index f4b6d93f095..b724bf4cd34 100644 --- a/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp @@ -88,12 +88,14 @@ ThrowCompletionOr 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 { if (value.is_object()) { auto number_value = value.as_object().get_without_side_effects("continuation"); - return reinterpret_cast(static_cast(number_value.as_double())); + if (number_value.is_null()) + return {}; + return static_cast(number_value.as_double()); } - return nullptr; + return {}; }; auto& realm = *vm.current_realm(); @@ -103,16 +105,12 @@ ThrowCompletionOr 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 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); diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp index 2e6cc54b63b..4b14e44a3e5 100644 --- a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp @@ -147,7 +147,7 @@ ThrowCompletionOr 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> 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; diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index 62620856824..142b11a0a34 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -180,7 +180,7 @@ ThrowCompletionOr 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 { diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 86399b28c64..9ad9d49211c 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -299,7 +299,7 @@ ThrowCompletionOr 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 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(); - return {}; + if (!context->program_counter.has_value()) + return {}; + + return context->executable->source_range_at(context->program_counter.value()); } Vector VM::stack_trace() const diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index ee87acbe254..90843971c49 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -717,7 +717,7 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtr