1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:04:59 +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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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