LibJS: Implement bytecode generation for For-In/Of statements

This also implements the rather interesting behaviour that #12772 relies
on, so this fixes that bug in BC mode (the AST interp remains affected).
This commit is contained in:
Ali Mohammad Pur 2022-03-18 20:18:19 +03:30 committed by Andreas Kling
parent 83afc1154c
commit 8f7021faf7
Notes: sideshowbarker 2024-07-17 17:07:51 +09:00
6 changed files with 454 additions and 15 deletions

View file

@ -894,6 +894,7 @@ public:
Statement const& body() const { return *m_body; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;
@ -918,6 +919,7 @@ public:
Statement const& body() const { return *m_body; }
virtual Completion execute(Interpreter&, GlobalObject&) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override;
virtual void dump(int indent) const override;

View file

@ -1155,6 +1155,23 @@ static Bytecode::CodeGenerationErrorOr<void> generate_binding_pattern_bytecode(B
return generate_array_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg);
}
static Bytecode::CodeGenerationErrorOr<void> assign_accumulator_to_variable_declarator(Bytecode::Generator& generator, VariableDeclarator const& declarator, VariableDeclaration const& declaration)
{
auto initialization_mode = declaration.is_lexical_declaration() ? Bytecode::Op::SetVariable::InitializationMode::Initialize : Bytecode::Op::SetVariable::InitializationMode::Set;
auto environment_mode = declaration.is_lexical_declaration() ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var;
return declarator.target().visit(
[&](NonnullRefPtr<Identifier> const& id) -> Bytecode::CodeGenerationErrorOr<void> {
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(id->string()), initialization_mode, environment_mode);
return {};
},
[&](NonnullRefPtr<BindingPattern> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
auto value_register = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(value_register);
return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register);
});
}
Bytecode::CodeGenerationErrorOr<void> VariableDeclaration::generate_bytecode(Bytecode::Generator& generator) const
{
for (auto& declarator : m_declarations) {
@ -1162,20 +1179,7 @@ Bytecode::CodeGenerationErrorOr<void> VariableDeclaration::generate_bytecode(Byt
TRY(declarator.init()->generate_bytecode(generator));
else
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
auto initialization_mode = is_lexical_declaration() ? Bytecode::Op::SetVariable::InitializationMode::Initialize : Bytecode::Op::SetVariable::InitializationMode::Set;
auto environment_mode = is_lexical_declaration() ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var;
TRY(declarator.target().visit(
[&](NonnullRefPtr<Identifier> const& id) -> Bytecode::CodeGenerationErrorOr<void> {
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(id->string()), initialization_mode, environment_mode);
return {};
},
[&](NonnullRefPtr<BindingPattern> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
auto value_register = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(value_register);
return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register);
}));
TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this));
}
return {};
@ -1695,4 +1699,336 @@ Bytecode::CodeGenerationErrorOr<void> WithStatement::generate_bytecode(Bytecode:
return {};
}
enum class LHSKind {
Assignment,
VarBinding,
LexicalBinding,
};
enum class IterationKind {
Enumerate,
Iterate,
AsyncIterate,
};
// 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation
struct ForInOfHeadEvaluationResult {
bool is_destructuring { false };
LHSKind lhs_kind { LHSKind::Assignment };
};
static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_head_evaluation(Bytecode::Generator& generator, IterationKind iteration_kind, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> const& lhs, NonnullRefPtr<ASTNode> const& rhs)
{
ForInOfHeadEvaluationResult result {};
if (auto* ast_ptr = lhs.get_pointer<NonnullRefPtr<ASTNode>>(); ast_ptr && is<VariableDeclaration>(**ast_ptr)) {
// Runtime Semantics: ForInOfLoopEvaluation, for any of:
// ForInOfStatement : for ( var ForBinding in Expression ) Statement
// ForInOfStatement : for ( ForDeclaration in Expression ) Statement
// ForInOfStatement : for ( var ForBinding of AssignmentExpression ) Statement
// ForInOfStatement : for ( ForDeclaration of AssignmentExpression ) Statement
auto& variable_declaration = static_cast<VariableDeclaration const&>(**ast_ptr);
result.is_destructuring = variable_declaration.declarations().first().target().has<NonnullRefPtr<BindingPattern>>();
result.lhs_kind = variable_declaration.is_lexical_declaration() ? LHSKind::LexicalBinding : LHSKind::VarBinding;
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
// NOTE: 'uninitializedBoundNames' refers to the lexical bindings (i.e. Const/Let) present in the second and last form.
// 2. If uninitializedBoundNames is not an empty List, then
bool entered_lexical_scope = false;
if (variable_declaration.declaration_kind() != DeclarationKind::Var) {
entered_lexical_scope = true;
// a. Assert: uninitializedBoundNames has no duplicate entries.
// b. Let newEnv be NewDeclarativeEnvironment(oldEnv).
generator.begin_variable_scope();
// c. For each String name of uninitializedBoundNames, do
variable_declaration.for_each_bound_name([&](auto const& name) {
// i. Perform ! newEnv.CreateMutableBinding(name, false).
auto identifier = generator.intern_identifier(name);
generator.register_binding(identifier);
generator.emit<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
});
// d. Set the running execution context's LexicalEnvironment to newEnv.
// NOTE: Done by CreateEnvironment.
}
// 3. Let exprRef be the result of evaluating expr.
TRY(rhs->generate_bytecode(generator));
// 4. Set the running execution context's LexicalEnvironment to oldEnv.
if (entered_lexical_scope)
generator.end_variable_scope();
// 5. Let exprValue be ? GetValue(exprRef).
// NOTE: No need to store this anywhere.
// 6. If iterationKind is enumerate, then
if (iteration_kind == IterationKind::Enumerate) {
// a. If exprValue is undefined or null, then
auto& nullish_block = generator.make_block();
auto& continuation_block = generator.make_block();
auto& jump = generator.emit<Bytecode::Op::JumpNullish>();
jump.set_targets(Bytecode::Label { nullish_block }, Bytecode::Label { continuation_block });
// i. Return Completion Record { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }.
generator.switch_to_basic_block(nullish_block);
generator.perform_needed_unwinds<Bytecode::Op::Jump>(true);
generator.emit<Bytecode::Op::Jump>().set_targets(generator.nearest_breakable_scope(), {});
generator.switch_to_basic_block(continuation_block);
// b. Let obj be ! ToObject(exprValue).
// NOTE: GetObjectPropertyIterator does this.
// c. Let iterator be EnumerateObjectProperties(obj).
// d. Let nextMethod be ! GetV(iterator, "next").
// e. Return the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
generator.emit<Bytecode::Op::GetObjectPropertyIterator>();
}
// 7. Else,
else {
// a. Assert: iterationKind is iterate or async-iterate.
// b. If iterationKind is async-iterate, let iteratorHint be async.
if (iteration_kind == IterationKind::AsyncIterate) {
return Bytecode::CodeGenerationError {
rhs.ptr(),
"Unimplemented iteration mode: AsyncIterate"sv,
};
}
// c. Else, let iteratorHint be sync.
// d. Return ? GetIterator(exprValue, iteratorHint).
generator.emit<Bytecode::Op::GetIterator>();
}
} else {
// Runtime Semantics: ForInOfLoopEvaluation, for any of:
// ForInOfStatement : for ( LeftHandSideExpression in Expression ) Statement
// ForInOfStatement : for ( LeftHandSideExpression of AssignmentExpression ) Statement
// Skip everything except steps 3, 5 and 7 (see above true branch for listing).
result.lhs_kind = LHSKind::Assignment;
// 3. Let exprRef be the result of evaluating expr.
TRY(rhs->generate_bytecode(generator));
// 5. Let exprValue be ? GetValue(exprRef).
// NOTE: No need to store this anywhere.
// a. Assert: iterationKind is iterate or async-iterate.
// b. If iterationKind is async-iterate, let iteratorHint be async.
if (iteration_kind == IterationKind::AsyncIterate) {
return Bytecode::CodeGenerationError {
rhs.ptr(),
"Unimplemented iteration mode: AsyncIterate"sv,
};
}
// c. Else, let iteratorHint be sync.
// d. Return ? GetIterator(exprValue, iteratorHint).
generator.emit<Bytecode::Op::GetIterator>();
}
return result;
}
// 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> const& lhs, ASTNode const& body, ForInOfHeadEvaluationResult const& head_result, Bytecode::BasicBlock& loop_end, Bytecode::BasicBlock& loop_update)
{
auto iterator_register = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(iterator_register);
// FIXME: Implement this
// 1. If iteratorKind is not present, set iteratorKind to sync.
// 2. Let oldEnv be the running execution context's LexicalEnvironment.
bool has_lexical_binding = false;
// 3. Let V be undefined.
// NOTE: We don't need 'V' as the resulting value will naturally flow through via the accumulator register.
// 4. Let destructuring be IsDestructuring of lhs.
auto destructuring = head_result.is_destructuring;
// 5. If destructuring is true and if lhsKind is assignment, then
if (destructuring) {
// a. Assert: lhs is a LeftHandSideExpression.
// b. Let assignmentPattern be the AssignmentPattern that is covered by lhs.
// FIXME: Implement this.
return Bytecode::CodeGenerationError {
&node,
"Unimplemented: destructuring in for-in/of"sv,
};
}
// 6. Repeat,
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { loop_update });
generator.switch_to_basic_block(loop_update);
// a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
generator.emit<Bytecode::Op::Load>(iterator_register);
generator.emit<Bytecode::Op::IteratorNext>();
// FIXME: Implement this:
// b. If iteratorKind is async, set nextResult to ? Await(nextResult).
// c. If Type(nextResult) is not Object, throw a TypeError exception.
// NOTE: IteratorComplete already does this.
// d. Let done be ? IteratorComplete(nextResult).
auto iterator_result_register = generator.allocate_register();
generator.emit<Bytecode::Op::Store>(iterator_result_register);
generator.emit<Bytecode::Op::IteratorResultDone>();
// e. If done is true, return V.
auto& loop_continue = generator.make_block();
generator.emit<Bytecode::Op::JumpConditional>().set_targets(Bytecode::Label { loop_end }, Bytecode::Label { loop_continue });
generator.switch_to_basic_block(loop_continue);
// f. Let nextValue be ? IteratorValue(nextResult).
generator.emit<Bytecode::Op::Load>(iterator_result_register);
generator.emit<Bytecode::Op::IteratorResultValue>();
// g. If lhsKind is either assignment or varBinding, then
if (head_result.lhs_kind != LHSKind::LexicalBinding) {
// i. If destructuring is false, then
if (!destructuring) {
// 1. Let lhsRef be the result of evaluating lhs. (It may be evaluated repeatedly.)
// NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that.
if (head_result.lhs_kind == LHSKind::VarBinding) {
auto& declaration = static_cast<VariableDeclaration const&>(*lhs.get<NonnullRefPtr<ASTNode>>());
VERIFY(declaration.declarations().size() == 1);
TRY(assign_accumulator_to_variable_declarator(generator, declaration.declarations().first(), declaration));
} else {
TRY(generator.emit_store_to_reference(*lhs.get<NonnullRefPtr<ASTNode>>()));
}
}
}
// h. Else,
else {
// i. Assert: lhsKind is lexicalBinding.
// ii. Assert: lhs is a ForDeclaration.
// iii. Let iterationEnv be NewDeclarativeEnvironment(oldEnv).
// iv. Perform ForDeclarationBindingInstantiation of lhs with argument iterationEnv.
// v. Set the running execution context's LexicalEnvironment to iterationEnv.
generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical);
has_lexical_binding = true;
// 14.7.5.4 Runtime Semantics: ForDeclarationBindingInstantiation, https://tc39.es/ecma262/#sec-runtime-semantics-fordeclarationbindinginstantiation
// 1. Assert: environment is a declarative Environment Record.
// NOTE: We just made it.
auto& variable_declaration = static_cast<VariableDeclaration const&>(*lhs.get<NonnullRefPtr<ASTNode>>());
// 2. For each element name of the BoundNames of ForBinding, do
variable_declaration.for_each_bound_name([&](auto const& name) {
auto identifier = generator.intern_identifier(name);
generator.register_binding(identifier, Bytecode::Generator::BindingMode::Lexical);
// a. If IsConstantDeclaration of LetOrConst is true, then
if (variable_declaration.is_constant_declaration()) {
// i. Perform ! environment.CreateImmutableBinding(name, true).
generator.emit<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, true);
}
// b. Else,
else {
// i. Perform ! environment.CreateMutableBinding(name, false).
generator.emit<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
}
});
// 3. Return unused.
// NOTE: No need to do that as we've inlined this.
// vi. If destructuring is false, then
if (!destructuring) {
// 1. Assert: lhs binds a single name.
// 2. Let lhsName be the sole element of BoundNames of lhs.
auto lhs_name = variable_declaration.declarations().first().target().get<NonnullRefPtr<Identifier>>()->string();
// 3. Let lhsRef be ! ResolveBinding(lhsName).
// NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that.
auto identifier = generator.intern_identifier(lhs_name);
generator.emit<Bytecode::Op::SetVariable>(identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical);
}
}
// i. If destructuring is false, then
if (!destructuring) {
// i. If lhsRef is an abrupt completion, then
// 1. Let status be lhsRef.
// ii. Else if lhsKind is lexicalBinding, then
// 1. Let status be Completion(InitializeReferencedBinding(lhsRef, nextValue)).
// iii. Else,
// 1. Let status be Completion(PutValue(lhsRef, nextValue)).
// NOTE: This is performed above.
}
// j. Else,
else {
// FIXME: Implement destructuring
// i. If lhsKind is assignment, then
// 1. Let status be Completion(DestructuringAssignmentEvaluation of assignmentPattern with argument nextValue).
// ii. Else if lhsKind is varBinding, then
// 1. Assert: lhs is a ForBinding.
// 2. Let status be Completion(BindingInitialization of lhs with arguments nextValue and undefined).
// iii. Else,
// 1. Assert: lhsKind is lexicalBinding.
// 2. Assert: lhs is a ForDeclaration.
// 3. Let status be Completion(ForDeclarationBindingInitialization of lhs with arguments nextValue and iterationEnv).
return Bytecode::CodeGenerationError {
&node,
"Unimplemented: destructuring in for-in/of"sv,
};
}
// FIXME: Implement iteration closure.
// k. If status is an abrupt completion, then
// i. Set the running execution context's LexicalEnvironment to oldEnv.
// ii. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status).
// iii. If iterationKind is enumerate, then
// 1. Return ? status.
// iv. Else,
// 1. Assert: iterationKind is iterate.
// 2. Return ? IteratorClose(iteratorRecord, status).
// l. Let result be the result of evaluating stmt.
TRY(body.generate_bytecode(generator));
// m. Set the running execution context's LexicalEnvironment to oldEnv.
if (has_lexical_binding)
generator.end_variable_scope();
generator.end_continuable_scope();
generator.end_breakable_scope();
// NOTE: If we're here, then the loop definitely continues.
// n. If LoopContinues(result, labelSet) is false, then
// i. If iterationKind is enumerate, then
// 1. Return ? UpdateEmpty(result, V).
// ii. Else,
// 1. Assert: iterationKind is iterate.
// 2. Set status to Completion(UpdateEmpty(result, V)).
// 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status).
// 4. Return ? IteratorClose(iteratorRecord, status).
// o. If result.[[Value]] is not empty, set V to result.[[Value]].
generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { loop_update }, {});
generator.switch_to_basic_block(loop_end);
return {};
}
// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation
Bytecode::CodeGenerationErrorOr<void> ForInStatement::generate_bytecode(Bytecode::Generator& generator) const
{
auto& loop_end = generator.make_block();
auto& loop_update = generator.make_block();
generator.begin_breakable_scope(Bytecode::Label { loop_end });
generator.begin_continuable_scope(Bytecode::Label { loop_update });
auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Enumerate, m_lhs, m_rhs));
// Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over.
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update);
}
Bytecode::CodeGenerationErrorOr<void> ForOfStatement::generate_bytecode(Bytecode::Generator& generator) const
{
auto& loop_end = generator.make_block();
auto& loop_update = generator.make_block();
generator.begin_breakable_scope(Bytecode::Label { loop_end });
generator.begin_continuable_scope(Bytecode::Label { loop_update });
auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Iterate, m_lhs, m_rhs));
// Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over.
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update);
}
}

View file

@ -194,7 +194,10 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
return {};
}
VERIFY_NOT_REACHED();
return CodeGenerationError {
&node,
"Unimplemented/invalid node used a reference"sv
};
}
String CodeGenerationError::to_string()

View file

@ -31,6 +31,7 @@
O(GetById) \
O(GetByValue) \
O(GetIterator) \
O(GetObjectPropertyIterator) \
O(GetVariable) \
O(GreaterThan) \
O(GreaterThanEquals) \

View file

@ -18,6 +18,7 @@
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/RegExpObject.h>
#include <LibJS/Runtime/Value.h>
@ -574,6 +575,85 @@ ThrowCompletionOr<void> GetIterator::execute_impl(Bytecode::Interpreter& interpr
return {};
}
// 14.7.5.9 EnumerateObjectProperties ( O ), https://tc39.es/ecma262/#sec-enumerate-object-properties
ThrowCompletionOr<void> GetObjectPropertyIterator::execute_impl(Bytecode::Interpreter& interpreter) const
{
// While the spec does provide an algorithm, it allows us to implement it ourselves so long as we meet the following invariants:
// 1- Returned property keys do not include keys that are Symbols
// 2- Properties of the target object may be deleted during enumeration. A property that is deleted before it is processed by the iterator's next method is ignored
// 3- If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration
// 4- A property name will be returned by the iterator's next method at most once in any enumeration.
// 5- Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively;
// but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's next method.
// 6- The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed.
// 7- The enumerable property names of prototype objects must be obtained by invoking EnumerateObjectProperties passing the prototype object as the argument.
// 8- EnumerateObjectProperties must obtain the own property keys of the target object by calling its [[OwnPropertyKeys]] internal method.
// 9- Property attributes of the target object must be obtained by calling its [[GetOwnProperty]] internal method
// Invariant 3 effectively allows the implementation to ignore newly added keys, and we do so (similar to other implementations).
// Invariants 1 and 6 through 9 are implemented in `enumerable_own_property_names`, which implements the EnumerableOwnPropertyNames AO.
auto* object = TRY(interpreter.accumulator().to_object(interpreter.global_object()));
// Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys,
// so we just keep the order consistent anyway.
OrderedHashTable<PropertyKey> properties;
HashTable<Object*> seen_objects;
// Collect all keys immediately (invariant no. 5)
for (auto* object_to_check = object; object_to_check && !seen_objects.contains(object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) {
seen_objects.set(object_to_check);
for (auto& key : TRY(object_to_check->enumerable_own_property_names(Object::PropertyKind::Key))) {
properties.set(TRY(PropertyKey::from_value(interpreter.global_object(), key)));
}
}
Iterator iterator {
.iterator = object,
.next_method = NativeFunction::create(
interpreter.global_object(),
[seen_items = HashTable<PropertyKey>(), items = move(properties)](VM& vm, GlobalObject& global_object) mutable -> ThrowCompletionOr<Value> {
auto iterated_object_value = vm.this_value(global_object);
if (!iterated_object_value.is_object())
return vm.throw_completion<InternalError>(global_object, "Invalid state for GetObjectPropertyIterator.next");
auto& iterated_object = iterated_object_value.as_object();
auto* result_object = Object::create(global_object, nullptr);
while (true) {
if (items.is_empty()) {
result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes);
return result_object;
}
auto it = items.begin();
auto key = *it;
items.remove(it);
// If the key was already seen, skip over it (invariant no. 4)
auto result = seen_items.set(key);
if (result != AK::HashSetResult::InsertedNewEntry)
continue;
// 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, JS::Value(key.as_number()), default_attributes);
else if (key.is_string())
result_object->define_direct_property(vm.names.value, js_string(vm.heap(), key.as_string()), default_attributes);
else
VERIFY_NOT_REACHED(); // We should not have non-string/number keys.
return result_object;
}
},
1,
interpreter.vm().names.next),
.done = false,
};
interpreter.accumulator() = iterator_to_object(interpreter.global_object(), move(iterator));
return {};
}
ThrowCompletionOr<void> IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto* iterator_object = TRY(interpreter.accumulator().to_object(interpreter.global_object()));
@ -871,6 +951,11 @@ String GetIterator::to_string_impl(Executable const&) const
return "GetIterator";
}
String GetObjectPropertyIterator::to_string_impl(const Bytecode::Executable&) const
{
return "GetObjectPropertyIterator";
}
String IteratorNext::to_string_impl(Executable const&) const
{
return "IteratorNext";

View file

@ -778,6 +778,18 @@ public:
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
};
class GetObjectPropertyIterator final : public Instruction {
public:
GetObjectPropertyIterator()
: Instruction(Type::GetObjectPropertyIterator)
{
}
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
String to_string_impl(Bytecode::Executable const&) const;
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
};
class IteratorNext final : public Instruction {
public:
IteratorNext()