1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 07:37:35 +00:00

LibJS: Stop converting between Object <-> IteratorRecord all the time

This patch makes IteratorRecord an Object. Although it's not exposed to
author code, this does allow us to store it in a VM register.

Now that we can store it in a VM register, we don't need to convert it
back and forth between IteratorRecord and Object when accessing it from
bytecode.

The big win here is avoiding 3 [[Get]] accesses on every iteration step
of for..of loops. There are also a bunch of smaller efficiencies gained.

20% speed-up on this microbenchmark:

    function go(a) {
        for (const p of a) {
        }
    }
    const a = [];
    a.length = 1_000_000;
    go(a);
This commit is contained in:
Andreas Kling 2023-12-07 10:44:41 +01:00
parent 4966c083df
commit 4699c81fc1
23 changed files with 226 additions and 144 deletions

View file

@ -338,7 +338,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
}
// e. Let iteratorRecord be undefined.
Optional<IteratorRecord> iterator_record;
GCPtr<IteratorRecord> iterator_record;
// f. If usingAsyncIterator is not undefined, then
if (using_async_iterator) {
@ -354,7 +354,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
}
// h. If iteratorRecord is not undefined, then
if (iterator_record.has_value()) {
if (iterator_record) {
GCPtr<Object> array;
// i. If IsConstructor(C) is true, then
@ -377,7 +377,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
auto error = vm.throw_completion<TypeError>(ErrorType::ArrayMaxSize);
// b. Return ? AsyncIteratorClose(iteratorRecord, error).
return *TRY(async_iterator_close(vm, iterator_record.value(), move(error)));
return *TRY(async_iterator_close(vm, *iterator_record, move(error)));
}
// 2. Let Pk be ! ToString(𝔽(k)).
@ -433,7 +433,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
// b. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
if (mapped_value_or_error.is_error()) {
TRY(async_iterator_close(vm, iterator_record.value(), mapped_value_or_error));
TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error));
return mapped_value_or_error;
}
@ -442,7 +442,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
// d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
if (mapped_value_or_error.is_error()) {
TRY(async_iterator_close(vm, iterator_record.value(), mapped_value_or_error));
TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error));
return mapped_value_or_error;
}
@ -458,7 +458,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async)
// 9. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus).
if (define_status.is_error())
return *TRY(iterator_close(vm, iterator_record.value(), define_status.release_error()));
return *TRY(iterator_close(vm, *iterator_record, define_status.release_error()));
// 10. Set k to k + 1.
}

View file

@ -13,12 +13,12 @@ namespace JS {
JS_DEFINE_ALLOCATOR(AsyncFromSyncIterator);
NonnullGCPtr<AsyncFromSyncIterator> AsyncFromSyncIterator::create(Realm& realm, IteratorRecord sync_iterator_record)
NonnullGCPtr<AsyncFromSyncIterator> AsyncFromSyncIterator::create(Realm& realm, NonnullGCPtr<IteratorRecord> sync_iterator_record)
{
return realm.heap().allocate<AsyncFromSyncIterator>(realm, realm, sync_iterator_record);
}
AsyncFromSyncIterator::AsyncFromSyncIterator(Realm& realm, IteratorRecord sync_iterator_record)
AsyncFromSyncIterator::AsyncFromSyncIterator(Realm& realm, NonnullGCPtr<IteratorRecord> sync_iterator_record)
: Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().async_from_sync_iterator_prototype())
, m_sync_iterator_record(sync_iterator_record)
{
@ -27,8 +27,7 @@ AsyncFromSyncIterator::AsyncFromSyncIterator(Realm& realm, IteratorRecord sync_i
void AsyncFromSyncIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_sync_iterator_record.iterator);
visitor.visit(m_sync_iterator_record.next_method);
visitor.visit(m_sync_iterator_record);
}
}

View file

@ -18,7 +18,7 @@ class AsyncFromSyncIterator final : public Object {
JS_DECLARE_ALLOCATOR(AsyncFromSyncIterator);
public:
static NonnullGCPtr<AsyncFromSyncIterator> create(Realm&, IteratorRecord sync_iterator_record);
static NonnullGCPtr<AsyncFromSyncIterator> create(Realm&, NonnullGCPtr<IteratorRecord> sync_iterator_record);
virtual ~AsyncFromSyncIterator() override = default;
@ -28,9 +28,9 @@ public:
IteratorRecord const& sync_iterator_record() const { return m_sync_iterator_record; }
private:
AsyncFromSyncIterator(Realm&, IteratorRecord sync_iterator_record);
AsyncFromSyncIterator(Realm&, NonnullGCPtr<IteratorRecord> sync_iterator_record);
IteratorRecord m_sync_iterator_record; // [[SyncIteratorRecord]]
NonnullGCPtr<IteratorRecord> m_sync_iterator_record; // [[SyncIteratorRecord]]
};
}

View file

@ -199,7 +199,7 @@ JS_DEFINE_NATIVE_FUNCTION(AsyncFromSyncIteratorPrototype::throw_)
}
// 27.1.4.1 CreateAsyncFromSyncIterator ( syncIteratorRecord ), https://tc39.es/ecma262/#sec-createasyncfromsynciterator
IteratorRecord create_async_from_sync_iterator(VM& vm, IteratorRecord sync_iterator_record)
NonnullGCPtr<IteratorRecord> create_async_from_sync_iterator(VM& vm, NonnullGCPtr<IteratorRecord> sync_iterator_record)
{
auto& realm = *vm.current_realm();
@ -211,7 +211,7 @@ IteratorRecord create_async_from_sync_iterator(VM& vm, IteratorRecord sync_itera
auto next_method = MUST(async_iterator->get(vm.names.next));
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
auto iterator_record = IteratorRecord { .iterator = async_iterator, .next_method = next_method, .done = false };
auto iterator_record = vm.heap().allocate<IteratorRecord>(realm, realm, async_iterator, next_method, false);
// 5. Return iteratorRecord.
return iterator_record;

View file

@ -31,6 +31,6 @@ private:
JS_DECLARE_NATIVE_FUNCTION(throw_);
};
IteratorRecord create_async_from_sync_iterator(VM&, IteratorRecord sync_iterator);
NonnullGCPtr<IteratorRecord> create_async_from_sync_iterator(VM&, NonnullGCPtr<IteratorRecord> sync_iterator);
}

View file

@ -17,25 +17,26 @@
namespace JS {
JS_DEFINE_ALLOCATOR(Iterator);
JS_DEFINE_ALLOCATOR(IteratorRecord);
NonnullGCPtr<Iterator> Iterator::create(Realm& realm, Object& prototype, IteratorRecord iterated)
NonnullGCPtr<Iterator> Iterator::create(Realm& realm, Object& prototype, NonnullGCPtr<IteratorRecord> iterated)
{
return realm.heap().allocate<Iterator>(realm, prototype, move(iterated));
}
Iterator::Iterator(Object& prototype, IteratorRecord iterated)
Iterator::Iterator(Object& prototype, NonnullGCPtr<IteratorRecord> iterated)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_iterated(move(iterated))
{
}
Iterator::Iterator(Object& prototype)
: Iterator(prototype, {})
: Iterator(prototype, prototype.heap().allocate<IteratorRecord>(prototype.shape().realm(), prototype.shape().realm(), nullptr, js_undefined(), false))
{
}
// 7.4.2 GetIteratorFromMethod ( obj, method ), https://tc39.es/ecma262/#sec-getiteratorfrommethod
ThrowCompletionOr<IteratorRecord> get_iterator_from_method(VM& vm, Value object, NonnullGCPtr<FunctionObject> method)
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_from_method(VM& vm, Value object, NonnullGCPtr<FunctionObject> method)
{
// 1. Let iterator be ? Call(method, obj).
auto iterator = TRY(call(vm, *method, object));
@ -48,14 +49,15 @@ ThrowCompletionOr<IteratorRecord> get_iterator_from_method(VM& vm, Value object,
auto next_method = TRY(iterator.get(vm, vm.names.next));
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
auto iterator_record = IteratorRecord { .iterator = &iterator.as_object(), .next_method = next_method, .done = false };
auto& realm = *vm.current_realm();
auto iterator_record = vm.heap().allocate<IteratorRecord>(realm, realm, iterator.as_object(), next_method, false);
// 5. Return iteratorRecord.
return iterator_record;
}
// 7.4.3 GetIterator ( obj, kind ), https://tc39.es/ecma262/#sec-getiterator
ThrowCompletionOr<IteratorRecord> get_iterator(VM& vm, Value object, IteratorHint kind)
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator(VM& vm, Value object, IteratorHint kind)
{
JS::GCPtr<FunctionObject> method;
@ -95,20 +97,19 @@ ThrowCompletionOr<IteratorRecord> get_iterator(VM& vm, Value object, IteratorHin
}
// 2.1.1 GetIteratorDirect ( obj ), https://tc39.es/proposal-iterator-helpers/#sec-getiteratorflattenable
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM& vm, Object& object)
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_direct(VM& vm, Object& object)
{
// 1. Let nextMethod be ? Get(obj, "next").
auto next_method = TRY(object.get(vm.names.next));
// 2. Let iteratorRecord be Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
IteratorRecord iterator_record { .iterator = object, .next_method = next_method, .done = false };
// 3. Return iteratorRecord.
return iterator_record;
auto& realm = *vm.current_realm();
return vm.heap().allocate<IteratorRecord>(realm, realm, object, next_method, false);
}
// 2.1.2 GetIteratorFlattenable ( obj, stringHandling ), https://tc39.es/proposal-iterator-helpers/#sec-getiteratorflattenable
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM& vm, Value object, StringHandling string_handling)
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_flattenable(VM& vm, Value object, StringHandling string_handling)
{
// 1. If obj is not an Object, then
if (!object.is_object()) {
@ -320,4 +321,17 @@ Completion get_iterator_values(VM& vm, Value iterable, IteratorValueCallback cal
}
}
void Iterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_iterated);
}
void IteratorRecord::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(iterator);
visitor.visit(next_method);
}
}

View file

@ -17,26 +17,47 @@
namespace JS {
// 7.4.1 Iterator Records, https://tc39.es/ecma262/#sec-iterator-records
struct IteratorRecord {
class IteratorRecord final : public Object {
JS_OBJECT(IteratorRecord, Object);
JS_DECLARE_ALLOCATOR(IteratorRecord);
public:
IteratorRecord(Realm& realm, GCPtr<Object> iterator, Value next_method, bool done)
: Object(ConstructWithoutPrototypeTag::Tag, realm)
, iterator(iterator)
, next_method(next_method)
, done(done)
{
}
GCPtr<Object> iterator; // [[Iterator]]
Value next_method; // [[NextMethod]]
bool done { false }; // [[Done]]
private:
virtual void visit_edges(Cell::Visitor&) override;
virtual bool is_iterator_record() const override { return true; }
};
template<>
inline bool Object::fast_is<IteratorRecord>() const { return is_iterator_record(); }
class Iterator : public Object {
JS_OBJECT(Iterator, Object);
JS_DECLARE_ALLOCATOR(Iterator);
public:
static NonnullGCPtr<Iterator> create(Realm&, Object& prototype, IteratorRecord iterated);
static NonnullGCPtr<Iterator> create(Realm&, Object& prototype, NonnullGCPtr<IteratorRecord> iterated);
IteratorRecord const& iterated() const { return m_iterated; }
private:
Iterator(Object& prototype, IteratorRecord iterated);
Iterator(Object& prototype, NonnullGCPtr<IteratorRecord> iterated);
explicit Iterator(Object& prototype);
IteratorRecord m_iterated; // [[Iterated]]
virtual void visit_edges(Cell::Visitor&) override;
NonnullGCPtr<IteratorRecord> m_iterated; // [[Iterated]]
};
enum class IteratorHint {
@ -49,10 +70,10 @@ enum class StringHandling {
RejectStrings,
};
ThrowCompletionOr<IteratorRecord> get_iterator_from_method(VM&, Value, NonnullGCPtr<FunctionObject>);
ThrowCompletionOr<IteratorRecord> get_iterator(VM&, Value, IteratorHint);
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM&, Object&);
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM&, Value, StringHandling);
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_from_method(VM&, Value, NonnullGCPtr<FunctionObject>);
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator(VM&, Value, IteratorHint);
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_direct(VM&, Object&);
ThrowCompletionOr<NonnullGCPtr<IteratorRecord>> get_iterator_flattenable(VM&, Value, StringHandling);
ThrowCompletionOr<NonnullGCPtr<Object>> iterator_next(VM&, IteratorRecord const&, Optional<Value> = {});
ThrowCompletionOr<GCPtr<Object>> iterator_step(VM&, IteratorRecord const&);
ThrowCompletionOr<bool> iterator_complete(VM&, Object& iterator_result);

View file

@ -70,12 +70,12 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorConstructor::from)
auto iterator_record = TRY(get_iterator_flattenable(vm, object, StringHandling::IterateStrings));
// 2. Let hasInstance be ? OrdinaryHasInstance(%Iterator%, iteratorRecord.[[Iterator]]).
auto has_instance = TRY(ordinary_has_instance(vm, iterator_record.iterator, realm.intrinsics().iterator_constructor()));
auto has_instance = TRY(ordinary_has_instance(vm, iterator_record->iterator, realm.intrinsics().iterator_constructor()));
// 3. If hasInstance is true, then
if (has_instance.is_boolean() && has_instance.as_bool()) {
// a. Return iteratorRecord.[[Iterator]].
return iterator_record.iterator;
return iterator_record->iterator;
}
// 4. Let wrapper be OrdinaryObjectCreate(%WrapForValidIteratorPrototype%, « [[Iterated]] »).

View file

@ -13,12 +13,12 @@ namespace JS {
JS_DEFINE_ALLOCATOR(IteratorHelper);
ThrowCompletionOr<NonnullGCPtr<IteratorHelper>> IteratorHelper::create(Realm& realm, IteratorRecord underlying_iterator, Closure closure, Optional<AbruptClosure> abrupt_closure)
ThrowCompletionOr<NonnullGCPtr<IteratorHelper>> IteratorHelper::create(Realm& realm, NonnullGCPtr<IteratorRecord> underlying_iterator, Closure closure, Optional<AbruptClosure> abrupt_closure)
{
return realm.heap().allocate<IteratorHelper>(realm, realm, realm.intrinsics().iterator_helper_prototype(), move(underlying_iterator), move(closure), move(abrupt_closure));
}
IteratorHelper::IteratorHelper(Realm& realm, Object& prototype, IteratorRecord underlying_iterator, Closure closure, Optional<AbruptClosure> abrupt_closure)
IteratorHelper::IteratorHelper(Realm& realm, Object& prototype, NonnullGCPtr<IteratorRecord> underlying_iterator, Closure closure, Optional<AbruptClosure> abrupt_closure)
: GeneratorObject(realm, prototype, realm.vm().running_execution_context().copy(), "Iterator Helper"sv)
, m_underlying_iterator(move(underlying_iterator))
, m_closure(move(closure))
@ -29,7 +29,7 @@ IteratorHelper::IteratorHelper(Realm& realm, Object& prototype, IteratorRecord u
void IteratorHelper::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_underlying_iterator.iterator);
visitor.visit(m_underlying_iterator);
}
Value IteratorHelper::result(Value value)

View file

@ -22,7 +22,7 @@ public:
using Closure = JS::SafeFunction<ThrowCompletionOr<Value>(VM&, IteratorHelper&)>;
using AbruptClosure = JS::SafeFunction<ThrowCompletionOr<Value>(VM&, IteratorHelper&, Completion const&)>;
static ThrowCompletionOr<NonnullGCPtr<IteratorHelper>> create(Realm&, IteratorRecord, Closure, Optional<AbruptClosure> = {});
static ThrowCompletionOr<NonnullGCPtr<IteratorHelper>> create(Realm&, NonnullGCPtr<IteratorRecord>, Closure, Optional<AbruptClosure> = {});
IteratorRecord const& underlying_iterator() const { return m_underlying_iterator; }
@ -33,12 +33,12 @@ public:
ThrowCompletionOr<Value> close_result(VM&, Completion);
private:
IteratorHelper(Realm&, Object& prototype, IteratorRecord, Closure, Optional<AbruptClosure>);
IteratorHelper(Realm&, Object& prototype, NonnullGCPtr<IteratorRecord>, Closure, Optional<AbruptClosure>);
virtual void visit_edges(Visitor&) override;
virtual ThrowCompletionOr<Value> execute(VM&, JS::Completion const& completion) override;
IteratorRecord m_underlying_iterator; // [[UnderlyingIterator]]
NonnullGCPtr<IteratorRecord> m_underlying_iterator; // [[UnderlyingIterator]]
Closure m_closure;
Optional<AbruptClosure> m_abrupt_closure;

View file

@ -318,7 +318,7 @@ class FlatMapIterator : public Cell {
public:
ThrowCompletionOr<Value> next(VM& vm, IteratorRecord const& iterated, IteratorHelper& iterator, FunctionObject& mapper)
{
if (m_inner_iterator.has_value())
if (m_inner_iterator)
return next_inner_iterator(vm, iterated, iterator, mapper);
return next_outer_iterator(vm, iterated, iterator, mapper);
}
@ -326,7 +326,7 @@ public:
// NOTE: This implements step 5.b.ix.4.d of Iterator.prototype.flatMap.
ThrowCompletionOr<Value> on_abrupt_completion(VM& vm, IteratorHelper& iterator, Completion const& completion)
{
VERIFY(m_inner_iterator.has_value());
VERIFY(m_inner_iterator);
// d. If completion is an abrupt completion, then
@ -347,9 +347,7 @@ private:
virtual void visit_edges(Visitor& visitor) override
{
Base::visit_edges(visitor);
if (m_inner_iterator.has_value())
visitor.visit(m_inner_iterator->iterator);
visitor.visit(m_inner_iterator);
}
ThrowCompletionOr<Value> next_outer_iterator(VM& vm, IteratorRecord const& iterated, IteratorHelper& iterator, FunctionObject& mapper)
@ -391,7 +389,7 @@ private:
ThrowCompletionOr<Value> next_inner_iterator(VM& vm, IteratorRecord const& iterated, IteratorHelper& iterator, FunctionObject& mapper)
{
VERIFY(m_inner_iterator.has_value());
VERIFY(m_inner_iterator);
// 1. Let innerNext be Completion(IteratorStep(innerIterator)).
auto inner_next = iterator_step(vm, *m_inner_iterator);
@ -403,7 +401,7 @@ private:
// 3. If innerNext is false, then
if (!inner_next.value()) {
// a. Set innerAlive to false.
m_inner_iterator.clear();
m_inner_iterator = nullptr;
return next_outer_iterator(vm, iterated, iterator, mapper);
}
@ -422,7 +420,7 @@ private:
}
}
Optional<IteratorRecord> m_inner_iterator;
GCPtr<IteratorRecord> m_inner_iterator;
};
JS_DEFINE_ALLOCATOR(FlatMapIterator);

View file

@ -189,6 +189,7 @@ public:
virtual bool is_proxy_object() const { return false; }
virtual bool is_native_function() const { return false; }
virtual bool is_ecmascript_function_object() const { return false; }
virtual bool is_iterator_record() const { return false; }
// B.3.7 The [[IsHTMLDDA]] Internal Slot, https://tc39.es/ecma262/#sec-IsHTMLDDA-internal-slot
virtual bool is_htmldda() const { return false; }

View file

@ -333,7 +333,7 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::all)
// 8. If result is an abrupt completion, then
if (result.is_error()) {
// a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
if (!iterator_record.done)
if (!iterator_record->done)
result = iterator_close(vm, iterator_record, result.release_error());
// b. IfAbruptRejectPromise(result, promiseCapability).
@ -367,7 +367,7 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::all_settled)
// 8. If result is an abrupt completion, then
if (result.is_error()) {
// a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
if (!iterator_record.done)
if (!iterator_record->done)
result = iterator_close(vm, iterator_record, result.release_error());
// b. IfAbruptRejectPromise(result, promiseCapability).
@ -401,7 +401,7 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::any)
// 8. If result is an abrupt completion, then
if (result.is_error()) {
// a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
if (!iterator_record.done)
if (!iterator_record->done)
result = iterator_close(vm, iterator_record, result.release_error());
// b. IfAbruptRejectPromise(result, promiseCapability).
@ -435,7 +435,7 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::race)
// 8. If result is an abrupt completion, then
if (result.is_error()) {
// a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
if (!iterator_record.done)
if (!iterator_record->done)
result = iterator_close(vm, iterator_record, result.release_error());
// b. IfAbruptRejectPromise(result, promiseCapability).

View file

@ -226,7 +226,7 @@ ThrowCompletionOr<void> VM::binding_initialization(NonnullRefPtr<BindingPattern
auto result = iterator_binding_initialization(*target, iterator_record, environment);
// 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result).
if (!iterator_record.done) {
if (!iterator_record->done) {
// iterator_close() always returns a Completion, which ThrowCompletionOr will interpret as a throw
// completion. So only return the result of iterator_close() if it is indeed a throw completion.
auto completion = result.is_throw_completion() ? result.release_error() : normal_completion({});