From c037bda455d2afb935123f0464d29fd6b7e0b017 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 25 Mar 2025 20:08:38 +0000 Subject: [PATCH] LibJS: Use a premade shape for normal function objects This avoids going through all the shape transitions when setting up the most common form of ESFO. This is extremely hot on Uber Eats, and this provides some relief. --- .../Runtime/ECMAScriptFunctionObject.cpp | 55 +++++++++++-------- Libraries/LibJS/Runtime/Intrinsics.cpp | 10 ++++ Libraries/LibJS/Runtime/Intrinsics.h | 10 ++++ Libraries/LibJS/Runtime/Object.cpp | 6 ++ Libraries/LibJS/Runtime/Object.h | 2 + 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index b7f6bf79140..5658571fc6f 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -76,6 +76,9 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, ByteString so , m_is_arrow_function(is_arrow_function) , m_kind(kind) { + if (!m_is_arrow_function && m_kind == FunctionKind::Normal) + unsafe_set_shape(m_realm->intrinsics().normal_function_shape()); + // NOTE: This logic is from OrdinaryFunctionCreate, https://tc39.es/ecma262/#sec-ordinaryfunctioncreate // 9. If thisMode is lexical-this, set F.[[ThisMode]] to lexical. @@ -347,30 +350,38 @@ void ECMAScriptFunctionObject::initialize(Realm& realm) m_name_string = PrimitiveString::create(vm, m_name); - MUST(define_property_or_throw(vm.names.length, { .value = Value(m_function_length), .writable = false, .enumerable = false, .configurable = true })); - MUST(define_property_or_throw(vm.names.name, { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true })); + if (!m_is_arrow_function && m_kind == FunctionKind::Normal) { + put_direct(realm.intrinsics().normal_function_length_offset(), Value(m_function_length)); + put_direct(realm.intrinsics().normal_function_name_offset(), m_name_string); - if (!m_is_arrow_function) { - Object* prototype = nullptr; - switch (m_kind) { - case FunctionKind::Normal: - prototype = Object::create_with_premade_shape(realm.intrinsics().normal_function_prototype_shape()); - prototype->put_direct(realm.intrinsics().normal_function_prototype_constructor_offset(), this); - break; - case FunctionKind::Generator: - // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) - prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype()); - break; - case FunctionKind::Async: - break; - case FunctionKind::AsyncGenerator: - prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype()); - break; + auto prototype = Object::create_with_premade_shape(realm.intrinsics().normal_function_prototype_shape()); + prototype->put_direct(realm.intrinsics().normal_function_prototype_constructor_offset(), this); + put_direct(realm.intrinsics().normal_function_prototype_offset(), prototype); + } else { + MUST(define_property_or_throw(vm.names.length, { .value = Value(m_function_length), .writable = false, .enumerable = false, .configurable = true })); + MUST(define_property_or_throw(vm.names.name, { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true })); + + if (!m_is_arrow_function) { + Object* prototype = nullptr; + switch (m_kind) { + case FunctionKind::Normal: + VERIFY_NOT_REACHED(); + break; + case FunctionKind::Generator: + // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) + prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype()); + break; + case FunctionKind::Async: + break; + case FunctionKind::AsyncGenerator: + prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype()); + break; + } + // 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances + // AsyncFunction instances do not have a prototype property as they are not constructible. + if (m_kind != FunctionKind::Async) + define_direct_property(vm.names.prototype, prototype, Attribute::Writable); } - // 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances - // AsyncFunction instances do not have a prototype property as they are not constructible. - if (m_kind != FunctionKind::Async) - define_direct_property(vm.names.prototype, prototype, Attribute::Writable); } } diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index c4d6de09429..50ea3cc782b 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -203,6 +203,15 @@ void Intrinsics::initialize_intrinsics(Realm& realm) m_normal_function_prototype_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable); m_normal_function_prototype_constructor_offset = m_normal_function_prototype_shape->lookup(vm.names.constructor.to_string_or_symbol()).value().offset; + m_normal_function_shape = heap().allocate(realm); + m_normal_function_shape->set_prototype_without_transition(m_function_prototype); + m_normal_function_shape->add_property_without_transition(vm.names.length, Attribute::Configurable); + m_normal_function_shape->add_property_without_transition(vm.names.name, Attribute::Configurable); + m_normal_function_shape->add_property_without_transition(vm.names.prototype, Attribute::Writable); + m_normal_function_length_offset = m_normal_function_shape->lookup(vm.names.length.to_string_or_symbol()).value().offset; + m_normal_function_name_offset = m_normal_function_shape->lookup(vm.names.name.to_string_or_symbol()).value().offset; + m_normal_function_prototype_offset = m_normal_function_shape->lookup(vm.names.prototype.to_string_or_symbol()).value().offset; + // Normally Realm::create() takes care of this, but these are allocated via Heap::allocate(). m_function_prototype->initialize(realm); m_object_prototype->initialize(realm); @@ -372,6 +381,7 @@ void Intrinsics::visit_edges(Visitor& visitor) visitor.visit(m_new_object_shape); visitor.visit(m_iterator_result_object_shape); visitor.visit(m_normal_function_prototype_shape); + visitor.visit(m_normal_function_shape); visitor.visit(m_proxy_constructor); visitor.visit(m_async_from_sync_iterator_prototype); visitor.visit(m_async_generator_prototype); diff --git a/Libraries/LibJS/Runtime/Intrinsics.h b/Libraries/LibJS/Runtime/Intrinsics.h index cc10f933493..a38f2894504 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.h +++ b/Libraries/LibJS/Runtime/Intrinsics.h @@ -31,6 +31,11 @@ public: [[nodiscard]] GC::Ref normal_function_prototype_shape() { return *m_normal_function_prototype_shape; } [[nodiscard]] u32 normal_function_prototype_constructor_offset() const { return m_normal_function_prototype_constructor_offset; } + [[nodiscard]] GC::Ref normal_function_shape() { return *m_normal_function_shape; } + [[nodiscard]] u32 normal_function_length_offset() const { return m_normal_function_length_offset; } + [[nodiscard]] u32 normal_function_name_offset() const { return m_normal_function_name_offset; } + [[nodiscard]] u32 normal_function_prototype_offset() const { return m_normal_function_prototype_offset; } + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype GC::Ref proxy_constructor() { return *m_proxy_constructor; } @@ -139,6 +144,11 @@ private: GC::Ptr m_normal_function_prototype_shape; u32 m_normal_function_prototype_constructor_offset { 0 }; + GC::Ptr m_normal_function_shape; + u32 m_normal_function_length_offset { 0 }; + u32 m_normal_function_name_offset { 0 }; + u32 m_normal_function_prototype_offset { 0 }; + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype GC::Ptr m_proxy_constructor; diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 7f0bd7b5511..69a9172d7f7 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -97,6 +97,12 @@ void Object::initialize(Realm&) { } +void Object::unsafe_set_shape(Shape& shape) +{ + m_shape = shape; + m_storage.resize(shape.property_count()); +} + // 7.2 Testing and Comparison Operations, https://tc39.es/ecma262/#sec-testing-and-comparison-operations // 7.2.5 IsExtensible ( O ), https://tc39.es/ecma262/#sec-isextensible-o diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 6fdce3c335b..5ae213adcb8 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -246,6 +246,8 @@ protected: Object(ConstructWithPrototypeTag, Object& prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No); explicit Object(Shape&, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No); + void unsafe_set_shape(Shape&); + // [[Extensible]] bool m_is_extensible { true };