ladybird/Libraries/LibJS/Runtime/ArrayIterator.cpp
Aliaksandr Kalenik 646457099c LibJS: Avoid IteratorRecord GC-allocation in GetIterator instruction
With this change, `GetIterator` no longer GC-allocates an
`IteratorRecord`. Instead, it stores the iterator record fields in
bytecode registers. This avoids per-iteration allocations in patterns
like: `for (let [x] of array) {}`.

`IteratorRecord` now inherits from `IteratorRecordImpl`, which holds the
iteration state. This allows the existing iteration helpers
(`iterator_next()`, `iterator_step()`, etc.) operate on both the
GC-allocated and the register-backed forms.

Microbenchmarks:
1.1x array-destructuring-assignment-rest.js
1.226x array-destructuring-assignment.js
2025-11-02 20:05:47 +01:00

157 lines
5.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayIterator.h>
#include <LibJS/Runtime/ArrayIteratorPrototype.h>
#include <LibJS/Runtime/TypedArray.h>
namespace JS {
GC_DEFINE_ALLOCATOR(ArrayIterator);
// 23.1.5.1 CreateArrayIterator ( array, kind ), https://tc39.es/ecma262/#sec-createarrayiterator
GC::Ref<ArrayIterator> ArrayIterator::create(Realm& realm, Value array, Object::PropertyKind iteration_kind)
{
// 1. Let iterator be OrdinaryObjectCreate(%ArrayIteratorPrototype%, « [[IteratedArrayLike]], [[ArrayLikeNextIndex]], [[ArrayLikeIterationKind]] »).
// 2. Set iterator.[[IteratedArrayLike]] to array.
// 3. Set iterator.[[ArrayLikeNextIndex]] to 0.
// 4. Set iterator.[[ArrayLikeIterationKind]] to kind.
// 5. Return iterator.
return realm.create<ArrayIterator>(array, iteration_kind, realm.intrinsics().array_iterator_prototype());
}
ArrayIterator::ArrayIterator(Value array, Object::PropertyKind iteration_kind, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_array(array)
, m_iteration_kind(iteration_kind)
{
}
void ArrayIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_array);
}
BuiltinIterator* ArrayIterator::as_builtin_iterator_if_next_is_not_redefined(Value next_method)
{
if (next_method.is_object()) {
auto const& next_function = next_method.as_object();
if (next_function.is_native_function()) {
auto const& native_function = static_cast<NativeFunction const&>(next_function);
if (native_function.is_array_prototype_next_builtin())
return this;
}
}
return nullptr;
}
ThrowCompletionOr<void> 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<TypedArrayBase&>(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<TypeError>(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<i32>(index) };
}
// 14. Else,
else {
// a. Let elementKey be ! ToString(indexNumber).
// b. Let elementValue be ? Get(array, elementKey).
auto element_value = TRY([&]() -> ThrowCompletionOr<Value> {
// 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<i32>(index)), element_value });
}
}
// 15. Return CreateIteratorResultObject(result, false).
value = result;
return {};
}
}