From f47a14b9d615c8b907b4db58f6acc2de24cd490c Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 7 Dec 2023 22:30:53 +0100 Subject: [PATCH] LibJS: Use a premade shape when creating iterator result objects Instead of going through the steps of creating an empty new object, and adding two properties ("value" and "done") to it, we can pre-bake a shape object and cache the property offsets. This makes creating iterator result objects in the runtime much faster. 47% speedup on this microbenchmark: function go(a) { for (const p of a) { } } const a = []; a.length = 1_000_000; go(a); --- Userland/Libraries/LibJS/Runtime/Intrinsics.cpp | 11 +++++++++++ Userland/Libraries/LibJS/Runtime/Intrinsics.h | 8 ++++++++ Userland/Libraries/LibJS/Runtime/Iterator.cpp | 6 +++--- Userland/Libraries/LibJS/Runtime/Object.cpp | 5 +++++ Userland/Libraries/LibJS/Runtime/Object.h | 1 + 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp index a988c84cf0..3a327924e0 100644 --- a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -190,6 +190,16 @@ ThrowCompletionOr Intrinsics::initialize_intrinsics(Realm& realm) m_new_ordinary_function_prototype_object_shape->set_prototype_without_transition(m_object_prototype); m_new_ordinary_function_prototype_object_shape->add_property_without_transition(vm.names.constructor, Attribute::Writable | Attribute::Configurable); + // OPTIMIZATION: A lot of runtime algorithms create an "iterator result" object. + // We pre-bake a shape for these objects and remember the property offsets. + // This allows us to construct them very quickly. + m_iterator_result_object_shape = heap().allocate_without_realm(realm); + m_iterator_result_object_shape->set_prototype_without_transition(m_object_prototype); + m_iterator_result_object_shape->add_property_without_transition(vm.names.value, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable); + m_iterator_result_object_shape->add_property_without_transition(vm.names.done, Attribute::Writable | Attribute::Configurable | Attribute::Enumerable); + m_iterator_result_object_value_offset = m_iterator_result_object_shape->lookup(vm.names.value.to_string_or_symbol()).value().offset; + m_iterator_result_object_done_offset = m_iterator_result_object_shape->lookup(vm.names.done.to_string_or_symbol()).value().offset; + // Normally Heap::allocate() takes care of this, but these are allocated via allocate_without_realm(). m_function_prototype->initialize(realm); m_object_prototype->initialize(realm); @@ -358,6 +368,7 @@ void Intrinsics::visit_edges(Visitor& visitor) visitor.visit(m_empty_object_shape); visitor.visit(m_new_object_shape); visitor.visit(m_new_ordinary_function_prototype_object_shape); + visitor.visit(m_iterator_result_object_shape); visitor.visit(m_proxy_constructor); visitor.visit(m_async_from_sync_iterator_prototype); visitor.visit(m_async_generator_prototype); diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.h b/Userland/Libraries/LibJS/Runtime/Intrinsics.h index 36d03e8923..6a5ba36c80 100644 --- a/Userland/Libraries/LibJS/Runtime/Intrinsics.h +++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.h @@ -23,6 +23,10 @@ public: NonnullGCPtr new_object_shape() { return *m_new_object_shape; } NonnullGCPtr new_ordinary_function_prototype_object_shape() { return *m_new_ordinary_function_prototype_object_shape; } + [[nodiscard]] NonnullGCPtr iterator_result_object_shape() { return *m_iterator_result_object_shape; } + [[nodiscard]] u32 iterator_result_object_value_offset() { return m_iterator_result_object_value_offset; } + [[nodiscard]] u32 iterator_result_object_done_offset() { return m_iterator_result_object_done_offset; } + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype NonnullGCPtr proxy_constructor() { return *m_proxy_constructor; } @@ -123,6 +127,10 @@ private: GCPtr m_new_object_shape; GCPtr m_new_ordinary_function_prototype_object_shape; + GCPtr m_iterator_result_object_shape; + u32 m_iterator_result_object_value_offset { 0 }; + u32 m_iterator_result_object_done_offset { 0 }; + // Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype GCPtr m_proxy_constructor; diff --git a/Userland/Libraries/LibJS/Runtime/Iterator.cpp b/Userland/Libraries/LibJS/Runtime/Iterator.cpp index d6ba888d02..664efc1bdd 100644 --- a/Userland/Libraries/LibJS/Runtime/Iterator.cpp +++ b/Userland/Libraries/LibJS/Runtime/Iterator.cpp @@ -264,13 +264,13 @@ NonnullGCPtr create_iterator_result_object(VM& vm, Value value, bool don auto& realm = *vm.current_realm(); // 1. Let obj be OrdinaryObjectCreate(%Object.prototype%). - auto object = Object::create(realm, realm.intrinsics().object_prototype()); + auto object = Object::create_with_premade_shape(realm.intrinsics().iterator_result_object_shape()); // 2. Perform ! CreateDataPropertyOrThrow(obj, "value", value). - MUST(object->create_data_property_or_throw(vm.names.value, value)); + object->put_direct(realm.intrinsics().iterator_result_object_value_offset(), value); // 3. Perform ! CreateDataPropertyOrThrow(obj, "done", done). - MUST(object->create_data_property_or_throw(vm.names.done, Value(done))); + object->put_direct(realm.intrinsics().iterator_result_object_done_offset(), Value(done)); // 4. Return obj. return object; diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 657d7ce29c..6377430447 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -37,6 +37,11 @@ NonnullGCPtr Object::create(Realm& realm, Object* prototype) return realm.heap().allocate(realm, ConstructWithPrototypeTag::Tag, *prototype); } +NonnullGCPtr Object::create_with_premade_shape(Shape& shape) +{ + return shape.heap().allocate(shape.realm(), shape); +} + Object::Object(GlobalObjectTag, Realm& realm, MayInterfereWithIndexedPropertyAccess may_interfere_with_indexed_property_access) : m_may_interfere_with_indexed_property_access(may_interfere_with_indexed_property_access == MayInterfereWithIndexedPropertyAccess::Yes) { diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 1525feee5f..9b8de50445 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -58,6 +58,7 @@ class Object : public Cell { public: static NonnullGCPtr create(Realm&, Object* prototype); + static NonnullGCPtr create_with_premade_shape(Shape&); virtual void initialize(Realm&) override; virtual ~Object();