From 5edf88f9c2f2db7d863386ecb173ac4352f1eceb Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 8 Apr 2025 13:39:07 +0200 Subject: [PATCH] LibJS: Make class-specific members of ESFO lazily allocated We don't need the [[Fields]] and [[PrivateMethods]] slots for most ESFO instances, so let's reduce their impact on class size! This shrinks ESFO from 200 bytes to 160 bytes, allowing more allocations before we have to collect garbage. --- .../Runtime/ECMAScriptFunctionObject.cpp | 38 ++++++++++++------- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 18 ++++++--- Libraries/LibJS/Runtime/Object.cpp | 4 ++ 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index d741a3c4578..c35bdb5097f 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -649,21 +649,23 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor) visitor.visit(m_bytecode_executable); - for (auto& field : m_fields) { - field.initializer.visit( - [&visitor](GC::Ref& initializer) { - visitor.visit(initializer); - }, - [&visitor](Value initializer) { - visitor.visit(initializer); - }, - [](Empty) {}); - if (auto* property_key_ptr = field.name.get_pointer(); property_key_ptr && property_key_ptr->is_symbol()) - visitor.visit(property_key_ptr->as_symbol()); - } + if (m_class_data) { + for (auto& field : m_class_data->fields) { + field.initializer.visit( + [&visitor](GC::Ref& initializer) { + visitor.visit(initializer); + }, + [&visitor](Value initializer) { + visitor.visit(initializer); + }, + [](Empty) {}); + if (auto* property_key_ptr = field.name.get_pointer(); property_key_ptr && property_key_ptr->is_symbol()) + visitor.visit(property_key_ptr->as_symbol()); + } - for (auto& private_element : m_private_methods) - visitor.visit(private_element.value); + for (auto& private_element : m_class_data->private_methods) + visitor.visit(private_element.value); + } m_script_or_module.visit( [](Empty) {}, @@ -960,4 +962,12 @@ void ECMAScriptFunctionObject::set_name(FlyString const& name) m_name_string = PrimitiveString::create(vm, name); MUST(define_property_or_throw(vm.names.name, { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true })); } + +ECMAScriptFunctionObject::ClassData& ECMAScriptFunctionObject::ensure_class_data() const +{ + if (!m_class_data) + m_class_data = make(); + return *m_class_data; +} + } diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index e9ba2439f92..95b053996b1 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -152,11 +152,13 @@ public: [[nodiscard]] ByteString const& source_text() const { return shared_data().m_source_text; } void set_source_text(ByteString source_text) { const_cast(shared_data()).m_source_text = move(source_text); } - Vector const& fields() const { return m_fields; } - void add_field(ClassFieldDefinition field) { m_fields.append(move(field)); } + Vector const& fields() const { return ensure_class_data().fields; } + void add_field(ClassFieldDefinition field) { ensure_class_data().fields.append(move(field)); } - Vector const& private_methods() const { return m_private_methods; } - void add_private_method(PrivateElement method) { m_private_methods.append(move(method)); } + Vector const& private_methods() const { return ensure_class_data().private_methods; } + void add_private_method(PrivateElement method) { ensure_class_data().private_methods.append(move(method)); } + + [[nodiscard]] bool has_class_data() const { return m_class_data; } // This is for IsSimpleParameterList (static semantics) bool has_simple_parameter_list() const { return shared_data().m_has_simple_parameter_list; } @@ -211,8 +213,12 @@ private: GC::Ptr m_realm; // [[Realm]] ScriptOrModule m_script_or_module; // [[ScriptOrModule]] GC::Ptr m_home_object; // [[HomeObject]] - Vector m_fields; // [[Fields]] - Vector m_private_methods; // [[PrivateMethods]] + struct ClassData { + Vector fields; // [[Fields]] + Vector private_methods; // [[PrivateMethods]] + }; + ClassData& ensure_class_data() const; + mutable OwnPtr m_class_data; }; template<> diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 7812d3433f6..606bd0cba90 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -699,6 +699,10 @@ ThrowCompletionOr Object::define_field(ClassFieldDefinition const& field) // 7.3.34 InitializeInstanceElements ( O, constructor ), https://tc39.es/ecma262/#sec-initializeinstanceelements ThrowCompletionOr Object::initialize_instance_elements(ECMAScriptFunctionObject& constructor) { + // AD-HOC: Avoid lazy instantiation of ECMAScriptFunctionObject::ClassData. + if (!constructor.has_class_data()) + return {}; + // 1. Let methods be the value of constructor.[[PrivateMethods]]. // 2. For each PrivateElement method of methods, do for (auto const& method : constructor.private_methods()) {