From 865e651a7ded49468b477d6605fd4ee313e8a0b2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 1 May 2024 19:33:49 +0200 Subject: [PATCH] LibJS: Merge CallFrame into ExecutionContext Before this change both ExecutionContext and CallFrame were created before executing function/module/script with a couple exceptions: - executable created for default function argument evaluation has to run in function's execution context. - `execute_ast_node()` where executable compiled for ASTNode has to be executed in running execution context. This change moves all members previously owned by CallFrame into ExecutionContext, and makes two exceptions where an executable that does not have a corresponding execution context saves and restores registers before running. Now, all execution state lives in a single entity, which makes it a bit easier to reason about and opens opportunities for optimizations, such as moving registers and local variables into a single array. --- .../LibJS/Bytecode/CommonImplementations.h | 2 +- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 108 ++++++------------ .../Libraries/LibJS/Bytecode/Interpreter.h | 69 +++-------- Userland/Libraries/LibJS/Heap/Heap.cpp | 3 - .../LibJS/Runtime/AbstractOperations.cpp | 4 +- .../LibJS/Runtime/AsyncGenerator.cpp | 19 +-- .../Libraries/LibJS/Runtime/AsyncGenerator.h | 3 +- .../Runtime/ECMAScriptFunctionObject.cpp | 31 +++-- .../LibJS/Runtime/ExecutionContext.cpp | 9 ++ .../LibJS/Runtime/ExecutionContext.h | 5 + .../LibJS/Runtime/GeneratorObject.cpp | 19 +-- .../Libraries/LibJS/Runtime/GeneratorObject.h | 3 +- .../Libraries/LibJS/Runtime/ShadowRealm.cpp | 8 +- Userland/Libraries/LibJS/Runtime/VM.cpp | 17 ++- Userland/Libraries/LibJS/SourceTextModule.cpp | 8 +- 15 files changed, 121 insertions(+), 187 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h index 7ab35fd0b1..3aa5caaa56 100644 --- a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h +++ b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h @@ -647,7 +647,7 @@ inline ThrowCompletionOr new_class(VM& vm, Value supe // NOTE: NewClass expects classEnv to be active lexical environment auto* class_environment = vm.lexical_environment(); - vm.running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last(); + vm.running_execution_context().lexical_environment = vm.running_execution_context().saved_lexical_environments.take_last(); Optional binding_name; DeprecatedFlyString class_name; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 90a6d77dff..a7465a790c 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -106,18 +106,6 @@ static ByteString format_value_list(StringView name, ReadonlySpan values) return builder.to_byte_string(); } -NonnullOwnPtr CallFrame::create(size_t register_count) -{ - size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count; - auto* memory = malloc(allocation_size); - VERIFY(memory); - auto call_frame = adopt_own(*new (memory) CallFrame); - call_frame->register_count = register_count; - for (auto i = 0u; i < register_count; ++i) - new (&call_frame->register_values[i]) Value(); - return call_frame; -} - ALWAYS_INLINE static ThrowCompletionOr loosely_inequals(VM& vm, Value src1, Value src2) { if (src1.tag() == src2.tag()) { @@ -163,13 +151,6 @@ Interpreter::~Interpreter() { } -void Interpreter::visit_edges(Cell::Visitor& visitor) -{ - for (auto& frame : m_call_frames) { - frame.visit([&](auto& value) { value->visit_edges(visitor); }); - } -} - ALWAYS_INLINE Value Interpreter::get(Operand op) const { switch (op.type()) { @@ -263,11 +244,11 @@ ThrowCompletionOr Interpreter::run(Script& script_record, JS::GCPtrdump(); // a. Set result to the result of evaluating script. - auto result_or_error = run_and_return_frame(*executable, nullptr); + auto result_or_error = run_executable(*executable, nullptr); if (result_or_error.value.is_error()) result = result_or_error.value.release_error(); else - result = result_or_error.frame->registers()[0]; + result = result_or_error.return_register_value; } } @@ -384,7 +365,8 @@ void Interpreter::run_bytecode() do_return(saved_return_value()); break; } - auto const* old_scheduled_jump = call_frame().previously_scheduled_jumps.take_last(); + 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) { // FIXME: If we `break` or `continue` in the finally, we need to clear // this field @@ -418,7 +400,8 @@ void Interpreter::run_bytecode() if (!handler && !finalizer) return; - auto& unwind_context = unwind_contexts().last(); + 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) { @@ -453,7 +436,8 @@ void Interpreter::run_bytecode() } if (auto const* finalizer = m_current_block->finalizer(); finalizer && !will_yield) { - auto& unwind_context = unwind_contexts().last(); + 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()) = {}; @@ -470,7 +454,7 @@ void Interpreter::run_bytecode() } } -Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executable, BasicBlock const* entry_point, CallFrame* in_frame) +Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& executable, BasicBlock const* entry_point) { dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable); @@ -484,12 +468,13 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa TemporaryChange restore_current_block { m_current_block, entry_point ?: executable.basic_blocks.first() }; - if (in_frame) - push_call_frame(in_frame); - else - push_call_frame(CallFrame::create(executable.number_of_registers)); + 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); - vm().execution_context_stack().last()->executable = &executable; + reg(Register::return_value()) = {}; + + running_execution_context.executable = &executable; run_bytecode(); @@ -513,48 +498,39 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa return_value = reg(Register::saved_return_value()); auto exception = reg(Register::exception()); - auto frame = pop_call_frame(); - - // NOTE: The return value from a called function is put into $0 in the caller context. - if (!m_call_frames.is_empty()) - call_frame().registers()[0] = return_value; - // At this point we may have already run any queued promise jobs via on_call_stack_emptied, // in which case this is a no-op. vm().run_queued_promise_jobs(); vm().finish_execution_generation(); - if (!exception.is_empty()) { - if (auto* call_frame = frame.get_pointer>()) - return { throw_completion(exception), move(*call_frame) }; - return { throw_completion(exception), nullptr }; - } - - if (auto* call_frame = frame.get_pointer>()) - return { return_value, move(*call_frame) }; - return { return_value, nullptr }; + if (!exception.is_empty()) + return { throw_completion(exception), vm().running_execution_context().registers[0] }; + return { return_value, vm().running_execution_context().registers[0] }; } void Interpreter::enter_unwind_context() { - unwind_contexts().empend( + auto& running_execution_context = vm().running_execution_context(); + running_execution_context.unwind_contexts.empend( m_current_executable, vm().running_execution_context().lexical_environment); - call_frame().previously_scheduled_jumps.append(m_scheduled_jump); + running_execution_context.previously_scheduled_jumps.append(m_scheduled_jump); m_scheduled_jump = nullptr; } void Interpreter::leave_unwind_context() { - unwind_contexts().take_last(); + auto& running_execution_context = vm().running_execution_context(); + running_execution_context.unwind_contexts.take_last(); } void Interpreter::catch_exception(Operand dst) { set(dst, reg(Register::exception())); reg(Register::exception()) = {}; - auto& context = unwind_contexts().last(); + auto& running_execution_context = vm().running_execution_context(); + auto& context = running_execution_context.unwind_contexts.last(); VERIFY(!context.handler_called); VERIFY(context.executable == ¤t_executable()); context.handler_called = true; @@ -563,9 +539,10 @@ void Interpreter::catch_exception(Operand dst) void Interpreter::enter_object_environment(Object& object) { - auto& old_environment = vm().running_execution_context().lexical_environment; - saved_lexical_environment_stack().append(old_environment); - vm().running_execution_context().lexical_environment = new_object_environment(object, true, old_environment); + auto& running_execution_context = vm().running_execution_context(); + auto& old_environment = running_execution_context.lexical_environment; + running_execution_context.saved_lexical_environments.append(old_environment); + running_execution_context.lexical_environment = new_object_environment(object, true, old_environment); } ThrowCompletionOr> compile(VM& vm, ASTNode const& node, ReadonlySpan parameters, FunctionKind kind, DeprecatedFlyString const& name) @@ -583,20 +560,6 @@ ThrowCompletionOr> compile(VM& vm, ASTNode co return bytecode_executable; } -void Interpreter::push_call_frame(Variant, CallFrame*> frame) -{ - m_call_frames.append(move(frame)); - m_current_call_frame = this->call_frame().registers(); - reg(Register::return_value()) = {}; -} - -Variant, CallFrame*> Interpreter::pop_call_frame() -{ - auto frame = m_call_frames.take_last(); - m_current_call_frame = m_call_frames.is_empty() ? Span {} : this->call_frame().registers(); - return frame; -} - } namespace JS::Bytecode { @@ -1039,7 +1002,8 @@ ThrowCompletionOr CreateLexicalEnvironment::execute_impl(Bytecode::Interpr swap(old_environment, environment); return environment; }; - interpreter.saved_lexical_environment_stack().append(make_and_swap_envs(interpreter.vm().running_execution_context().lexical_environment)); + auto& running_execution_context = interpreter.vm().running_execution_context(); + running_execution_context.saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment)); return {}; } @@ -1453,7 +1417,8 @@ ThrowCompletionOr ScheduleJump::execute_impl(Bytecode::Interpreter&) const ThrowCompletionOr LeaveLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.vm().running_execution_context().lexical_environment = interpreter.saved_lexical_environment_stack().take_last(); + auto& running_execution_context = interpreter.vm().running_execution_context(); + running_execution_context.lexical_environment = running_execution_context.saved_lexical_environments.take_last(); return {}; } @@ -1643,9 +1608,10 @@ ThrowCompletionOr BlockDeclarationInstantiation::execute_impl(Bytecode::In { auto& vm = interpreter.vm(); auto old_environment = vm.running_execution_context().lexical_environment; - interpreter.saved_lexical_environment_stack().append(old_environment); - vm.running_execution_context().lexical_environment = new_declarative_environment(*old_environment); - m_scope_node.block_declaration_instantiation(vm, vm.running_execution_context().lexical_environment); + auto& running_execution_context = vm.running_execution_context(); + running_execution_context.saved_lexical_environments.append(old_environment); + running_execution_context.lexical_environment = new_declarative_environment(*old_environment); + m_scope_node.block_declaration_instantiation(vm, running_execution_context.lexical_environment); return {}; } diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 7e39464eb9..f92b4d5d88 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -19,31 +19,6 @@ namespace JS::Bytecode { class InstructionStreamIterator; -struct CallFrame { - static NonnullOwnPtr create(size_t register_count); - - void operator delete(void* ptr) { free(ptr); } - - void visit_edges(Cell::Visitor& visitor) - { - visitor.visit(registers()); - visitor.visit(saved_lexical_environments); - for (auto& context : unwind_contexts) { - visitor.visit(context.lexical_environment); - } - } - - Vector> saved_lexical_environments; - Vector unwind_contexts; - Vector previously_scheduled_jumps; - - Span registers() { return { register_values, register_count }; } - ReadonlySpan registers() const { return { register_values, register_count }; } - - size_t register_count { 0 }; - Value register_values[]; -}; - class Interpreter { public: explicit Interpreter(VM&); @@ -60,27 +35,30 @@ public: ThrowCompletionOr run(Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr) { - auto value_and_frame = run_and_return_frame(executable, entry_point); - return move(value_and_frame.value); + auto result_and_return_register = run_executable(executable, entry_point); + return move(result_and_return_register.value); } - struct ValueAndFrame { + struct ResultAndReturnRegister { ThrowCompletionOr value; - OwnPtr frame; + Value return_register_value; }; - ValueAndFrame run_and_return_frame(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point, CallFrame* = nullptr); + ResultAndReturnRegister run_executable(Bytecode::Executable&, Bytecode::BasicBlock const* entry_point); 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 registers()[r.index()]; } - Value reg(Register const& r) const { return registers()[r.index()]; } + Value& reg(Register const& r) + { + return vm().running_execution_context().registers[r.index()]; + } + Value reg(Register const& r) const + { + return vm().running_execution_context().registers[r.index()]; + } [[nodiscard]] Value get(Operand) const; void set(Operand, Value); - auto& saved_lexical_environment_stack() { return call_frame().saved_lexical_environments; } - auto& unwind_contexts() { return call_frame().unwind_contexts; } - void do_return(Value value) { reg(Register::return_value()) = value; @@ -98,30 +76,13 @@ public: BasicBlock const& current_block() const { return *m_current_block; } Optional instruction_stream_iterator() const { return m_pc; } - void visit_edges(Cell::Visitor&); - - Span registers() { return m_current_call_frame; } - ReadonlySpan registers() const { return m_current_call_frame; } + Vector& registers() { return vm().running_execution_context().registers; } + Vector const& registers() const { return vm().running_execution_context().registers; } private: void run_bytecode(); - CallFrame& call_frame() - { - return m_call_frames.last().visit([](auto& x) -> CallFrame& { return *x; }); - } - - CallFrame const& call_frame() const - { - return const_cast(this)->call_frame(); - } - - void push_call_frame(Variant, CallFrame*>); - [[nodiscard]] Variant, CallFrame*> pop_call_frame(); - VM& m_vm; - Vector, CallFrame*>> m_call_frames; - Span m_current_call_frame; BasicBlock const* m_scheduled_jump { nullptr }; GCPtr m_current_executable { nullptr }; BasicBlock const* m_current_block { nullptr }; diff --git a/Userland/Libraries/LibJS/Heap/Heap.cpp b/Userland/Libraries/LibJS/Heap/Heap.cpp index f6d1adf25a..62d8e91e73 100644 --- a/Userland/Libraries/LibJS/Heap/Heap.cpp +++ b/Userland/Libraries/LibJS/Heap/Heap.cpp @@ -259,7 +259,6 @@ AK::JsonObject Heap::dump_graph() HashMap roots; gather_roots(roots); GraphConstructorVisitor visitor(*this, roots); - vm().bytecode_interpreter().visit_edges(visitor); visitor.visit_all_cells(); return visitor.dump(); } @@ -457,8 +456,6 @@ void Heap::mark_live_cells(HashMap const& roots) MarkingVisitor visitor(*this, roots); - vm().bytecode_interpreter().visit_edges(visitor); - visitor.mark_all_live_cells(); for (auto& inverse_root : m_uprooted_cells) diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index d1aef64466..42dab329f2 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -693,11 +693,11 @@ 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_and_return_frame(*executable, nullptr); + auto result_or_error = vm.bytecode_interpreter().run_executable(*executable, nullptr); if (result_or_error.value.is_error()) return result_or_error.value.release_error(); - auto& result = result_or_error.frame->registers()[0]; + auto& result = result_or_error.return_register_value; if (!result.is_empty()) eval_result = result; diff --git a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp index c27e67b9cd..684995525c 100644 --- a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp +++ b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp @@ -16,7 +16,7 @@ namespace JS { JS_DEFINE_ALLOCATOR(AsyncGenerator); -ThrowCompletionOr> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context, NonnullOwnPtr frame) +ThrowCompletionOr> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context) { auto& vm = realm.vm(); // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) @@ -24,7 +24,6 @@ ThrowCompletionOr> AsyncGenerator::create(Realm& re auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm)); auto object = realm.heap().allocate(realm, realm, generating_function_prototype_object, move(execution_context)); object->m_generating_function = generating_function; - object->m_frame = move(frame); object->m_previous_value = initial_value; return object; } @@ -45,8 +44,6 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor) } visitor.visit(m_generating_function); visitor.visit(m_previous_value); - if (m_frame) - m_frame->visit_edges(visitor); visitor.visit(m_current_promise); m_async_generator_context->visit_edges(visitor); } @@ -192,19 +189,9 @@ void AsyncGenerator::execute(VM& vm, Completion completion) VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end()); - Bytecode::CallFrame* frame = nullptr; - if (m_frame) - frame = m_frame.ptr(); + bytecode_interpreter.accumulator() = completion_object; - if (frame) - frame->registers()[0] = completion_object; - else - bytecode_interpreter.accumulator() = completion_object; - - auto next_result = bytecode_interpreter.run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame); - - if (!m_frame) - m_frame = move(next_result.frame); + auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block); auto result_value = move(next_result.value); if (!result_value.is_throw_completion()) { diff --git a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.h b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.h index 091a241a5b..db980ce452 100644 --- a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.h +++ b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.h @@ -28,7 +28,7 @@ public: Completed, }; - static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr, NonnullOwnPtr); + static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr); virtual ~AsyncGenerator() override = default; @@ -60,7 +60,6 @@ private: GCPtr m_generating_function; Value m_previous_value; - OwnPtr m_frame; GCPtr m_current_promise; }; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 424535a734..8a369a672d 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -713,11 +713,23 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) { argument_value = execution_context_arguments[i]; } else if (parameter.default_value) { - auto value_and_frame = vm.bytecode_interpreter().run_and_return_frame(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr); - if (value_and_frame.value.is_error()) - return value_and_frame.value.release_error(); - // Resulting value is in the accumulator. - argument_value = value_and_frame.frame->registers()[0]; + auto& running_execution_context = vm.running_execution_context(); + + // NOTE: Registers have to be saved and restored because executable created for default parameter uses + // running execution context. + // FIXME: This is a hack and instead instructions for default parameters should be a part of the function's bytecode. + auto saved_registers = running_execution_context.registers; + 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); + + for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index) + running_execution_context.registers[register_index] = saved_registers[register_index]; + + if (result_and_return_register.value.is_error()) + return result_and_return_register.value.release_error(); + argument_value = result_and_return_register.return_register_value; } else { argument_value = js_undefined(); } @@ -1103,7 +1115,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_and_return_frame(*maybe_executable.value(), nullptr).value; + result = vm.bytecode_interpreter().run_executable(*maybe_executable.value(), nullptr).value; } // b. Else, else { @@ -1230,9 +1242,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() } } - auto result_and_frame = vm.bytecode_interpreter().run_and_return_frame(*m_bytecode_executable, nullptr); + auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, nullptr); - VERIFY(result_and_frame.frame != nullptr); if (result_and_frame.value.is_error()) return result_and_frame.value.release_error(); @@ -1244,11 +1255,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() return { Completion::Type::Return, result.value_or(js_undefined()), {} }; if (m_kind == FunctionKind::AsyncGenerator) { - auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull())); + auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy())); return { Completion::Type::Return, async_generator_object, {} }; } - auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull())); + auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy())); // NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise // See AwaitExpression::generate_bytecode() for the transformation. diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp index 54fe22a360..ec87f2ec34 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp @@ -43,6 +43,10 @@ NonnullOwnPtr ExecutionContext::copy() const copy->executable = executable; copy->arguments = arguments; copy->locals = locals; + copy->registers = registers; + copy->unwind_contexts = unwind_contexts; + copy->saved_lexical_environments = saved_lexical_environments; + copy->previously_scheduled_jumps = previously_scheduled_jumps; return copy; } @@ -61,6 +65,11 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor) visitor.visit(function_name); visitor.visit(arguments); visitor.visit(locals); + visitor.visit(registers); + for (auto& context : unwind_contexts) { + visitor.visit(context.lexical_environment); + } + visitor.visit(saved_lexical_environments); script_or_module.visit( [](Empty) {}, [&](auto& script_or_module) { diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h index 43df15c60a..f343df171b 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -75,6 +76,10 @@ public: Vector arguments; Vector locals; + Vector registers; + Vector unwind_contexts; + Vector previously_scheduled_jumps; + Vector> saved_lexical_environments; }; struct StackTraceElement { diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp index 5a6d378db9..f4b6d93f09 100644 --- a/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp @@ -16,7 +16,7 @@ namespace JS { JS_DEFINE_ALLOCATOR(GeneratorObject); -ThrowCompletionOr> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context, NonnullOwnPtr frame) +ThrowCompletionOr> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context) { auto& vm = realm.vm(); // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) @@ -32,7 +32,6 @@ ThrowCompletionOr> GeneratorObject::create(Realm& auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm)); auto object = realm.heap().allocate(realm, realm, generating_function_prototype_object, move(execution_context)); object->m_generating_function = generating_function; - object->m_frame = move(frame); object->m_previous_value = initial_value; return object; } @@ -49,8 +48,6 @@ void GeneratorObject::visit_edges(Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_generating_function); visitor.visit(m_previous_value); - if (m_frame) - m_frame->visit_edges(visitor); m_execution_context->visit_edges(visitor); } @@ -113,22 +110,12 @@ ThrowCompletionOr GeneratorObject::execute(VM& vm, Completion const& comp VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end()); - Bytecode::CallFrame* frame = nullptr; - if (m_frame) - frame = m_frame; + bytecode_interpreter.registers()[0] = completion_object; - if (frame) - frame->registers()[0] = completion_object; - else - bytecode_interpreter.accumulator() = completion_object; - - auto next_result = bytecode_interpreter.run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame); + auto next_result = bytecode_interpreter.run_executable(*m_generating_function->bytecode_executable(), next_block); vm.pop_execution_context(); - if (!m_frame) - m_frame = move(next_result.frame); - auto result_value = move(next_result.value); if (result_value.is_throw_completion()) { // Uncaught exceptions disable the generator. diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.h b/Userland/Libraries/LibJS/Runtime/GeneratorObject.h index 596ba1acf2..48eb59e867 100644 --- a/Userland/Libraries/LibJS/Runtime/GeneratorObject.h +++ b/Userland/Libraries/LibJS/Runtime/GeneratorObject.h @@ -17,7 +17,7 @@ class GeneratorObject : public Object { JS_DECLARE_ALLOCATOR(GeneratorObject); public: - static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr, NonnullOwnPtr); + static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr); virtual ~GeneratorObject() override = default; void visit_edges(Cell::Visitor&) override; @@ -43,7 +43,6 @@ private: NonnullOwnPtr m_execution_context; GCPtr m_generating_function; Value m_previous_value; - OwnPtr m_frame; GeneratorState m_generator_state { GeneratorState::SuspendedStart }; Optional m_generator_brand; }; diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index 297d17dac9..6262085682 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -180,12 +180,12 @@ ThrowCompletionOr perform_shadow_realm_eval(VM& vm, StringView source_tex else { auto executable = maybe_executable.release_value(); - auto value_and_frame = vm.bytecode_interpreter().run_and_return_frame(*executable, nullptr); - if (value_and_frame.value.is_error()) { - result = value_and_frame.value.release_error(); + auto result_and_return_register = vm.bytecode_interpreter().run_executable(*executable, nullptr); + if (result_and_return_register.value.is_error()) { + result = result_and_return_register.value.release_error(); } else { // Resulting value is in the accumulator. - result = value_and_frame.frame->registers()[0].value_or(js_undefined()); + result = result_and_return_register.return_register_value.value_or(js_undefined()); } } } diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index c751d93306..86399b28c6 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -289,11 +289,24 @@ ThrowCompletionOr VM::binding_initialization(NonnullRefPtr VM::execute_ast_node(ASTNode const& node) { + // FIXME: This function should be gone once we will emit bytecode for everything before executing instructions. + auto executable = TRY(Bytecode::compile(*this, node, {}, FunctionKind::Normal, ""sv)); - auto result_or_error = bytecode_interpreter().run_and_return_frame(*executable, nullptr); + auto& running_execution_context = this->running_execution_context(); + + // Registers have to be saved and restored because executable for compiled ASTNode does not have its own execution context + auto saved_registers = running_execution_context.registers; + 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); + + for (size_t i = 0; i < saved_registers.size(); ++i) + running_execution_context.registers[i] = saved_registers[i]; + if (result_or_error.value.is_error()) return result_or_error.value.release_error(); - return result_or_error.frame->registers()[0]; + return result_or_error.return_register_value; } // 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index 9c5fd5a41b..4dd9d0affd 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -717,12 +717,12 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtrregisters()[0].value_or(js_undefined()); + result = result_and_return_register.return_register_value.value_or(js_undefined()); } }