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
This commit is contained in:
Aliaksandr Kalenik 2025-08-08 03:46:16 +02:00 committed by Alexander Kalenik
commit 4b3a87eb14
Notes: github-actions[bot] 2025-08-08 16:11:20 +00:00
3 changed files with 42 additions and 25 deletions

View file

@ -299,34 +299,35 @@ ThrowCompletionOr<Optional<PropertyDescriptor>> Array::internal_get_own_property
return Object::internal_get_own_property(property_key);
}
ThrowCompletionOr<bool> Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
bool Array::default_prototype_chain_intact() const
{
auto& vm = this->vm();
auto default_prototype_chain_intact = [&] {
auto const& intrinsics = m_realm->intrinsics();
auto* array_prototype = shape().prototype();
auto const* 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();
auto const& 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();
auto const* 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();
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<bool> Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
{
auto& vm = this->vm();
VERIFY(receiver.is_object());
auto& receiver_object = receiver.as_object();

View file

@ -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:

View file

@ -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<Array>(*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;