From ab52d86a69e677fa0129b8f77cf85162898134fe Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 30 Apr 2025 16:29:41 +0300 Subject: [PATCH] LibJS: Allow advancing built-in iterators without result object creation Expose a method on built-in iterators that allows retrieving the next iteration result without allocating a JS::Object. This change is a preparation for optimizing for..of and for..in loops. --- Libraries/LibJS/Forward.h | 1 + Libraries/LibJS/Runtime/ArrayIterator.cpp | 107 +++++++++++++++++- Libraries/LibJS/Runtime/ArrayIterator.h | 13 +-- .../LibJS/Runtime/ArrayIteratorPrototype.cpp | 98 +--------------- Libraries/LibJS/Runtime/Iterator.h | 6 + Libraries/LibJS/Runtime/MapIterator.cpp | 32 +++++- Libraries/LibJS/Runtime/MapIterator.h | 9 +- .../LibJS/Runtime/MapIteratorPrototype.cpp | 26 +---- Libraries/LibJS/Runtime/Object.h | 2 + Libraries/LibJS/Runtime/SetIterator.cpp | 29 ++++- Libraries/LibJS/Runtime/SetIterator.h | 10 +- .../LibJS/Runtime/SetIteratorPrototype.cpp | 24 +--- Libraries/LibJS/Runtime/StringIterator.cpp | 22 ++++ Libraries/LibJS/Runtime/StringIterator.h | 8 +- .../LibJS/Runtime/StringIteratorPrototype.cpp | 16 +-- 15 files changed, 234 insertions(+), 169 deletions(-) diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index ae0dd77b743..3613eecbe53 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -158,6 +158,7 @@ class Agent; struct AsyncGeneratorRequest; class BigInt; class BoundFunction; +class BuiltinIterator; struct CachedSourceRange; class Cell; class ClassExpression; diff --git a/Libraries/LibJS/Runtime/ArrayIterator.cpp b/Libraries/LibJS/Runtime/ArrayIterator.cpp index 9cc86572c92..6d8107f6868 100644 --- a/Libraries/LibJS/Runtime/ArrayIterator.cpp +++ b/Libraries/LibJS/Runtime/ArrayIterator.cpp @@ -4,8 +4,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include -#include +#include namespace JS { @@ -35,4 +36,108 @@ void ArrayIterator::visit_edges(Cell::Visitor& visitor) visitor.visit(m_array); } +ThrowCompletionOr ArrayIterator::next(VM& vm, bool& done, Value& value) +{ + // 1. Let O be the this value. + // 2. If O is not an Object, throw a TypeError exception. + // 3. If O does not have all of the internal slots of an Array Iterator Instance (23.1.5.3), throw a TypeError exception. + + // 4. Let array be O.[[IteratedArrayLike]]. + auto target_array = m_array; + + // 5. If array is undefined, return CreateIteratorResultObject(undefined, true). + if (target_array.is_undefined()) { + value = js_undefined(); + done = true; + return {}; + } + + VERIFY(target_array.is_object()); + auto& array = target_array.as_object(); + + // 6. Let index be O.[[ArrayLikeNextIndex]]. + auto index = m_index; + + // 7. Let kind be O.[[ArrayLikeIterationKind]]. + auto kind = m_iteration_kind; + + size_t length = 0; + + // 8. If array has a [[TypedArrayName]] internal slot, then + if (array.is_typed_array()) { + auto& typed_array = static_cast(array); + + // a. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(array, SEQ-CST). + auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::SeqCst); + + // b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(typed_array_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); + + // c. Let len be TypedArrayLength(taRecord). + length = typed_array_length(typed_array_record); + } + // 9. Else, + else { + // a. Let len be ? LengthOfArrayLike(array). + length = TRY(length_of_array_like(vm, array)); + } + + // 10. If index ≥ len, then + if (index >= length) { + // a. Set O.[[IteratedArrayLike]] to undefined. + m_array = js_undefined(); + + // b. Return CreateIteratorResultObject(undefined, true). + value = js_undefined(); + done = true; + return {}; + } + + // 11. Set O.[[ArrayLikeNextIndex]] to index + 1. + m_index++; + + // 12. Let indexNumber be 𝔽(index). + + Value result; + + // 13. If kind is KEY, then + if (kind == PropertyKind::Key) { + // a. Let result be indexNumber. + result = Value { static_cast(index) }; + } + // 14. Else, + else { + // a. Let elementKey be ! ToString(indexNumber). + // b. Let elementValue be ? Get(array, elementKey). + auto element_value = TRY([&]() -> ThrowCompletionOr { + // OPTIMIZATION: For objects that don't interfere with indexed property access, we try looking directly at storage. + if (!array.may_interfere_with_indexed_property_access() && array.indexed_properties().has_index(index)) { + if (auto value = array.indexed_properties().get(index)->value; !value.is_accessor()) + return value; + } + + return array.get(index); + }()); + + // c. If kind is VALUE, then + if (kind == PropertyKind::Value) { + // i. Let result be elementValue. + result = element_value; + } + // d. Else, + else { + // i. Assert: kind is KEY+VALUE. + VERIFY(kind == PropertyKind::KeyAndValue); + + // ii. Let result be CreateArrayFromList(« indexNumber, elementValue »). + result = Array::create_from(*vm.current_realm(), { Value(static_cast(index)), element_value }); + } + } + + // 15. Return CreateIteratorResultObject(result, false). + value = result; + return {}; +} + } diff --git a/Libraries/LibJS/Runtime/ArrayIterator.h b/Libraries/LibJS/Runtime/ArrayIterator.h index 86fe80612de..62e29b5fefd 100644 --- a/Libraries/LibJS/Runtime/ArrayIterator.h +++ b/Libraries/LibJS/Runtime/ArrayIterator.h @@ -6,11 +6,13 @@ #pragma once +#include #include namespace JS { -class ArrayIterator final : public Object { +class ArrayIterator final : public Object + , public BuiltinIterator { JS_OBJECT(ArrayIterator, Object); GC_DECLARE_ALLOCATOR(ArrayIterator); @@ -19,13 +21,8 @@ public: virtual ~ArrayIterator() override = default; - Value array() const { return m_array; } - void set_array(Value array) { m_array = array; } - - Object::PropertyKind iteration_kind() const { return m_iteration_kind; } - - size_t index() const { return m_index; } - void set_index(size_t index) { m_index = index; } + BuiltinIterator* as_builtin_iterator() override { return this; } + ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: ArrayIterator(Value array, Object::PropertyKind iteration_kind, Object& prototype); diff --git a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp index d16027cd727..e19bb7ecc14 100644 --- a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp @@ -35,103 +35,13 @@ void ArrayIteratorPrototype::initialize(Realm& realm) // 23.1.5.2.1 %ArrayIteratorPrototype%.next ( ), https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next) { - auto& realm = *vm.current_realm(); - - // 1. Let O be the this value. - // 2. If O is not an Object, throw a TypeError exception. - // 3. If O does not have all of the internal slots of an Array Iterator Instance (23.1.5.3), throw a TypeError exception. auto iterator = TRY(typed_this_value(vm)); - // 4. Let array be O.[[IteratedArrayLike]]. - auto target_array = iterator->array(); + Value value; + bool done = false; + TRY(iterator->next(vm, done, value)); - // 5. If array is undefined, return CreateIteratorResultObject(undefined, true). - if (target_array.is_undefined()) - return create_iterator_result_object(vm, js_undefined(), true); - - VERIFY(target_array.is_object()); - auto& array = target_array.as_object(); - - // 6. Let index be O.[[ArrayLikeNextIndex]]. - auto index = iterator->index(); - - // 7. Let kind be O.[[ArrayLikeIterationKind]]. - auto kind = iterator->iteration_kind(); - - size_t length = 0; - - // 8. If array has a [[TypedArrayName]] internal slot, then - if (array.is_typed_array()) { - auto& typed_array = static_cast(array); - - // a. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(array, SEQ-CST). - auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::SeqCst); - - // b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. - if (is_typed_array_out_of_bounds(typed_array_record)) - return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); - - // c. Let len be TypedArrayLength(taRecord). - length = typed_array_length(typed_array_record); - } - // 9. Else, - else { - // a. Let len be ? LengthOfArrayLike(array). - length = TRY(length_of_array_like(vm, array)); - } - - // 10. If index ≥ len, then - if (index >= length) { - // a. Set O.[[IteratedArrayLike]] to undefined. - iterator->set_array(js_undefined()); - - // b. Return CreateIteratorResultObject(undefined, true). - return create_iterator_result_object(vm, js_undefined(), true); - } - - // 11. Set O.[[ArrayLikeNextIndex]] to index + 1. - iterator->set_index(index + 1); - - // 12. Let indexNumber be 𝔽(index). - - Value result; - - // 13. If kind is KEY, then - if (kind == PropertyKind::Key) { - // a. Let result be indexNumber. - result = Value { static_cast(index) }; - } - // 14. Else, - else { - // a. Let elementKey be ! ToString(indexNumber). - // b. Let elementValue be ? Get(array, elementKey). - auto element_value = TRY([&]() -> ThrowCompletionOr { - // OPTIMIZATION: For objects that don't interfere with indexed property access, we try looking directly at storage. - if (!array.may_interfere_with_indexed_property_access() && array.indexed_properties().has_index(index)) { - if (auto value = array.indexed_properties().get(index)->value; !value.is_accessor()) - return value; - } - - return array.get(index); - }()); - - // c. If kind is VALUE, then - if (kind == PropertyKind::Value) { - // i. Let result be elementValue. - result = element_value; - } - // d. Else, - else { - // i. Assert: kind is KEY+VALUE. - VERIFY(kind == PropertyKind::KeyAndValue); - - // ii. Let result be CreateArrayFromList(« indexNumber, elementValue »). - result = Array::create_from(realm, { Value(static_cast(index)), element_value }); - } - } - - // 15. Return CreateIteratorResultObject(result, false). - return create_iterator_result_object(vm, result, false); + return create_iterator_result_object(vm, value, done); } } diff --git a/Libraries/LibJS/Runtime/Iterator.h b/Libraries/LibJS/Runtime/Iterator.h index 5de60f0c6e7..a6904513798 100644 --- a/Libraries/LibJS/Runtime/Iterator.h +++ b/Libraries/LibJS/Runtime/Iterator.h @@ -67,6 +67,12 @@ enum class PrimitiveHandling { RejectPrimitives, }; +class BuiltinIterator { +public: + virtual ~BuiltinIterator() = default; + virtual ThrowCompletionOr next(VM&, bool& done, Value& value) = 0; +}; + // 7.4.12 IfAbruptCloseIterator ( value, iteratorRecord ), https://tc39.es/ecma262/#sec-ifabruptcloseiterator #define TRY_OR_CLOSE_ITERATOR(vm, iterator_record, expression) \ ({ \ diff --git a/Libraries/LibJS/Runtime/MapIterator.cpp b/Libraries/LibJS/Runtime/MapIterator.cpp index 7d498813972..4667a214332 100644 --- a/Libraries/LibJS/Runtime/MapIterator.cpp +++ b/Libraries/LibJS/Runtime/MapIterator.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include namespace JS { @@ -30,4 +30,34 @@ void MapIterator::visit_edges(Cell::Visitor& visitor) visitor.visit(m_map); } +ThrowCompletionOr MapIterator::next(VM& vm, bool& done, Value& value) +{ + if (m_done) { + done = true; + value = js_undefined(); + return {}; + } + + if (m_iterator.is_end()) { + m_done = true; + done = true; + value = js_undefined(); + return {}; + } + + auto entry = *m_iterator; + ++m_iterator; + if (m_iteration_kind == Object::PropertyKind::Key) { + value = entry.key; + return {}; + } + if (m_iteration_kind == Object::PropertyKind::Value) { + value = entry.value; + return {}; + } + + value = Array::create_from(*vm.current_realm(), { entry.key, entry.value }); + return {}; +} + } diff --git a/Libraries/LibJS/Runtime/MapIterator.h b/Libraries/LibJS/Runtime/MapIterator.h index e2d4f1fd31a..2e55cddb327 100644 --- a/Libraries/LibJS/Runtime/MapIterator.h +++ b/Libraries/LibJS/Runtime/MapIterator.h @@ -6,12 +6,14 @@ #pragma once +#include #include #include namespace JS { -class MapIterator final : public Object { +class MapIterator final : public Object + , public BuiltinIterator { JS_OBJECT(MapIterator, Object); GC_DECLARE_ALLOCATOR(MapIterator); @@ -20,9 +22,8 @@ public: virtual ~MapIterator() override = default; - Map& map() const { return m_map; } - bool done() const { return m_done; } - Object::PropertyKind iteration_kind() const { return m_iteration_kind; } + BuiltinIterator* as_builtin_iterator() override { return this; } + ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: friend class MapIteratorPrototype; diff --git a/Libraries/LibJS/Runtime/MapIteratorPrototype.cpp b/Libraries/LibJS/Runtime/MapIteratorPrototype.cpp index 806122776e9..657e2e385a5 100644 --- a/Libraries/LibJS/Runtime/MapIteratorPrototype.cpp +++ b/Libraries/LibJS/Runtime/MapIteratorPrototype.cpp @@ -6,8 +6,6 @@ #include #include -#include -#include #include #include @@ -32,27 +30,13 @@ void MapIteratorPrototype::initialize(Realm& realm) // 24.1.5.2.1 %MapIteratorPrototype%.next ( ), https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next JS_DEFINE_NATIVE_FUNCTION(MapIteratorPrototype::next) { - auto& realm = *vm.current_realm(); + auto iterator = TRY(typed_this_value(vm)); - auto map_iterator = TRY(typed_this_value(vm)); - if (map_iterator->done()) - return create_iterator_result_object(vm, js_undefined(), true); + Value value; + bool done = false; + TRY(iterator->next(vm, done, value)); - if (map_iterator->m_iterator.is_end()) { - map_iterator->m_done = true; - return create_iterator_result_object(vm, js_undefined(), true); - } - - auto iteration_kind = map_iterator->iteration_kind(); - - auto entry = *map_iterator->m_iterator; - ++map_iterator->m_iterator; - if (iteration_kind == Object::PropertyKind::Key) - return create_iterator_result_object(vm, entry.key, false); - if (iteration_kind == Object::PropertyKind::Value) - return create_iterator_result_object(vm, entry.value, false); - - return create_iterator_result_object(vm, Array::create_from(realm, { entry.key, entry.value }), false); + return create_iterator_result_object(vm, value, done); } } diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 8e7e1bbf129..b6e4c3b276d 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -212,6 +212,8 @@ public: virtual bool is_array_iterator() const { return false; } virtual bool is_raw_json_object() const { return false; } + virtual BuiltinIterator* as_builtin_iterator() { return nullptr; } + // B.3.7 The [[IsHTMLDDA]] Internal Slot, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot virtual bool is_htmldda() const { return false; } diff --git a/Libraries/LibJS/Runtime/SetIterator.cpp b/Libraries/LibJS/Runtime/SetIterator.cpp index 5d6cd4dedda..02188e96555 100644 --- a/Libraries/LibJS/Runtime/SetIterator.cpp +++ b/Libraries/LibJS/Runtime/SetIterator.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include namespace JS { @@ -30,4 +30,31 @@ void SetIterator::visit_edges(Cell::Visitor& visitor) visitor.visit(m_set); } +ThrowCompletionOr SetIterator::next(VM& vm, bool& done, Value& value) +{ + if (m_done) { + done = true; + value = js_undefined(); + return {}; + } + + if (m_iterator == m_set->end()) { + m_done = true; + done = true; + value = js_undefined(); + return {}; + } + + VERIFY(m_iteration_kind != Object::PropertyKind::Key); + + value = (*m_iterator).key; + ++m_iterator; + if (m_iteration_kind == Object::PropertyKind::Value) { + return {}; + } + + value = Array::create_from(*vm.current_realm(), { value, value }); + return {}; +} + } diff --git a/Libraries/LibJS/Runtime/SetIterator.h b/Libraries/LibJS/Runtime/SetIterator.h index 4ad95c18da5..9d63a543e73 100644 --- a/Libraries/LibJS/Runtime/SetIterator.h +++ b/Libraries/LibJS/Runtime/SetIterator.h @@ -6,13 +6,14 @@ #pragma once -#include +#include #include #include namespace JS { -class SetIterator final : public Object { +class SetIterator final : public Object + , public BuiltinIterator { JS_OBJECT(SetIterator, Object); GC_DECLARE_ALLOCATOR(SetIterator); @@ -21,9 +22,8 @@ public: virtual ~SetIterator() override = default; - Set& set() const { return m_set; } - bool done() const { return m_done; } - Object::PropertyKind iteration_kind() const { return m_iteration_kind; } + BuiltinIterator* as_builtin_iterator() override { return this; } + ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: friend class SetIteratorPrototype; diff --git a/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp b/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp index ec4c6a86b39..53fed15bbb3 100644 --- a/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp +++ b/Libraries/LibJS/Runtime/SetIteratorPrototype.cpp @@ -34,27 +34,13 @@ void SetIteratorPrototype::initialize(Realm& realm) // 24.2.5.2.1 %SetIteratorPrototype%.next ( ), https://tc39.es/ecma262/#sec-%setiteratorprototype%.next JS_DEFINE_NATIVE_FUNCTION(SetIteratorPrototype::next) { - auto& realm = *vm.current_realm(); + auto iterator = TRY(typed_this_value(vm)); - auto set_iterator = TRY(typed_this_value(vm)); - if (set_iterator->done()) - return create_iterator_result_object(vm, js_undefined(), true); + Value value; + bool done = false; + TRY(iterator->next(vm, done, value)); - auto& set = set_iterator->set(); - if (set_iterator->m_iterator == set.end()) { - set_iterator->m_done = true; - return create_iterator_result_object(vm, js_undefined(), true); - } - - auto iteration_kind = set_iterator->iteration_kind(); - VERIFY(iteration_kind != Object::PropertyKind::Key); - - auto value = (*set_iterator->m_iterator).key; - ++set_iterator->m_iterator; - if (iteration_kind == Object::PropertyKind::Value) - return create_iterator_result_object(vm, value, false); - - return create_iterator_result_object(vm, Array::create_from(realm, { value, value }), false); + return create_iterator_result_object(vm, value, done); } } diff --git a/Libraries/LibJS/Runtime/StringIterator.cpp b/Libraries/LibJS/Runtime/StringIterator.cpp index 651e2e25d1e..cdfe30aa84b 100644 --- a/Libraries/LibJS/Runtime/StringIterator.cpp +++ b/Libraries/LibJS/Runtime/StringIterator.cpp @@ -24,4 +24,26 @@ StringIterator::StringIterator(String string, Object& prototype) { } +ThrowCompletionOr StringIterator::next(VM& vm, bool& done, Value& value) +{ + if (m_done) { + done = true; + value = js_undefined(); + return {}; + } + + if (m_iterator.done()) { + m_done = true; + done = true; + value = js_undefined(); + return {}; + } + + auto code_point = String::from_code_point(*m_iterator); + ++m_iterator; + + value = PrimitiveString::create(vm, move(code_point)); + return {}; +} + } diff --git a/Libraries/LibJS/Runtime/StringIterator.h b/Libraries/LibJS/Runtime/StringIterator.h index dbb3c10663b..154f1832dff 100644 --- a/Libraries/LibJS/Runtime/StringIterator.h +++ b/Libraries/LibJS/Runtime/StringIterator.h @@ -8,11 +8,13 @@ #include #include +#include #include namespace JS { -class StringIterator final : public Object { +class StringIterator final : public Object + , public BuiltinIterator { JS_OBJECT(StringIterator, Object); GC_DECLARE_ALLOCATOR(StringIterator); @@ -21,8 +23,8 @@ public: virtual ~StringIterator() override = default; - Utf8CodePointIterator& iterator() { return m_iterator; } - bool done() const { return m_done; } + BuiltinIterator* as_builtin_iterator() override { return this; } + ThrowCompletionOr next(VM&, bool& done, Value& value) override; private: explicit StringIterator(String string, Object& prototype); diff --git a/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp b/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp index 56d1378a152..57f6e29eb4b 100644 --- a/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp +++ b/Libraries/LibJS/Runtime/StringIteratorPrototype.cpp @@ -33,20 +33,12 @@ void StringIteratorPrototype::initialize(Realm& realm) JS_DEFINE_NATIVE_FUNCTION(StringIteratorPrototype::next) { auto iterator = TRY(typed_this_value(vm)); - if (iterator->done()) - return create_iterator_result_object(vm, js_undefined(), true); - auto& utf8_iterator = iterator->iterator(); + Value value; + bool done = false; + TRY(iterator->next(vm, done, value)); - if (utf8_iterator.done()) { - iterator->m_done = true; - return create_iterator_result_object(vm, js_undefined(), true); - } - - auto code_point = String::from_code_point(*utf8_iterator); - ++utf8_iterator; - - return create_iterator_result_object(vm, PrimitiveString::create(vm, move(code_point)), false); + return create_iterator_result_object(vm, value, done); } }