From 5a92929282ec8251b8e02546df5739b44a617e7a Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 23 Apr 2025 19:44:54 +0200 Subject: [PATCH] LibJS: Put vector of regs+consts+locals+args in tail of ExecutionContext By doing that we avoid doing separate allocation for each such vector, which was really expensive on js heavy websites. For example this change helps to get EC allocation down from ~17% to ~2% on Google Maps. This comes at cost of adding extra complexity to custom execution context allocator, because EC no longer has fixed size and we need to maintain a list of buckets. --- Libraries/LibJS/Bytecode/Interpreter.cpp | 12 +-- Libraries/LibJS/Runtime/ExecutionContext.cpp | 94 ++++++++++++++++---- Libraries/LibJS/Runtime/ExecutionContext.h | 38 +++++--- 3 files changed, 110 insertions(+), 34 deletions(-) diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 289fbcb0f3f..82ba095d65e 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -741,11 +741,11 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe auto& running_execution_context = vm().running_execution_context(); u32 registers_and_constants_and_locals_count = executable.number_of_registers + executable.constants.size() + executable.local_variable_names.size(); - VERIFY(registers_and_constants_and_locals_count <= running_execution_context.registers_and_constants_and_locals_and_arguments.size()); + VERIFY(registers_and_constants_and_locals_count <= running_execution_context.registers_and_constants_and_locals_and_arguments_span().size()); TemporaryChange restore_running_execution_context { m_running_execution_context, &running_execution_context }; TemporaryChange restore_arguments { m_arguments, running_execution_context.arguments() }; - TemporaryChange restore_registers_and_constants_and_locals { m_registers_and_constants_and_locals, running_execution_context.registers_and_constants_and_locals_and_arguments.span() }; + TemporaryChange restore_registers_and_constants_and_locals { m_registers_and_constants_and_locals, running_execution_context.registers_and_constants_and_locals_and_arguments_span() }; reg(Register::accumulator()) = initial_accumulator_value; reg(Register::return_value()) = js_special_empty_value(); @@ -759,8 +759,9 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe running_execution_context.executable = &executable; + auto* registers_and_constants_and_locals_and_arguments = running_execution_context.registers_and_constants_and_locals_and_arguments(); for (size_t i = 0; i < executable.constants.size(); ++i) { - running_execution_context.registers_and_constants_and_locals_and_arguments[executable.number_of_registers + i] = executable.constants[i]; + registers_and_constants_and_locals_and_arguments[executable.number_of_registers + i] = executable.constants[i]; } run_bytecode(entry_point.value_or(0)); @@ -768,7 +769,6 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter did run unit {:p}", &executable); if constexpr (JS_BYTECODE_DEBUG) { - auto const& registers_and_constants_and_locals_and_arguments = running_execution_context.registers_and_constants_and_locals_and_arguments; for (size_t i = 0; i < executable.number_of_registers; ++i) { String value_string; if (registers_and_constants_and_locals_and_arguments[i].is_special_empty_value()) @@ -788,8 +788,8 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe vm().finish_execution_generation(); if (!exception.is_special_empty_value()) - return { throw_completion(exception), running_execution_context.registers_and_constants_and_locals_and_arguments[0] }; - return { return_value, running_execution_context.registers_and_constants_and_locals_and_arguments[0] }; + return { throw_completion(exception), registers_and_constants_and_locals_and_arguments[0] }; + return { return_value, registers_and_constants_and_locals_and_arguments[0] }; } void Interpreter::enter_unwind_context() diff --git a/Libraries/LibJS/Runtime/ExecutionContext.cpp b/Libraries/LibJS/Runtime/ExecutionContext.cpp index cd523b9acac..4b88bf6ff4d 100644 --- a/Libraries/LibJS/Runtime/ExecutionContext.cpp +++ b/Libraries/LibJS/Runtime/ExecutionContext.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2020-2021, Linus Groh * Copyright (c) 2022, Luke Wilde + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,39 +16,96 @@ namespace JS { class ExecutionContextAllocator { public: - NonnullOwnPtr allocate() + NonnullOwnPtr allocate(u32 registers_and_constants_and_locals_count, u32 arguments_count) { - if (m_execution_contexts.is_empty()) - return adopt_own(*new ExecutionContext); - void* slot = m_execution_contexts.take_last(); - return adopt_own(*new (slot) ExecutionContext); + auto tail_size = registers_and_constants_and_locals_count + arguments_count; + + void* slot = nullptr; + if (tail_size <= 4 && !m_execution_contexts_with_4_tail.is_empty()) { + slot = m_execution_contexts_with_4_tail.take_last(); + } else if (tail_size <= 16 && !m_execution_contexts_with_16_tail.is_empty()) { + slot = m_execution_contexts_with_16_tail.take_last(); + } else if (tail_size <= 64 && !m_execution_contexts_with_64_tail.is_empty()) { + slot = m_execution_contexts_with_64_tail.take_last(); + } else if (tail_size <= 128 && !m_execution_contexts_with_128_tail.is_empty()) { + slot = m_execution_contexts_with_128_tail.take_last(); + } else if (tail_size <= 256 && !m_execution_contexts_with_256_tail.is_empty()) { + slot = m_execution_contexts_with_256_tail.take_last(); + } else if (tail_size <= 512 && !m_execution_contexts_with_512_tail.is_empty()) { + slot = m_execution_contexts_with_512_tail.take_last(); + } + + if (slot) { + return adopt_own(*new (slot) ExecutionContext(registers_and_constants_and_locals_count, arguments_count)); + } + + auto tail_allocation_size = [tail_size] -> u32 { + if (tail_size <= 4) + return 4; + if (tail_size <= 16) + return 16; + if (tail_size <= 64) + return 64; + if (tail_size <= 128) + return 128; + if (tail_size <= 256) + return 256; + if (tail_size <= 512) + return 512; + return tail_size; + }; + + auto* memory = ::operator new(sizeof(ExecutionContext) + tail_allocation_size() * sizeof(Value)); + return adopt_own(*::new (memory) ExecutionContext(registers_and_constants_and_locals_count, arguments_count)); } - void deallocate(void* ptr) + void deallocate(void* ptr, u32 tail_size) { - m_execution_contexts.append(ptr); + if (tail_size <= 4) { + m_execution_contexts_with_4_tail.append(ptr); + } else if (tail_size <= 16) { + m_execution_contexts_with_16_tail.append(ptr); + } else if (tail_size <= 64) { + m_execution_contexts_with_64_tail.append(ptr); + } else if (tail_size <= 128) { + m_execution_contexts_with_128_tail.append(ptr); + } else if (tail_size <= 256) { + m_execution_contexts_with_256_tail.append(ptr); + } else if (tail_size <= 512) { + m_execution_contexts_with_512_tail.append(ptr); + } else { + ::operator delete(ptr); + } } private: - Vector m_execution_contexts; + Vector m_execution_contexts_with_4_tail; + Vector m_execution_contexts_with_16_tail; + Vector m_execution_contexts_with_64_tail; + Vector m_execution_contexts_with_128_tail; + Vector m_execution_contexts_with_256_tail; + Vector m_execution_contexts_with_512_tail; }; static NeverDestroyed s_execution_context_allocator; NonnullOwnPtr ExecutionContext::create(u32 registers_and_constants_and_locals_count, u32 arguments_count) { - auto execution_context = s_execution_context_allocator->allocate(); - execution_context->registers_and_constants_and_locals_and_arguments.resize_with_default_value(registers_and_constants_and_locals_count + arguments_count, js_special_empty_value()); - execution_context->arguments_offset = registers_and_constants_and_locals_count; - return execution_context; + return s_execution_context_allocator->allocate(registers_and_constants_and_locals_count, arguments_count); } void ExecutionContext::operator delete(void* ptr) { - s_execution_context_allocator->deallocate(ptr); + auto const* execution_context = static_cast(ptr); + s_execution_context_allocator->deallocate(ptr, execution_context->registers_and_constants_and_locals_and_arguments_count); } -ExecutionContext::ExecutionContext() +ExecutionContext::ExecutionContext(u32 registers_and_constants_and_locals_count, u32 arguments_count) { + registers_and_constants_and_locals_and_arguments_count = registers_and_constants_and_locals_count + arguments_count; + arguments_offset = registers_and_constants_and_locals_count; + auto* registers_and_constants_and_locals_and_arguments = this->registers_and_constants_and_locals_and_arguments(); + for (size_t i = 0; i < registers_and_constants_and_locals_count; ++i) + registers_and_constants_and_locals_and_arguments[i] = js_special_empty_value(); } ExecutionContext::~ExecutionContext() @@ -56,7 +114,7 @@ ExecutionContext::~ExecutionContext() NonnullOwnPtr ExecutionContext::copy() const { - auto copy = create(registers_and_constants_and_locals_and_arguments.size(), arguments().size()); + auto copy = create(registers_and_constants_and_locals_and_arguments_count, arguments().size()); copy->function = function; copy->realm = realm; copy->script_or_module = script_or_module; @@ -70,10 +128,12 @@ NonnullOwnPtr ExecutionContext::copy() const copy->executable = executable; copy->arguments_offset = arguments_offset; copy->passed_argument_count = passed_argument_count; - copy->registers_and_constants_and_locals_and_arguments = registers_and_constants_and_locals_and_arguments; copy->unwind_contexts = unwind_contexts; copy->saved_lexical_environments = saved_lexical_environments; copy->previously_scheduled_jumps = previously_scheduled_jumps; + copy->registers_and_constants_and_locals_and_arguments_count = registers_and_constants_and_locals_and_arguments_count; + for (size_t i = 0; i < registers_and_constants_and_locals_and_arguments_count; ++i) + copy->registers_and_constants_and_locals_and_arguments()[i] = registers_and_constants_and_locals_and_arguments()[i]; return copy; } @@ -89,7 +149,7 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor) visitor.visit(*this_value); visitor.visit(executable); visitor.visit(function_name); - visitor.visit(registers_and_constants_and_locals_and_arguments); + visitor.visit(registers_and_constants_and_locals_and_arguments_span()); for (auto& context : unwind_contexts) { visitor.visit(context.lexical_environment); } diff --git a/Libraries/LibJS/Runtime/ExecutionContext.h b/Libraries/LibJS/Runtime/ExecutionContext.h index 25a66936f7c..5ae9f5cd838 100644 --- a/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Libraries/LibJS/Runtime/ExecutionContext.h @@ -2,6 +2,7 @@ * Copyright (c) 2020-2024, Andreas Kling * Copyright (c) 2020-2021, Linus Groh * Copyright (c) 2022, Luke Wilde + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -42,7 +43,7 @@ struct ExecutionContext { private: friend class ExecutionContextAllocator; - ExecutionContext(); + ExecutionContext(u32 registers_and_constants_and_locals_count, u32 arguments_count); public: void operator delete(void* ptr); @@ -70,17 +71,27 @@ public: // FIXME: Move this out of LibJS (e.g. by using the CustomData concept), as it's used exclusively by LibWeb. size_t skip_when_determining_incumbent_counter { 0 }; + Span registers_and_constants_and_locals_and_arguments_span() + { + return { registers_and_constants_and_locals_and_arguments(), registers_and_constants_and_locals_and_arguments_count }; + } + + Value const* registers_and_constants_and_locals_and_arguments() const + { + return reinterpret_cast(reinterpret_cast(this) + sizeof(ExecutionContext)); + } + Value argument(size_t index) const { - auto arguments_size = registers_and_constants_and_locals_and_arguments.size() - arguments_offset; + auto arguments_size = registers_and_constants_and_locals_and_arguments_count - arguments_offset; if (index >= arguments_size) [[unlikely]] return js_undefined(); - return registers_and_constants_and_locals_and_arguments[arguments_offset + index]; + return registers_and_constants_and_locals_and_arguments()[arguments_offset + index]; } Value& local(size_t index) { - return registers_and_constants_and_locals_and_arguments[index]; + return registers_and_constants_and_locals_and_arguments()[index]; } u32 arguments_offset { 0 }; @@ -89,22 +100,27 @@ public: Span arguments() { - return registers_and_constants_and_locals_and_arguments.span().slice(arguments_offset); + return { registers_and_constants_and_locals_and_arguments() + arguments_offset, registers_and_constants_and_locals_and_arguments_count - arguments_offset }; } ReadonlySpan arguments() const { - return registers_and_constants_and_locals_and_arguments.span().slice(arguments_offset); + return { registers_and_constants_and_locals_and_arguments() + arguments_offset, registers_and_constants_and_locals_and_arguments_count - arguments_offset }; } -private: - friend class Bytecode::Interpreter; - Vector registers_and_constants_and_locals_and_arguments; - -public: Vector unwind_contexts; Vector> previously_scheduled_jumps; Vector> saved_lexical_environments; + +private: + friend class Bytecode::Interpreter; + + Value* registers_and_constants_and_locals_and_arguments() + { + return reinterpret_cast(reinterpret_cast(this) + sizeof(ExecutionContext)); + } + + u32 registers_and_constants_and_locals_and_arguments_count { 0 }; }; struct StackTraceElement {