From 3fc0333ee63744e4f12c582ad68b65b00ff6cde2 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 27 Nov 2023 17:27:51 +0100 Subject: [PATCH] LibJS: Put Bytecode::CallFrame + register slots in a single allocation The number of registers in a call frame never changes, so we can allocate it at the end of the CallFrame object and save ourselves the cost of allocating separate Vector storage for every call frame. --- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 27 +++++++++++++------ .../Libraries/LibJS/Bytecode/Interpreter.h | 16 ++++++++--- .../LibJS/Runtime/AbstractOperations.cpp | 2 +- .../LibJS/Runtime/AsyncGenerator.cpp | 14 +++++----- .../Libraries/LibJS/Runtime/AsyncGenerator.h | 4 +-- .../Runtime/ECMAScriptFunctionObject.cpp | 6 ++--- .../LibJS/Runtime/GeneratorObject.cpp | 14 +++++----- .../Libraries/LibJS/Runtime/GeneratorObject.h | 4 +-- .../Libraries/LibJS/Runtime/ShadowRealm.cpp | 2 +- Userland/Libraries/LibJS/Runtime/VM.cpp | 2 +- Userland/Libraries/LibJS/SourceTextModule.cpp | 2 +- 11 files changed, 57 insertions(+), 36 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 78d3f65b37..8b0b763842 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -40,6 +40,18 @@ namespace JS::Bytecode { bool g_dump_bytecode = false; +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; +} + Interpreter::Interpreter(VM& vm) : m_vm(vm) { @@ -125,7 +137,7 @@ ThrowCompletionOr Interpreter::run(Script& script_record, JS::GCPtrregisters[0]; + result = result_or_error.frame->registers()[0]; } } @@ -354,9 +366,9 @@ 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, executable.number_of_registers); + push_call_frame(in_frame); else - push_call_frame(make(), executable.number_of_registers); + push_call_frame(CallFrame::create(executable.number_of_registers)); vm().execution_context_stack().last()->executable = &executable; @@ -400,7 +412,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa // 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; + 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. @@ -471,18 +483,17 @@ Realm& Interpreter::realm() return *m_vm.current_realm(); } -void Interpreter::push_call_frame(Variant, CallFrame*> frame, size_t register_count) +void Interpreter::push_call_frame(Variant, CallFrame*> frame) { m_call_frames.append(move(frame)); - this->call_frame().registers.resize(register_count); - m_current_call_frame = this->call_frame().registers; + 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; + m_current_call_frame = m_call_frames.is_empty() ? Span {} : this->call_frame().registers(); return frame; } diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index e45c9ece63..2b819ca0a2 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -20,9 +20,13 @@ 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) { - for (auto const& value : registers) + for (auto const& value : registers()) visitor.visit(value); for (auto const& environment : saved_lexical_environments) visitor.visit(environment); @@ -30,10 +34,16 @@ struct CallFrame { visitor.visit(context.lexical_environment); } } - Vector registers; + 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 { @@ -101,7 +111,7 @@ private: return const_cast(this)->call_frame(); } - void push_call_frame(Variant, CallFrame*>, size_t register_count); + void push_call_frame(Variant, CallFrame*>); [[nodiscard]] Variant, CallFrame*> pop_call_frame(); VM& m_vm; diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 1a3cb09118..87af53314f 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -691,7 +691,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, 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.frame->registers()[0]; if (!result.is_empty()) eval_result = result; diff --git a/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp b/Userland/Libraries/LibJS/Runtime/AsyncGenerator.cpp index e07a09428f..3e18fcba79 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, Bytecode::CallFrame frame) +ThrowCompletionOr> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context, NonnullOwnPtr frame) { auto& vm = realm.vm(); // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) @@ -45,7 +45,7 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor) } visitor.visit(m_generating_function); visitor.visit(m_previous_value); - if (m_frame.has_value()) + if (m_frame) m_frame->visit_edges(visitor); visitor.visit(m_current_promise); } @@ -192,18 +192,18 @@ 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.has_value()) - frame = &m_frame.value(); + if (m_frame) + frame = m_frame.ptr(); if (frame) - frame->registers[0] = completion_object; + 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.has_value()) - m_frame = move(*next_result.frame); + if (!m_frame) + m_frame = move(next_result.frame); 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 595068994b..091a241a5b 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, Bytecode::CallFrame); + static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr, NonnullOwnPtr); virtual ~AsyncGenerator() override = default; @@ -60,7 +60,7 @@ private: GCPtr m_generating_function; Value m_previous_value; - Optional m_frame; + OwnPtr m_frame; GCPtr m_current_promise; }; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 65f6366eb2..4270a0d442 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -704,7 +704,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia 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.at(0); + argument_value = value_and_frame.frame->registers()[0]; } else { argument_value = js_undefined(); } @@ -1225,11 +1225,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(), move(*result_and_frame.frame))); + auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull())); return { Completion::Type::Return, async_generator_object, {} }; } - auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame))); + auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull())); // 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/GeneratorObject.cpp b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp index 98e421be28..0b9ecdbe5a 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, Bytecode::CallFrame frame) +ThrowCompletionOr> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr execution_context, NonnullOwnPtr frame) { auto& vm = realm.vm(); // This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) @@ -49,7 +49,7 @@ void GeneratorObject::visit_edges(Cell::Visitor& visitor) Base::visit_edges(visitor); visitor.visit(m_generating_function); visitor.visit(m_previous_value); - if (m_frame.has_value()) + if (m_frame) m_frame->visit_edges(visitor); } @@ -113,11 +113,11 @@ 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.has_value()) - frame = &m_frame.value(); + if (m_frame) + frame = m_frame; if (frame) - frame->registers[0] = completion_object; + frame->registers()[0] = completion_object; else bytecode_interpreter.accumulator() = completion_object; @@ -125,8 +125,8 @@ ThrowCompletionOr GeneratorObject::execute(VM& vm, Completion const& comp vm.pop_execution_context(); - if (!m_frame.has_value()) - m_frame = move(*next_result.frame); + if (!m_frame) + m_frame = move(next_result.frame); auto result_value = move(next_result.value); if (result_value.is_throw_completion()) { diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.h b/Userland/Libraries/LibJS/Runtime/GeneratorObject.h index 1a0b9c762a..596ba1acf2 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, Bytecode::CallFrame); + static ThrowCompletionOr> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr, NonnullOwnPtr); virtual ~GeneratorObject() override = default; void visit_edges(Cell::Visitor&) override; @@ -43,7 +43,7 @@ private: NonnullOwnPtr m_execution_context; GCPtr m_generating_function; Value m_previous_value; - Optional m_frame; + 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 9dd5bf4368..c610821e54 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -185,7 +185,7 @@ ThrowCompletionOr perform_shadow_realm_eval(VM& vm, StringView source_tex result = value_and_frame.value.release_error(); } else { // Resulting value is in the accumulator. - result = value_and_frame.frame->registers.at(0).value_or(js_undefined()); + result = value_and_frame.frame->registers()[0].value_or(js_undefined()); } } } diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index cc94e5029b..d636fd0ea2 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -285,7 +285,7 @@ ThrowCompletionOr VM::execute_ast_node(ASTNode const& node) auto result_or_error = bytecode_interpreter().run_and_return_frame(*executable, nullptr); 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.frame->registers()[0]; } // 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 6f8615a4be..7254dd9e74 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -708,7 +708,7 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtrregisters.at(0).value_or(js_undefined()); + result = value_and_frame.frame->registers()[0].value_or(js_undefined()); } }