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:
parent
83afc1154c
commit
8f7021faf7
Notes:
sideshowbarker
2024-07-17 17:07:51 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/8f7021faf7 Pull-request: https://github.com/SerenityOS/serenity/pull/13116 Reviewed-by: https://github.com/Lubrsi
6 changed files with 454 additions and 15 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
O(GetById) \
|
||||
O(GetByValue) \
|
||||
O(GetIterator) \
|
||||
O(GetObjectPropertyIterator) \
|
||||
O(GetVariable) \
|
||||
O(GreaterThan) \
|
||||
O(GreaterThanEquals) \
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue