Selaa lähdekoodia

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).
Ali Mohammad Pur 3 vuotta sitten
vanhempi
commit
8f7021faf7

+ 2 - 0
Userland/Libraries/LibJS/AST.h

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

+ 350 - 14
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

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

+ 4 - 1
Userland/Libraries/LibJS/Bytecode/Generator.cpp

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

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

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

+ 85 - 0
Userland/Libraries/LibJS/Bytecode/Op.cpp

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

+ 12 - 0
Userland/Libraries/LibJS/Bytecode/Op.h

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