LibJS+LibWeb: Add fast path for builtin iterators in iterator_step()

We already have fast path for built-in iterators that skips `next()`
lookup and iteration result object allocation applied for `for..of` and
`for..in` loops. This change extends it to `iterator_step()` to cover
`Array.from()`, `[...arr]` and many other cases.

Makes following function go 2.35x faster on my computer:
```js
(function f() {
  let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  for (let i = 0; i < 1000000; i++) {
    let [a, ...rest] = arr;
  }
})();
```
This commit is contained in:
Aliaksandr Kalenik 2025-05-12 00:31:51 +03:00 committed by Alexander Kalenik
parent 31301ef08b
commit bb53485dea
Notes: github-actions[bot] 2025-05-13 12:15:28 +00:00
6 changed files with 38 additions and 18 deletions

View file

@ -203,13 +203,24 @@ ThrowCompletionOr<Value> iterator_value(VM& vm, Object& iterator_result)
} }
// 7.4.9 IteratorStep ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstep // 7.4.9 IteratorStep ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstep
ThrowCompletionOr<GC::Ptr<Object>> iterator_step(VM& vm, IteratorRecord& iterator_record) ThrowCompletionOr<IterationResultOrDone> iterator_step(VM& vm, IteratorRecord& iterator_record)
{ {
if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator_if_next_is_not_redefined()) {
Value value;
bool done = false;
TRY(builtin_iterator->next(vm, done, value));
if (done) {
iterator_record.done = true;
return ThrowCompletionOr<IterationResultOrDone> { IterationDone {} };
}
return ThrowCompletionOr<IterationResultOrDone> { IterationResult { done, value } };
}
// 1. Let result be ? IteratorNext(iteratorRecord). // 1. Let result be ? IteratorNext(iteratorRecord).
auto result = TRY(iterator_next(vm, iterator_record)); auto result = TRY(iterator_next(vm, iterator_record));
// 2. Let done be Completion(IteratorComplete(result)). // 2. Let done be Completion(IteratorComplete(result)).
auto done = iterator_complete(vm, result); auto done = result->get(vm.names.done);
// 3. If done is a throw completion, then // 3. If done is a throw completion, then
if (done.is_throw_completion()) { if (done.is_throw_completion()) {
@ -224,32 +235,32 @@ ThrowCompletionOr<GC::Ptr<Object>> iterator_step(VM& vm, IteratorRecord& iterato
auto done_value = done.release_value(); auto done_value = done.release_value();
// 5. If done is true, then // 5. If done is true, then
if (done_value) { if (done_value.to_boolean()) {
// a. Set iteratorRecord.[[Done]] to true. // a. Set iteratorRecord.[[Done]] to true.
iterator_record.done = true; iterator_record.done = true;
// b. Return DONE. // b. Return DONE.
return nullptr; return ThrowCompletionOr<IterationResultOrDone> { IterationDone {} };
} }
// 6. Return result. // 6. Return result.
return result; return ThrowCompletionOr<IterationResultOrDone> { IterationResult { done_value, result->get(vm.names.value) } };
} }
// 7.4.10 IteratorStepValue ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstepvalue // 7.4.10 IteratorStepValue ( iteratorRecord ), https://tc39.es/ecma262/#sec-iteratorstepvalue
ThrowCompletionOr<Optional<Value>> iterator_step_value(VM& vm, IteratorRecord& iterator_record) ThrowCompletionOr<Optional<Value>> iterator_step_value(VM& vm, IteratorRecord& iterator_record)
{ {
// 1. Let result be ? IteratorStep(iteratorRecord). // 1. Let result be ? IteratorStep(iteratorRecord).
auto result = TRY(iterator_step(vm, iterator_record)); IterationResultOrDone result = TRY(iterator_step(vm, iterator_record));
// 2. If result is done, then // 2. If result is done, then
if (!result) { if (result.has<IterationDone>()) {
// a. Return DONE. // a. Return DONE.
return OptionalNone {}; return OptionalNone {};
} }
// 3. Let value be Completion(IteratorValue(result)). // 3. Let value be Completion(IteratorValue(result)).
auto value = iterator_value(vm, *result); auto& value = result.get<IterationResult>().value;
// 4. If value is a throw completion, then // 4. If value is a throw completion, then
if (value.is_throw_completion()) { if (value.is_throw_completion()) {
@ -363,8 +374,10 @@ ThrowCompletionOr<GC::RootVector<Value>> iterator_to_list(VM& vm, IteratorRecord
return values; return values;
} }
auto value = next.release_value();
// c. Append next to values. // c. Append next to values.
values.append(next.release_value()); values.append(value);
} }
} }

View file

@ -76,6 +76,13 @@ protected:
bool m_next_method_was_redefined { false }; bool m_next_method_was_redefined { false };
}; };
struct IterationResult {
ThrowCompletionOr<Value> done;
ThrowCompletionOr<Value> value;
};
struct IterationDone { };
using IterationResultOrDone = Variant<IterationResult, IterationDone>;
// 7.4.12 IfAbruptCloseIterator ( value, iteratorRecord ), https://tc39.es/ecma262/#sec-ifabruptcloseiterator // 7.4.12 IfAbruptCloseIterator ( value, iteratorRecord ), https://tc39.es/ecma262/#sec-ifabruptcloseiterator
#define TRY_OR_CLOSE_ITERATOR(vm, iterator_record, expression) \ #define TRY_OR_CLOSE_ITERATOR(vm, iterator_record, expression) \
({ \ ({ \
@ -101,7 +108,7 @@ ThrowCompletionOr<GC::Ref<IteratorRecord>> get_iterator_flattenable(VM&, Value,
ThrowCompletionOr<GC::Ref<Object>> iterator_next(VM&, IteratorRecord&, Optional<Value> = {}); ThrowCompletionOr<GC::Ref<Object>> iterator_next(VM&, IteratorRecord&, Optional<Value> = {});
ThrowCompletionOr<bool> iterator_complete(VM&, Object& iterator_result); ThrowCompletionOr<bool> iterator_complete(VM&, Object& iterator_result);
ThrowCompletionOr<Value> iterator_value(VM&, Object& iterator_result); ThrowCompletionOr<Value> iterator_value(VM&, Object& iterator_result);
ThrowCompletionOr<GC::Ptr<Object>> iterator_step(VM&, IteratorRecord&); ThrowCompletionOr<IterationResultOrDone> iterator_step(VM&, IteratorRecord&);
ThrowCompletionOr<Optional<Value>> iterator_step_value(VM&, IteratorRecord&); ThrowCompletionOr<Optional<Value>> iterator_step_value(VM&, IteratorRecord&);
Completion iterator_close(VM&, IteratorRecord const&, Completion); Completion iterator_close(VM&, IteratorRecord const&, Completion);
Completion async_iterator_close(VM&, IteratorRecord const&, Completion); Completion async_iterator_close(VM&, IteratorRecord const&, Completion);

View file

@ -128,10 +128,10 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
iterator.increment_counter(); iterator.increment_counter();
// ii. Let next be ? IteratorStep(iterated). // ii. Let next be ? IteratorStep(iterated).
auto next = TRY(iterator_step(vm, iterated)); IterationResultOrDone next = TRY(iterator_step(vm, iterated));
// iii. If next is DONE, return ReturnCompletion(undefined). // iii. If next is DONE, return ReturnCompletion(undefined).
if (!next) if (next.has<IterationDone>())
return iterator.result(js_undefined()); return iterator.result(js_undefined());
} }

View file

@ -346,12 +346,12 @@ static WebIDL::ExceptionOr<Vector<BaseKeyframe>> process_a_keyframes_argument(JS
auto next = TRY(JS::iterator_step(vm, iter)); auto next = TRY(JS::iterator_step(vm, iter));
// 3. If next is false abort this loop. // 3. If next is false abort this loop.
if (!next) if (!next.has<JS::IterationResult>())
break; break;
// 4. Let nextItem be IteratorValue(next). // 4. Let nextItem be IteratorValue(next).
// 5. Check the completion record of nextItem. // 5. Check the completion record of nextItem.
auto next_item = TRY(JS::iterator_value(vm, *next)); auto next_item = TRY(next.get<JS::IterationResult>().value);
// 6. If Type(nextItem) is not Undefined, Null or Object, then throw a TypeError and abort these steps. // 6. If Type(nextItem) is not Undefined, Null or Object, then throw a TypeError and abort these steps.
if (!next_item.is_nullish() && !next_item.is_object()) if (!next_item.is_nullish() && !next_item.is_object())

View file

@ -89,11 +89,11 @@ static JS::ThrowCompletionOr<Vector<String>> convert_value_to_sequence_of_string
auto next = TRY(JS::iterator_step(vm, iterator)); auto next = TRY(JS::iterator_step(vm, iterator));
// 2. If next is false, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj. // 2. If next is false, then return an IDL sequence value of type sequence<T> of length i, where the value of the element at index j is Sj.
if (!next) if (!next.has<JS::IterationResult>())
return sequence_of_strings; return sequence_of_strings;
// 3. Let nextItem be ? IteratorValue(next). // 3. Let nextItem be ? IteratorValue(next).
auto next_item = TRY(JS::iterator_value(vm, *next)); auto next_item = TRY(next.get<JS::IterationResult>().value);
// 4. Initialize Si to the result of converting nextItem to an IDL value of type T. // 4. Initialize Si to the result of converting nextItem to an IDL value of type T.

View file

@ -1766,10 +1766,10 @@ void IDL::ParameterizedType::generate_sequence_from_iterable(SourceGenerator& ge
sequence_generator.append(R"~~~( sequence_generator.append(R"~~~(
for (;;) { for (;;) {
auto next@recursion_depth@ = TRY(JS::iterator_step(vm, @iterable_cpp_name@_iterator@recursion_depth@)); auto next@recursion_depth@ = TRY(JS::iterator_step(vm, @iterable_cpp_name@_iterator@recursion_depth@));
if (!next@recursion_depth@) if (!next@recursion_depth@.has<JS::IterationResult>())
break; break;
auto next_item@recursion_depth@ = TRY(JS::iterator_value(vm, *next@recursion_depth@)); auto next_item@recursion_depth@ = TRY(next@recursion_depth@.get<JS::IterationResult>().value);
)~~~"); )~~~");
// FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here. // FIXME: Sequences types should be TypeWithExtendedAttributes, which would allow us to get [LegacyNullToEmptyString] here.