From 4b3a87eb14ea94a61be087365cc6e7c79e9c8c2f Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Fri, 8 Aug 2025 03:46:16 +0200 Subject: [PATCH] LibJS: Add fast path for Array.prototype.shift Makes `MicroBench/array-prototype-shift.js` 100x faster on my machine. Progress on https://github.com/LadybirdBrowser/ladybird/issues/5725 --- Libraries/LibJS/Runtime/Array.cpp | 51 +++++++++++----------- Libraries/LibJS/Runtime/Array.h | 3 ++ Libraries/LibJS/Runtime/ArrayPrototype.cpp | 13 ++++++ 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Libraries/LibJS/Runtime/Array.cpp b/Libraries/LibJS/Runtime/Array.cpp index ba8caa11762..49f0ced29d6 100644 --- a/Libraries/LibJS/Runtime/Array.cpp +++ b/Libraries/LibJS/Runtime/Array.cpp @@ -299,35 +299,36 @@ ThrowCompletionOr> Array::internal_get_own_property return Object::internal_get_own_property(property_key); } +bool Array::default_prototype_chain_intact() const +{ + auto const& intrinsics = m_realm->intrinsics(); + auto const* array_prototype = shape().prototype(); + if (!array_prototype) + return false; + if (!array_prototype->indexed_properties().is_empty()) + return false; + auto const& array_prototype_shape = shape().prototype()->shape(); + if (intrinsics.default_array_prototype_shape().ptr() != &array_prototype_shape) + return false; + + auto const* object_prototype = array_prototype_shape.prototype(); + if (!object_prototype) + return false; + if (!object_prototype->indexed_properties().is_empty()) + return false; + auto const& object_prototype_shape = array_prototype_shape.prototype()->shape(); + if (intrinsics.default_object_prototype_shape().ptr() != &object_prototype_shape) + return false; + if (object_prototype_shape.prototype()) + return false; + + return true; +} + ThrowCompletionOr Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) { auto& vm = this->vm(); - auto default_prototype_chain_intact = [&] { - auto const& intrinsics = m_realm->intrinsics(); - auto* array_prototype = shape().prototype(); - if (!array_prototype) - return false; - if (!array_prototype->indexed_properties().is_empty()) - return false; - auto& array_prototype_shape = shape().prototype()->shape(); - if (intrinsics.default_array_prototype_shape().ptr() != &array_prototype_shape) - return false; - - auto* object_prototype = array_prototype_shape.prototype(); - if (!object_prototype) - return false; - if (!object_prototype->indexed_properties().is_empty()) - return false; - auto& object_prototype_shape = array_prototype_shape.prototype()->shape(); - if (intrinsics.default_object_prototype_shape().ptr() != &object_prototype_shape) - return false; - if (object_prototype_shape.prototype()) - return false; - - return true; - }; - VERIFY(receiver.is_object()); auto& receiver_object = receiver.as_object(); diff --git a/Libraries/LibJS/Runtime/Array.h b/Libraries/LibJS/Runtime/Array.h index 6ce9add9c7b..351484be0f0 100644 --- a/Libraries/LibJS/Runtime/Array.h +++ b/Libraries/LibJS/Runtime/Array.h @@ -57,8 +57,11 @@ public: [[nodiscard]] bool length_is_writable() const { return m_length_writable; } + bool is_proxy_target() const { return m_is_proxy_target; } void set_is_proxy_target(bool is_proxy_target) { m_is_proxy_target = is_proxy_target; } + bool default_prototype_chain_intact() const; + virtual void visit_edges(Cell::Visitor& visitor) override; protected: diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Libraries/LibJS/Runtime/ArrayPrototype.cpp index f174886d40b..02b8cfe573e 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -1220,6 +1220,19 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift) TRY(this_object->set(vm.names.length, Value(0), Object::ShouldThrowExceptions::Yes)); return js_undefined(); } + + // OPTIMIZATION: If this object is Array that: + // - is not a proxy target, which means get/set/has/delete will not trap. + // - has intact prototype chain, which means we don't have to worry about getters/setters potentially defined for holes. + // - has simple storage type, which means all values have default attributes (if some elements have configurable=false, we cannot use fast path, because delete operation will fail). + // then we could take a fast path by directly taking first element from indexed storage. + if (auto* array = as_if(*this_object); array && !array->is_proxy_target() && array->default_prototype_chain_intact() && array->indexed_properties().storage()->is_simple_storage()) { + auto first = array->indexed_properties().storage()->take_first().value; + if (first.is_special_empty_value()) + return js_undefined(); + return first; + } + auto first = TRY(this_object->get(0)); for (size_t k = 1; k < length; ++k) { size_t from = k;