Browse Source

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);
Andreas Kling 1 year ago
parent
commit
4699c81fc1

+ 2 - 7
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1749,16 +1749,11 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
 
         // 5. Let iterator be iteratorRecord.[[Iterator]].
         auto iterator_register = generator.allocate_register();
-        auto iterator_identifier = generator.intern_identifier("iterator");
-        generator.emit_get_by_id(iterator_identifier);
-        generator.emit<Bytecode::Op::Store>(iterator_register);
+        generator.emit<Bytecode::Op::GetObjectFromIteratorRecord>(iterator_register, iterator_record_register);
 
         // Cache iteratorRecord.[[NextMethod]] for use in step 7.a.i.
         auto next_method_register = generator.allocate_register();
-        auto next_method_identifier = generator.intern_identifier("next");
-        generator.emit<Bytecode::Op::Load>(iterator_record_register);
-        generator.emit_get_by_id(next_method_identifier);
-        generator.emit<Bytecode::Op::Store>(next_method_register);
+        generator.emit<Bytecode::Op::GetNextMethodFromIteratorRecord>(next_method_register, iterator_record_register);
 
         // 6. Let received be NormalCompletion(undefined).
         // See get_received_completion_type_and_value above.

+ 35 - 62
Userland/Libraries/LibJS/Bytecode/CommonImplementations.cpp

@@ -654,31 +654,9 @@ ThrowCompletionOr<NonnullGCPtr<Object>> super_call_with_argument_array(VM& vm, V
     return result;
 }
 
-// FIXME: Since the accumulator is a Value, we store an object there and have to convert back and forth between that an Iterator records. Not great.
-// Make sure to put this into the accumulator before the iterator object disappears from the stack to prevent the members from being GC'd.
-Object* iterator_to_object(VM& vm, IteratorRecord iterator)
-{
-    auto& realm = *vm.current_realm();
-    auto object = Object::create(realm, nullptr);
-    object->define_direct_property(vm.names.iterator, iterator.iterator, 0);
-    object->define_direct_property(vm.names.next, iterator.next_method, 0);
-    object->define_direct_property(vm.names.done, Value(iterator.done), 0);
-    return object;
-}
-
-IteratorRecord object_to_iterator(VM& vm, Object& object)
-{
-    return IteratorRecord {
-        .iterator = &MUST(object.get(vm.names.iterator)).as_object(),
-        .next_method = MUST(object.get(vm.names.next)),
-        .done = MUST(object.get(vm.names.done)).as_bool()
-    };
-}
-
 ThrowCompletionOr<NonnullGCPtr<Array>> iterator_to_array(VM& vm, Value iterator)
 {
-    auto iterator_object = TRY(iterator.to_object(vm));
-    auto iterator_record = object_to_iterator(vm, iterator_object);
+    auto& iterator_record = verify_cast<IteratorRecord>(iterator.as_object());
 
     auto array = MUST(Array::create(*vm.current_realm(), 0));
     size_t index = 0;
@@ -832,47 +810,42 @@ ThrowCompletionOr<Object*> get_object_property_iterator(VM& vm, Value value)
                 properties.set(move(property_key));
         }
     }
-    IteratorRecord iterator {
-        .iterator = object,
-        .next_method = NativeFunction::create(
-            *vm.current_realm(),
-            [items = move(properties)](VM& vm) mutable -> ThrowCompletionOr<Value> {
-                auto& realm = *vm.current_realm();
-                auto iterated_object_value = vm.this_value();
-                if (!iterated_object_value.is_object())
-                    return vm.throw_completion<InternalError>("Invalid state for GetObjectPropertyIterator.next"sv);
-
-                auto& iterated_object = iterated_object_value.as_object();
-                auto result_object = Object::create(realm, nullptr);
-                while (true) {
-                    if (items.is_empty()) {
-                        result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
-                        return result_object;
-                    }
-
-                    auto key = items.take_first();
-
-                    // If the property is deleted, don't include it (invariant no. 2)
-                    if (!TRY(iterated_object.has_property(key)))
-                        continue;
-
-                    result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes);
-
-                    if (key.is_number())
-                        result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, TRY_OR_THROW_OOM(vm, String::number(key.as_number()))), default_attributes);
-                    else if (key.is_string())
-                        result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, key.as_string()), default_attributes);
-                    else
-                        VERIFY_NOT_REACHED(); // We should not have non-string/number keys.
-
+    auto& realm = *vm.current_realm();
+    auto callback = NativeFunction::create(
+        *vm.current_realm(), [items = move(properties)](VM& vm) mutable -> ThrowCompletionOr<Value> {
+            auto& realm = *vm.current_realm();
+            auto iterated_object_value = vm.this_value();
+            if (!iterated_object_value.is_object())
+                return vm.throw_completion<InternalError>("Invalid state for GetObjectPropertyIterator.next"sv);
+
+            auto& iterated_object = iterated_object_value.as_object();
+            auto result_object = Object::create(realm, nullptr);
+            while (true) {
+                if (items.is_empty()) {
+                    result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
                     return result_object;
                 }
-            },
-            1,
-            vm.names.next),
-        .done = false,
-    };
-    return iterator_to_object(vm, move(iterator));
+
+                auto key = items.take_first();
+
+                // If the property is deleted, don't include it (invariant no. 2)
+                if (!TRY(iterated_object.has_property(key)))
+                    continue;
+
+                result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes);
+
+                if (key.is_number())
+                    result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, TRY_OR_THROW_OOM(vm, String::number(key.as_number()))), default_attributes);
+                else if (key.is_string())
+                    result_object->define_direct_property(vm.names.value, PrimitiveString::create(vm, key.as_string()), default_attributes);
+                else
+                    VERIFY_NOT_REACHED(); // We should not have non-string/number keys.
+
+                return result_object;
+            }
+        },
+        1, vm.names.next);
+    return vm.heap().allocate<IteratorRecord>(realm, realm, object, callback, false).ptr();
 }
 
 }

+ 0 - 2
Userland/Libraries/LibJS/Bytecode/CommonImplementations.h

@@ -35,8 +35,6 @@ MarkedVector<Value> argument_list_evaluation(VM&, Value arguments);
 ThrowCompletionOr<void> create_variable(VM&, DeprecatedFlyString const& name, Op::EnvironmentMode, bool is_global, bool is_immutable, bool is_strict);
 ThrowCompletionOr<ECMAScriptFunctionObject*> new_class(VM&, Value super_class, ClassExpression const&, Optional<IdentifierTableIndex> const& lhs_name);
 ThrowCompletionOr<NonnullGCPtr<Object>> super_call_with_argument_array(VM&, Value argument_array, bool is_synthetic);
-Object* iterator_to_object(VM&, IteratorRecord);
-IteratorRecord object_to_iterator(VM&, Object&);
 ThrowCompletionOr<NonnullGCPtr<Array>> iterator_to_array(VM&, Value iterator);
 ThrowCompletionOr<void> append(VM& vm, Value lhs, Value rhs, bool is_spread);
 ThrowCompletionOr<Value> delete_by_id(Bytecode::Interpreter&, Value base, IdentifierTableIndex identifier);

+ 2 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -46,8 +46,10 @@
     O(GetByValueWithThis)              \
     O(GetCalleeAndThisFromEnvironment) \
     O(GetIterator)                     \
+    O(GetObjectFromIteratorRecord)     \
     O(GetMethod)                       \
     O(GetNewTarget)                    \
+    O(GetNextMethodFromIteratorRecord) \
     O(GetImportMeta)                   \
     O(GetObjectPropertyIterator)       \
     O(GetPrivateById)                  \

+ 28 - 8
Userland/Libraries/LibJS/Bytecode/Interpreter.cpp

@@ -1225,8 +1225,21 @@ ThrowCompletionOr<void> DeleteByValueWithThis::execute_impl(Bytecode::Interprete
 ThrowCompletionOr<void> GetIterator::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
-    auto iterator = TRY(get_iterator(vm, interpreter.accumulator(), m_hint));
-    interpreter.accumulator() = iterator_to_object(vm, iterator);
+    interpreter.accumulator() = TRY(get_iterator(vm, interpreter.accumulator(), m_hint));
+    return {};
+}
+
+ThrowCompletionOr<void> GetObjectFromIteratorRecord::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    auto& iterator_record = verify_cast<IteratorRecord>(interpreter.reg(m_iterator_record).as_object());
+    interpreter.reg(m_object) = iterator_record.iterator;
+    return {};
+}
+
+ThrowCompletionOr<void> GetNextMethodFromIteratorRecord::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    auto& iterator_record = verify_cast<IteratorRecord>(interpreter.reg(m_iterator_record).as_object());
+    interpreter.reg(m_next_method) = iterator_record.next_method;
     return {};
 }
 
@@ -1248,8 +1261,7 @@ ThrowCompletionOr<void> GetObjectPropertyIterator::execute_impl(Bytecode::Interp
 ThrowCompletionOr<void> IteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
-    auto iterator_object = TRY(interpreter.accumulator().to_object(vm));
-    auto iterator = object_to_iterator(vm, iterator_object);
+    auto& iterator = verify_cast<IteratorRecord>(interpreter.accumulator().as_object());
 
     // FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!)
     TRY(iterator_close(vm, iterator, Completion { m_completion_type, m_completion_value, {} }));
@@ -1259,8 +1271,7 @@ ThrowCompletionOr<void> IteratorClose::execute_impl(Bytecode::Interpreter& inter
 ThrowCompletionOr<void> AsyncIteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
-    auto iterator_object = TRY(interpreter.accumulator().to_object(vm));
-    auto iterator = object_to_iterator(vm, iterator_object);
+    auto& iterator = verify_cast<IteratorRecord>(interpreter.accumulator().as_object());
 
     // FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!)
     TRY(async_iterator_close(vm, iterator, Completion { m_completion_type, m_completion_value, {} }));
@@ -1270,8 +1281,7 @@ ThrowCompletionOr<void> AsyncIteratorClose::execute_impl(Bytecode::Interpreter&
 ThrowCompletionOr<void> IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
-    auto iterator_object = TRY(interpreter.accumulator().to_object(vm));
-    auto iterator = object_to_iterator(vm, iterator_object);
+    auto& iterator = verify_cast<IteratorRecord>(interpreter.accumulator().as_object());
 
     interpreter.accumulator() = TRY(iterator_next(vm, iterator));
     return {};
@@ -1818,4 +1828,14 @@ DeprecatedString Catch::to_deprecated_string_impl(Bytecode::Executable const&) c
     return "Catch"sv;
 }
 
+DeprecatedString GetObjectFromIteratorRecord::to_deprecated_string_impl(Bytecode::Executable const&) const
+{
+    return DeprecatedString::formatted("GetObjectFromIteratorRecord object:{} <- iterator_record:{}", m_object, m_iterator_record);
+}
+
+DeprecatedString GetNextMethodFromIteratorRecord::to_deprecated_string_impl(Bytecode::Executable const&) const
+{
+    return DeprecatedString::formatted("GetNextMethodFromIteratorRecord next_method:{} <- iterator_record:{}", m_next_method, m_iterator_record);
+}
+
 }

+ 40 - 1
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -1373,6 +1373,46 @@ private:
     IteratorHint m_hint { IteratorHint::Sync };
 };
 
+class GetObjectFromIteratorRecord final : public Instruction {
+public:
+    GetObjectFromIteratorRecord(Register object, Register iterator_record)
+        : Instruction(Type::GetObjectFromIteratorRecord, sizeof(*this))
+        , m_object(object)
+        , m_iterator_record(iterator_record)
+    {
+    }
+
+    ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
+    DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
+
+    Register object() const { return m_object; }
+    Register iterator_record() const { return m_iterator_record; }
+
+private:
+    Register m_object;
+    Register m_iterator_record;
+};
+
+class GetNextMethodFromIteratorRecord final : public Instruction {
+public:
+    GetNextMethodFromIteratorRecord(Register next_method, Register iterator_record)
+        : Instruction(Type::GetNextMethodFromIteratorRecord, sizeof(*this))
+        , m_next_method(next_method)
+        , m_iterator_record(iterator_record)
+    {
+    }
+
+    ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
+    DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
+
+    Register next_method() const { return m_next_method; }
+    Register iterator_record() const { return m_iterator_record; }
+
+private:
+    Register m_next_method;
+    Register m_iterator_record;
+};
+
 class GetMethod final : public Instruction {
 public:
     GetMethod(IdentifierTableIndex property)
@@ -1551,7 +1591,6 @@ public:
 private:
     size_t m_index;
 };
-
 }
 
 namespace JS::Bytecode {

+ 1 - 1
Userland/Libraries/LibJS/Forward.h

@@ -187,7 +187,7 @@ struct ImportEntry;
 class ImportStatement;
 class Identifier;
 class Intrinsics;
-struct IteratorRecord;
+class IteratorRecord;
 class MemberExpression;
 class MetaProperty;
 class Module;

+ 28 - 8
Userland/Libraries/LibJS/JIT/Compiler.cpp

@@ -3059,8 +3059,7 @@ void Compiler::compile_super_call_with_argument_array(Bytecode::Op::SuperCallWit
 
 static Value cxx_get_iterator(VM& vm, Value value, IteratorHint hint)
 {
-    auto iterator = TRY_OR_SET_EXCEPTION(get_iterator(vm, value, hint));
-    return Bytecode::iterator_to_object(vm, iterator);
+    return TRY_OR_SET_EXCEPTION(get_iterator(vm, value, hint));
 }
 
 void Compiler::compile_get_iterator(Bytecode::Op::GetIterator const& op)
@@ -3074,10 +3073,33 @@ void Compiler::compile_get_iterator(Bytecode::Op::GetIterator const& op)
     check_exception();
 }
 
+static Value cxx_get_object_from_iterator_record(VM&, Value value)
+{
+    return verify_cast<IteratorRecord>(value.as_object()).iterator;
+}
+
+void Compiler::compile_get_object_from_iterator_record(Bytecode::Op::GetObjectFromIteratorRecord const& op)
+{
+    load_vm_register(ARG1, op.iterator_record());
+    native_call((void*)cxx_get_object_from_iterator_record);
+    store_vm_register(op.object(), RET);
+}
+
+static Value cxx_next_method_from_iterator_record(VM&, Value value)
+{
+    return verify_cast<IteratorRecord>(value.as_object()).next_method;
+}
+
+void Compiler::compile_get_next_method_from_iterator_record(Bytecode::Op::GetNextMethodFromIteratorRecord const& op)
+{
+    load_vm_register(ARG1, op.iterator_record());
+    native_call((void*)cxx_next_method_from_iterator_record);
+    store_vm_register(op.next_method(), RET);
+}
+
 static Value cxx_iterator_next(VM& vm, Value iterator)
 {
-    auto iterator_object = TRY_OR_SET_EXCEPTION(iterator.to_object(vm));
-    auto iterator_record = Bytecode::object_to_iterator(vm, iterator_object);
+    auto& iterator_record = verify_cast<IteratorRecord>(iterator.as_object());
     return TRY_OR_SET_EXCEPTION(iterator_next(vm, iterator_record));
 }
 
@@ -3147,8 +3169,7 @@ void Compiler::compile_iterator_result_value(Bytecode::Op::IteratorResultValue c
 
 static Value cxx_iterator_close(VM& vm, Value iterator, Completion::Type completion_type, Optional<Value> const& completion_value)
 {
-    auto iterator_object = TRY_OR_SET_EXCEPTION(iterator.to_object(vm));
-    auto iterator_record = Bytecode::object_to_iterator(vm, iterator_object);
+    auto& iterator_record = verify_cast<IteratorRecord>(iterator.as_object());
 
     // FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!)
     TRY_OR_SET_EXCEPTION(iterator_close(vm, iterator_record, Completion { completion_type, completion_value, {} }));
@@ -3565,8 +3586,7 @@ void Compiler::compile_copy_object_excluding_properties(Bytecode::Op::CopyObject
 
 static Value cxx_async_iterator_close(VM& vm, Value iterator, Completion::Type completion_type, Optional<Value> const& completion_value)
 {
-    auto iterator_object = TRY_OR_SET_EXCEPTION(iterator.to_object(vm));
-    auto iterator_record = Bytecode::object_to_iterator(vm, iterator_object);
+    auto& iterator_record = verify_cast<IteratorRecord>(iterator.as_object());
 
     // FIXME: Return the value of the resulting completion. (Note that completion_value can be empty!)
     TRY_OR_SET_EXCEPTION(async_iterator_close(vm, iterator_record, Completion { completion_type, completion_value, {} }));

+ 2 - 0
Userland/Libraries/LibJS/JIT/Compiler.h

@@ -114,6 +114,8 @@ private:
         O(BlockDeclarationInstantiation, block_declaration_instantiation)        \
         O(SuperCallWithArgumentArray, super_call_with_argument_array)            \
         O(GetIterator, get_iterator)                                             \
+        O(GetObjectFromIteratorRecord, get_object_from_iterator_record)          \
+        O(GetNextMethodFromIteratorRecord, get_next_method_from_iterator_record) \
         O(IteratorNext, iterator_next)                                           \
         O(IteratorResultDone, iterator_result_done)                              \
         O(ThrowIfNotObject, throw_if_not_object)                                 \

+ 6 - 6
Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp

@@ -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.
             }

+ 3 - 4
Userland/Libraries/LibJS/Runtime/AsyncFromSyncIterator.cpp

@@ -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);
 }
 
 }

+ 3 - 3
Userland/Libraries/LibJS/Runtime/AsyncFromSyncIterator.h

@@ -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]]
 };
 
 }

+ 2 - 2
Userland/Libraries/LibJS/Runtime/AsyncFromSyncIteratorPrototype.cpp

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

+ 1 - 1
Userland/Libraries/LibJS/Runtime/AsyncFromSyncIteratorPrototype.h

@@ -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);
 
 }

+ 25 - 11
Userland/Libraries/LibJS/Runtime/Iterator.cpp

@@ -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);
+}
+
 }

+ 29 - 8
Userland/Libraries/LibJS/Runtime/Iterator.h

@@ -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);

+ 2 - 2
Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp

@@ -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]] »).

+ 3 - 3
Userland/Libraries/LibJS/Runtime/IteratorHelper.cpp

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

+ 3 - 3
Userland/Libraries/LibJS/Runtime/IteratorHelper.h

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

+ 6 - 8
Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp

@@ -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);

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Object.h

@@ -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; }

+ 4 - 4
Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp

@@ -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).

+ 1 - 1
Userland/Libraries/LibJS/Runtime/VM.cpp

@@ -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({});