/* * Copyright (c) 2021, Andreas Kling * Copyright (c) 2021, Linus Groh * Copyright (c) 2021, Gunnar Beutner * Copyright (c) 2021, Marcin Gasperowicz * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace JS { Bytecode::CodeGenerationErrorOr ASTNode::generate_bytecode(Bytecode::Generator&) const { return Bytecode::CodeGenerationError { this, "Missing generate_bytecode()"sv, }; } Bytecode::CodeGenerationErrorOr ScopeNode::generate_bytecode(Bytecode::Generator& generator) const { Optional maybe_error; size_t pushed_scope_count = 0; auto const failing_completion = Completion(Completion::Type::Throw, {}, {}); if (is(*this) || is(*this)) { // Perform the steps of BlockDeclarationInstantiation. if (has_lexical_declarations()) { generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Block); pushed_scope_count++; } (void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr { auto is_constant_declaration = declaration.is_constant_declaration(); declaration.for_each_bound_name([&](auto const& name) { auto index = generator.intern_identifier(name); if (is_constant_declaration || !generator.has_binding(index)) { generator.register_binding(index); generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration); } }); if (is(declaration)) { auto& function_declaration = static_cast(declaration); auto const& name = function_declaration.name(); auto index = generator.intern_identifier(name); generator.emit(function_declaration); generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::Initialize); } return {}; }); } else if (is(*this)) { // Perform the steps of GlobalDeclarationInstantiation. generator.begin_variable_scope(Bytecode::Generator::BindingMode::Global, Bytecode::Generator::SurroundingScopeKind::Global); pushed_scope_count++; // 1. Let lexNames be the LexicallyDeclaredNames of script. // 2. Let varNames be the VarDeclaredNames of script. // 3. For each element name of lexNames, do (void)for_each_lexically_declared_name([&](auto const& name) -> ThrowCompletionOr { auto identifier = generator.intern_identifier(name); // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. if (generator.has_binding(identifier)) { // FIXME: Throw an actual SyntaxError instance. generator.emit(generator.intern_string(String::formatted("SyntaxError: toplevel variable already declared: {}", name))); generator.emit(); return {}; } // FIXME: c. If hasRestrictedGlobalProperty is true, throw a SyntaxError exception. // d. If hasRestrictedGlobal is true, throw a SyntaxError exception. return {}; }); // 4. For each element name of varNames, do (void)for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr { auto identifier = generator.intern_identifier(name); // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. if (generator.has_binding(identifier)) { // FIXME: Throw an actual SyntaxError instance. generator.emit(generator.intern_string(String::formatted("SyntaxError: toplevel variable already declared: {}", name))); generator.emit(); } return {}; }); // 5. Let varDeclarations be the VarScopedDeclarations of script. // 6. Let functionsToInitialize be a new empty List. Vector functions_to_initialize; // 7. Let declaredFunctionNames be a new empty List. HashTable declared_function_names; // 8. For each element d of varDeclarations, in reverse List order, do (void)for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr { // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. // Note: This is checked in for_each_var_function_declaration_in_reverse_order. // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. // iii. Let fn be the sole element of the BoundNames of d. // iv. If fn is not an element of declaredFunctionNames, then if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) return {}; // FIXME: 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn). // FIXME: 2. If fnDefinable is false, throw a TypeError exception. // 3. Append fn to declaredFunctionNames. // Note: Already done in step iv. above. // 4. Insert d as the first element of functionsToInitialize. functions_to_initialize.prepend(function); return {}; }); // 9. Let declaredVarNames be a new empty List. HashTable declared_var_names; // 10. For each element d of varDeclarations, do (void)for_each_var_scoped_variable_declaration([&](Declaration const& declaration) { // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then // Note: This is done in for_each_var_scoped_variable_declaration. // i. For each String vn of the BoundNames of d, do return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { // 1. If vn is not an element of declaredFunctionNames, then if (declared_function_names.contains(name)) return {}; // FIXME: a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn). // FIXME: b. If vnDefinable is false, throw a TypeError exception. // c. If vn is not an element of declaredVarNames, then // i. Append vn to declaredVarNames. declared_var_names.set(name); return {}; }); }); // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps. // 12. NOTE: Annex B.3.2.2 adds additional steps at this point. // 12. Let strict be IsStrict of script. // 13. If strict is false, then if (!verify_cast(*this).is_strict_mode()) { // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. // b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within script, do (void)for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { // i. Let F be StringValue of the BindingIdentifier of f. auto& function_name = function_declaration.name(); // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for script, then // Note: This step is already performed during parsing and for_each_function_hoistable_with_annexB_extension so this always passes here. // 1. If env.HasLexicalDeclaration(F) is false, then auto index = generator.intern_identifier(function_name); if (generator.has_binding(index, Bytecode::Generator::BindingMode::Lexical)) return; // FIXME: a. Let fnDefinable be ? env.CanDeclareGlobalVar(F). // b. If fnDefinable is true, then // i. NOTE: A var binding for F is only instantiated here if it is neither a VarDeclaredName nor the name of another FunctionDeclaration. // ii. If declaredFunctionOrVarNames does not contain F, then if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) { // i. Perform ? env.CreateGlobalVarBinding(F, false). generator.emit(index, Bytecode::Op::EnvironmentMode::Var, false); generator.emit(js_undefined()); generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Var); // ii. Append F to declaredFunctionOrVarNames. declared_function_names.set(function_name); } // iii. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: // i. Let genv be the running execution context's VariableEnvironment. // ii. Let benv be the running execution context's LexicalEnvironment. // iii. Let fobj be ! benv.GetBindingValue(F, false). // iv. Perform ? genv.SetMutableBinding(F, fobj, false). // v. Return NormalCompletion(empty). function_declaration.set_should_do_additional_annexB_steps(); }); } // 15. For each element d of lexDeclarations, do (void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr { // a. NOTE: Lexically declared names are only instantiated here but not initialized. // b. For each element dn of the BoundNames of d, do return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { auto identifier = generator.intern_identifier(name); // i. If IsConstantDeclaration of d is true, then generator.register_binding(identifier); if (declaration.is_constant_declaration()) { // 1. Perform ? env.CreateImmutableBinding(dn, true). generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, true); } else { // ii. Else, // 1. Perform ? env.CreateMutableBinding(dn, false). generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, false); } return {}; }); }); // 16. For each Parse Node f of functionsToInitialize, do for (auto& function_declaration : functions_to_initialize) { // FIXME: Do this more correctly. // a. Let fn be the sole element of the BoundNames of f. // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. generator.emit(function_declaration); // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). auto const& name = function_declaration.name(); auto index = generator.intern_identifier(name); if (!generator.has_binding(index)) { generator.register_binding(index, Bytecode::Generator::BindingMode::Var); generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, false); } generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::Initialize); } // 17. For each String vn of declaredVarNames, do // a. Perform ? env.CreateGlobalVarBinding(vn, false). for (auto& var_name : declared_var_names) generator.register_binding(generator.intern_identifier(var_name), Bytecode::Generator::BindingMode::Var); } else { // Perform the steps of FunctionDeclarationInstantiation. generator.begin_variable_scope(Bytecode::Generator::BindingMode::Var, Bytecode::Generator::SurroundingScopeKind::Function); pushed_scope_count++; if (has_lexical_declarations()) { generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Function); pushed_scope_count++; } // FIXME: Implement this boi correctly. (void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr { auto is_constant_declaration = declaration.is_constant_declaration(); declaration.for_each_bound_name([&](auto const& name) { auto index = generator.intern_identifier(name); if (is_constant_declaration || !generator.has_binding(index)) { generator.register_binding(index); generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, is_constant_declaration); } }); if (is(declaration)) { auto& function_declaration = static_cast(declaration); if (auto result = function_declaration.generate_bytecode(generator); result.is_error()) { maybe_error = result.release_error(); // To make `for_each_lexically_scoped_declaration` happy. return failing_completion; } auto const& name = function_declaration.name(); auto index = generator.intern_identifier(name); if (!generator.has_binding(index)) { generator.register_binding(index); generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, false); } generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::InitializeOrSet); } return {}; }); } if (maybe_error.has_value()) return maybe_error.release_value(); for (auto& child : children()) { TRY(child.generate_bytecode(generator)); if (generator.is_current_block_terminated()) break; } for (size_t i = 0; i < pushed_scope_count; ++i) generator.end_variable_scope(); return {}; } Bytecode::CodeGenerationErrorOr EmptyStatement::generate_bytecode(Bytecode::Generator&) const { return {}; } Bytecode::CodeGenerationErrorOr ExpressionStatement::generate_bytecode(Bytecode::Generator& generator) const { return m_expression->generate_bytecode(generator); } Bytecode::CodeGenerationErrorOr BinaryExpression::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_lhs->generate_bytecode(generator)); auto lhs_reg = generator.allocate_register(); generator.emit(lhs_reg); TRY(m_rhs->generate_bytecode(generator)); switch (m_op) { case BinaryOp::Addition: generator.emit(lhs_reg); break; case BinaryOp::Subtraction: generator.emit(lhs_reg); break; case BinaryOp::Multiplication: generator.emit(lhs_reg); break; case BinaryOp::Division: generator.emit(lhs_reg); break; case BinaryOp::Modulo: generator.emit(lhs_reg); break; case BinaryOp::Exponentiation: generator.emit(lhs_reg); break; case BinaryOp::GreaterThan: generator.emit(lhs_reg); break; case BinaryOp::GreaterThanEquals: generator.emit(lhs_reg); break; case BinaryOp::LessThan: generator.emit(lhs_reg); break; case BinaryOp::LessThanEquals: generator.emit(lhs_reg); break; case BinaryOp::LooselyInequals: generator.emit(lhs_reg); break; case BinaryOp::LooselyEquals: generator.emit(lhs_reg); break; case BinaryOp::StrictlyInequals: generator.emit(lhs_reg); break; case BinaryOp::StrictlyEquals: generator.emit(lhs_reg); break; case BinaryOp::BitwiseAnd: generator.emit(lhs_reg); break; case BinaryOp::BitwiseOr: generator.emit(lhs_reg); break; case BinaryOp::BitwiseXor: generator.emit(lhs_reg); break; case BinaryOp::LeftShift: generator.emit(lhs_reg); break; case BinaryOp::RightShift: generator.emit(lhs_reg); break; case BinaryOp::UnsignedRightShift: generator.emit(lhs_reg); break; case BinaryOp::In: generator.emit(lhs_reg); break; case BinaryOp::InstanceOf: generator.emit(lhs_reg); break; default: VERIFY_NOT_REACHED(); } return {}; } Bytecode::CodeGenerationErrorOr LogicalExpression::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_lhs->generate_bytecode(generator)); // lhs // jump op (true) end (false) rhs // rhs // jump always (true) end // end auto& rhs_block = generator.make_block(); auto& end_block = generator.make_block(); switch (m_op) { case LogicalOp::And: generator.emit().set_targets( Bytecode::Label { rhs_block }, Bytecode::Label { end_block }); break; case LogicalOp::Or: generator.emit().set_targets( Bytecode::Label { end_block }, Bytecode::Label { rhs_block }); break; case LogicalOp::NullishCoalescing: generator.emit().set_targets( Bytecode::Label { rhs_block }, Bytecode::Label { end_block }); break; default: VERIFY_NOT_REACHED(); } generator.switch_to_basic_block(rhs_block); TRY(m_rhs->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { end_block }, {}); generator.switch_to_basic_block(end_block); return {}; } Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_lhs->generate_bytecode(generator)); switch (m_op) { case UnaryOp::BitwiseNot: generator.emit(); break; case UnaryOp::Not: generator.emit(); break; case UnaryOp::Plus: generator.emit(); break; case UnaryOp::Minus: generator.emit(); break; case UnaryOp::Typeof: generator.emit(); break; case UnaryOp::Void: generator.emit(js_undefined()); break; default: return Bytecode::CodeGenerationError { this, "Unimplemented operation"sv, }; } return {}; } Bytecode::CodeGenerationErrorOr NumericLiteral::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(m_value); return {}; } Bytecode::CodeGenerationErrorOr BooleanLiteral::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(Value(m_value)); return {}; } Bytecode::CodeGenerationErrorOr NullLiteral::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(js_null()); return {}; } Bytecode::CodeGenerationErrorOr BigIntLiteral::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(Crypto::SignedBigInteger::from_base(10, m_value.substring(0, m_value.length() - 1))); return {}; } Bytecode::CodeGenerationErrorOr StringLiteral::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(generator.intern_string(m_value)); return {}; } Bytecode::CodeGenerationErrorOr RegExpLiteral::generate_bytecode(Bytecode::Generator& generator) const { auto source_index = generator.intern_string(m_pattern); auto flags_index = generator.intern_string(m_flags); generator.emit(source_index, flags_index); return {}; } Bytecode::CodeGenerationErrorOr Identifier::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(generator.intern_identifier(m_string)); return {}; } Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) const { // FIXME: Implement this for BindingPatterns too. auto& lhs = m_lhs.get>(); if (m_op == AssignmentOp::Assignment) { TRY(m_rhs->generate_bytecode(generator)); return generator.emit_store_to_reference(lhs); } TRY(generator.emit_load_from_reference(lhs)); Bytecode::BasicBlock* rhs_block_ptr { nullptr }; Bytecode::BasicBlock* end_block_ptr { nullptr }; // Logical assignments short circuit. if (m_op == AssignmentOp::AndAssignment) { // &&= rhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); generator.emit().set_targets( Bytecode::Label { *rhs_block_ptr }, Bytecode::Label { *end_block_ptr }); } else if (m_op == AssignmentOp::OrAssignment) { // ||= rhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); generator.emit().set_targets( Bytecode::Label { *end_block_ptr }, Bytecode::Label { *rhs_block_ptr }); } else if (m_op == AssignmentOp::NullishAssignment) { // ??= rhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); generator.emit().set_targets( Bytecode::Label { *rhs_block_ptr }, Bytecode::Label { *end_block_ptr }); } if (rhs_block_ptr) generator.switch_to_basic_block(*rhs_block_ptr); // lhs_reg is a part of the rhs_block because the store isn't necessary // if the logical assignment condition fails. auto lhs_reg = generator.allocate_register(); generator.emit(lhs_reg); TRY(m_rhs->generate_bytecode(generator)); switch (m_op) { case AssignmentOp::AdditionAssignment: generator.emit(lhs_reg); break; case AssignmentOp::SubtractionAssignment: generator.emit(lhs_reg); break; case AssignmentOp::MultiplicationAssignment: generator.emit(lhs_reg); break; case AssignmentOp::DivisionAssignment: generator.emit(lhs_reg); break; case AssignmentOp::ModuloAssignment: generator.emit(lhs_reg); break; case AssignmentOp::ExponentiationAssignment: generator.emit(lhs_reg); break; case AssignmentOp::BitwiseAndAssignment: generator.emit(lhs_reg); break; case AssignmentOp::BitwiseOrAssignment: generator.emit(lhs_reg); break; case AssignmentOp::BitwiseXorAssignment: generator.emit(lhs_reg); break; case AssignmentOp::LeftShiftAssignment: generator.emit(lhs_reg); break; case AssignmentOp::RightShiftAssignment: generator.emit(lhs_reg); break; case AssignmentOp::UnsignedRightShiftAssignment: generator.emit(lhs_reg); break; case AssignmentOp::AndAssignment: case AssignmentOp::OrAssignment: case AssignmentOp::NullishAssignment: break; // These are handled above. default: return Bytecode::CodeGenerationError { this, "Unimplemented operation"sv, }; } TRY(generator.emit_store_to_reference(lhs)); if (end_block_ptr) { generator.emit().set_targets( Bytecode::Label { *end_block_ptr }, {}); generator.switch_to_basic_block(*end_block_ptr); } return {}; } Bytecode::CodeGenerationErrorOr WhileStatement::generate_bytecode(Bytecode::Generator& generator) const { // test // jump if_false (true) end (false) body // body // jump always (true) test // end auto& test_block = generator.make_block(); auto& body_block = generator.make_block(); auto& end_block = generator.make_block(); // Init result register generator.emit(js_undefined()); auto result_reg = generator.allocate_register(); generator.emit(result_reg); // jump to the test block generator.emit().set_targets( Bytecode::Label { test_block }, {}); generator.switch_to_basic_block(test_block); TRY(m_test->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { body_block }, Bytecode::Label { end_block }); generator.switch_to_basic_block(body_block); generator.begin_continuable_scope(Bytecode::Label { test_block }); generator.begin_breakable_scope(Bytecode::Label { end_block }); TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); if (!generator.is_current_block_terminated()) { generator.emit().set_targets( Bytecode::Label { test_block }, {}); generator.switch_to_basic_block(end_block); generator.emit(result_reg); } return {}; } Bytecode::CodeGenerationErrorOr DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const { // jump always (true) body // test // jump if_false (true) end (false) body // body // jump always (true) test // end auto& test_block = generator.make_block(); auto& body_block = generator.make_block(); auto& end_block = generator.make_block(); // Init result register generator.emit(js_undefined()); auto result_reg = generator.allocate_register(); generator.emit(result_reg); // jump to the body block generator.emit().set_targets( Bytecode::Label { body_block }, {}); generator.switch_to_basic_block(test_block); TRY(m_test->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { body_block }, Bytecode::Label { end_block }); generator.switch_to_basic_block(body_block); generator.begin_continuable_scope(Bytecode::Label { test_block }); generator.begin_breakable_scope(Bytecode::Label { end_block }); TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); if (!generator.is_current_block_terminated()) { generator.emit().set_targets( Bytecode::Label { test_block }, {}); generator.switch_to_basic_block(end_block); generator.emit(result_reg); } return {}; } Bytecode::CodeGenerationErrorOr ForStatement::generate_bytecode(Bytecode::Generator& generator) const { // init // jump always (true) test // test // jump if_true (true) body (false) end // body // jump always (true) update // update // jump always (true) test // end // If 'test' is missing, fuse the 'test' and 'body' basic blocks // If 'update' is missing, fuse the 'body' and 'update' basic blocks Bytecode::BasicBlock* test_block_ptr { nullptr }; Bytecode::BasicBlock* body_block_ptr { nullptr }; Bytecode::BasicBlock* update_block_ptr { nullptr }; auto& end_block = generator.make_block(); bool has_lexical_environment = false; if (m_init) { if (m_init->is_variable_declaration()) { auto& variable_declaration = verify_cast(*m_init); if (variable_declaration.is_lexical_declaration()) { has_lexical_environment = true; // FIXME: Is Block correct? generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Block); bool is_const = variable_declaration.is_constant_declaration(); variable_declaration.for_each_bound_name([&](auto const& name) { auto index = generator.intern_identifier(name); generator.register_binding(index); generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, is_const); }); } } TRY(m_init->generate_bytecode(generator)); } body_block_ptr = &generator.make_block(); if (m_test) test_block_ptr = &generator.make_block(); else test_block_ptr = body_block_ptr; if (m_update) update_block_ptr = &generator.make_block(); else update_block_ptr = body_block_ptr; generator.emit(js_undefined()); auto result_reg = generator.allocate_register(); generator.emit(result_reg); generator.emit().set_targets( Bytecode::Label { *test_block_ptr }, {}); if (m_test) { generator.switch_to_basic_block(*test_block_ptr); TRY(m_test->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { *body_block_ptr }, Bytecode::Label { end_block }); } generator.switch_to_basic_block(*body_block_ptr); generator.begin_continuable_scope(Bytecode::Label { *update_block_ptr }); generator.begin_breakable_scope(Bytecode::Label { end_block }); TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); if (!generator.is_current_block_terminated()) { if (m_update) { generator.emit().set_targets( Bytecode::Label { *update_block_ptr }, {}); generator.switch_to_basic_block(*update_block_ptr); TRY(m_update->generate_bytecode(generator)); } generator.emit().set_targets( Bytecode::Label { *test_block_ptr }, {}); generator.switch_to_basic_block(end_block); generator.emit(result_reg); } if (has_lexical_environment) generator.end_variable_scope(); return {}; } Bytecode::CodeGenerationErrorOr ObjectExpression::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(); if (m_properties.is_empty()) return {}; auto object_reg = generator.allocate_register(); generator.emit(object_reg); for (auto& property : m_properties) { if (property.type() != ObjectProperty::Type::KeyValue) return Bytecode::CodeGenerationError { this, "Unimplemented property kind"sv, }; if (is(property.key())) { auto& string_literal = static_cast(property.key()); Bytecode::IdentifierTableIndex key_name = generator.intern_identifier(string_literal.value()); TRY(property.value().generate_bytecode(generator)); generator.emit(object_reg, key_name); } else { TRY(property.key().generate_bytecode(generator)); auto property_reg = generator.allocate_register(); generator.emit(property_reg); TRY(property.value().generate_bytecode(generator)); generator.emit(object_reg, property_reg); } } generator.emit(object_reg); return {}; } Bytecode::CodeGenerationErrorOr ArrayExpression::generate_bytecode(Bytecode::Generator& generator) const { Vector element_regs; for (auto& element : m_elements) { if (element && is(*element)) { return Bytecode::CodeGenerationError { this, "Unimplemented element kind: SpreadExpression"sv, }; } element_regs.append(generator.allocate_register()); } size_t i = 0; for (auto& element : m_elements) { if (element) { TRY(element->generate_bytecode(generator)); if (is(*element)) VERIFY_NOT_REACHED(); } else { generator.emit(Value {}); } auto& element_reg = element_regs[i++]; generator.emit(element_reg); } if (element_regs.is_empty()) { generator.emit(); } else { generator.emit_with_extra_register_slots(2u, AK::Array { element_regs.first(), element_regs.last() }); } return {}; } Bytecode::CodeGenerationErrorOr MemberExpression::generate_bytecode(Bytecode::Generator& generator) const { return generator.emit_load_from_reference(*this); } Bytecode::CodeGenerationErrorOr FunctionDeclaration::generate_bytecode(Bytecode::Generator& generator) const { if (m_is_hoisted) { auto index = generator.intern_identifier(name()); generator.emit(index); generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::Set, Bytecode::Op::EnvironmentMode::Var); } return {}; } Bytecode::CodeGenerationErrorOr FunctionExpression::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(*this); return {}; } static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode, Bytecode::Register const& value_reg); static Bytecode::CodeGenerationErrorOr generate_object_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg) { Vector excluded_property_names; auto has_rest = false; if (pattern.entries.size() > 0) has_rest = pattern.entries[pattern.entries.size() - 1].is_rest; for (auto& [name, alias, initializer, is_rest] : pattern.entries) { if (is_rest) { VERIFY(name.has>()); VERIFY(alias.has()); VERIFY(!initializer); auto identifier = name.get>()->string(); auto interned_identifier = generator.intern_identifier(identifier); generator.emit_with_extra_register_slots(excluded_property_names.size(), value_reg, excluded_property_names); generator.emit(interned_identifier, initialization_mode); return {}; } Bytecode::StringTableIndex name_index; if (name.has>()) { auto identifier = name.get>()->string(); name_index = generator.intern_string(identifier); if (has_rest) { auto excluded_name_reg = generator.allocate_register(); excluded_property_names.append(excluded_name_reg); generator.emit(name_index); generator.emit(excluded_name_reg); } generator.emit(value_reg); generator.emit(generator.intern_identifier(identifier)); } else { auto expression = name.get>(); TRY(expression->generate_bytecode(generator)); if (has_rest) { auto excluded_name_reg = generator.allocate_register(); excluded_property_names.append(excluded_name_reg); generator.emit(excluded_name_reg); } generator.emit(value_reg); } if (initializer) { auto& if_undefined_block = generator.make_block(); auto& if_not_undefined_block = generator.make_block(); generator.emit().set_targets( Bytecode::Label { if_undefined_block }, Bytecode::Label { if_not_undefined_block }); generator.switch_to_basic_block(if_undefined_block); TRY(initializer->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { if_not_undefined_block }, {}); generator.switch_to_basic_block(if_not_undefined_block); } if (alias.has>()) { auto& binding_pattern = *alias.get>(); auto nested_value_reg = generator.allocate_register(); generator.emit(nested_value_reg); TRY(generate_binding_pattern_bytecode(generator, binding_pattern, initialization_mode, nested_value_reg)); } else if (alias.has()) { if (name.has>()) { // This needs some sort of SetVariableByValue opcode, as it's a runtime binding return Bytecode::CodeGenerationError { name.get>().ptr(), "Unimplemented name/alias pair: Empty/Expression"sv, }; } auto& identifier = name.get>()->string(); generator.emit(generator.intern_identifier(identifier), initialization_mode); } else { auto& identifier = alias.get>()->string(); generator.emit(generator.intern_identifier(identifier), initialization_mode); } } return {}; } static Bytecode::CodeGenerationErrorOr generate_array_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg) { /* * Consider the following destructuring assignment: * * let [a, b, c, d, e] = o; * * It would be fairly trivial to just loop through this iterator, getting the value * at each step and assigning them to the binding sequentially. However, this is not * correct: once an iterator is exhausted, it must not be called again. This complicates * the bytecode. In order to accomplish this, we do the following: * * - Reserve a special boolean register which holds 'true' if the iterator is exhausted, * and false otherwise * - When we are retrieving the value which should be bound, we first check this register. * If it is 'true', we load undefined into the accumulator. Otherwise, we grab the next * value from the iterator and store it into the accumulator. * * Note that the is_exhausted register does not need to be loaded with false because the * first IteratorNext bytecode is _not_ proceeded by an exhausted check, as it is * unnecessary. */ auto is_iterator_exhausted_register = generator.allocate_register(); auto iterator_reg = generator.allocate_register(); generator.emit(value_reg); generator.emit(); generator.emit(iterator_reg); bool first = true; auto temp_iterator_result_reg = generator.allocate_register(); auto assign_accumulator_to_alias = [&](auto& alias) { return alias.visit( [&](Empty) -> Bytecode::CodeGenerationErrorOr { // This element is an elision return {}; }, [&](NonnullRefPtr const& identifier) -> Bytecode::CodeGenerationErrorOr { auto interned_index = generator.intern_identifier(identifier->string()); generator.emit(interned_index, initialization_mode); return {}; }, [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { // Store the accumulator value in a permanent register auto target_reg = generator.allocate_register(); generator.emit(target_reg); return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, target_reg); }, [&](NonnullRefPtr const& expr) -> Bytecode::CodeGenerationErrorOr { return Bytecode::CodeGenerationError { expr.ptr(), "Unimplemented alias mode: MemberExpression"sv, }; }); }; for (auto& [name, alias, initializer, is_rest] : pattern.entries) { VERIFY(name.has()); if (is_rest) { if (first) { // The iterator has not been called, and is thus known to be not exhausted generator.emit(iterator_reg); generator.emit(); } else { auto& if_exhausted_block = generator.make_block(); auto& if_not_exhausted_block = generator.make_block(); auto& continuation_block = generator.make_block(); generator.emit(is_iterator_exhausted_register); generator.emit().set_targets( Bytecode::Label { if_exhausted_block }, Bytecode::Label { if_not_exhausted_block }); generator.switch_to_basic_block(if_exhausted_block); generator.emit(); generator.emit().set_targets( Bytecode::Label { continuation_block }, {}); generator.switch_to_basic_block(if_not_exhausted_block); generator.emit(iterator_reg); generator.emit(); generator.emit().set_targets( Bytecode::Label { continuation_block }, {}); generator.switch_to_basic_block(continuation_block); } return assign_accumulator_to_alias(alias); } // In the first iteration of the loop, a few things are true which can save // us some bytecode: // - the iterator result is still in the accumulator, so we can avoid a load // - the iterator is not yet exhausted, which can save us a jump and some // creation auto& iterator_is_exhausted_block = generator.make_block(); if (!first) { auto& iterator_is_not_exhausted_block = generator.make_block(); generator.emit(is_iterator_exhausted_register); generator.emit().set_targets( Bytecode::Label { iterator_is_exhausted_block }, Bytecode::Label { iterator_is_not_exhausted_block }); generator.switch_to_basic_block(iterator_is_not_exhausted_block); generator.emit(iterator_reg); } generator.emit(); generator.emit(temp_iterator_result_reg); generator.emit(); generator.emit(is_iterator_exhausted_register); // We still have to check for exhaustion here. If the iterator is exhausted, // we need to bail before trying to get the value auto& no_bail_block = generator.make_block(); generator.emit().set_targets( Bytecode::Label { iterator_is_exhausted_block }, Bytecode::Label { no_bail_block }); generator.switch_to_basic_block(no_bail_block); // Get the next value in the iterator generator.emit(temp_iterator_result_reg); generator.emit(); auto& create_binding_block = generator.make_block(); generator.emit().set_targets( Bytecode::Label { create_binding_block }, {}); // The iterator is exhausted, so we just load undefined and continue binding generator.switch_to_basic_block(iterator_is_exhausted_block); generator.emit(js_undefined()); generator.emit().set_targets( Bytecode::Label { create_binding_block }, {}); // Create the actual binding. The value which this entry must bind is now in the // accumulator. We can proceed, processing the alias as a nested destructuring // pattern if necessary. generator.switch_to_basic_block(create_binding_block); TRY(assign_accumulator_to_alias(alias)); first = false; } return {}; } static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg) { if (pattern.kind == BindingPattern::Kind::Object) return generate_object_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg); return generate_array_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg); } static Bytecode::CodeGenerationErrorOr 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 const& id) -> Bytecode::CodeGenerationErrorOr { generator.emit(generator.intern_identifier(id->string()), initialization_mode, environment_mode); return {}; }, [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { auto value_register = generator.allocate_register(); generator.emit(value_register); return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register); }); } Bytecode::CodeGenerationErrorOr VariableDeclaration::generate_bytecode(Bytecode::Generator& generator) const { for (auto& declarator : m_declarations) { if (declarator.init()) TRY(declarator.init()->generate_bytecode(generator)); else generator.emit(js_undefined()); TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this)); } return {}; } Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode::Generator& generator) const { auto callee_reg = generator.allocate_register(); auto this_reg = generator.allocate_register(); generator.emit(js_undefined()); generator.emit(this_reg); if (is(this)) { TRY(m_callee->generate_bytecode(generator)); generator.emit(callee_reg); } else if (is(*m_callee)) { return Bytecode::CodeGenerationError { this, "Unimplemented callee kind: SuperExpression"sv, }; } else if (is(*m_callee)) { auto& member_expression = static_cast(*m_callee); if (is(member_expression.object())) { return Bytecode::CodeGenerationError { this, "Unimplemented callee kind: MemberExpression on SuperExpression"sv, }; } TRY(member_expression.object().generate_bytecode(generator)); generator.emit(this_reg); if (member_expression.is_computed()) { TRY(member_expression.property().generate_bytecode(generator)); generator.emit(this_reg); } else { auto identifier_table_ref = generator.intern_identifier(verify_cast(member_expression.property()).string()); generator.emit(identifier_table_ref); } generator.emit(callee_reg); } else { // FIXME: this = global object in sloppy mode. TRY(m_callee->generate_bytecode(generator)); generator.emit(callee_reg); } Vector argument_registers; for (auto& arg : m_arguments) { TRY(arg.value->generate_bytecode(generator)); auto arg_reg = generator.allocate_register(); generator.emit(arg_reg); argument_registers.append(arg_reg); } Bytecode::Op::Call::CallType call_type; if (is(*this)) { call_type = Bytecode::Op::Call::CallType::Construct; } else { call_type = Bytecode::Op::Call::CallType::Call; } generator.emit_with_extra_register_slots(argument_registers.size(), call_type, callee_reg, this_reg, argument_registers); return {}; } Bytecode::CodeGenerationErrorOr ReturnStatement::generate_bytecode(Bytecode::Generator& generator) const { if (m_argument) TRY(m_argument->generate_bytecode(generator)); if (generator.is_in_generator_or_async_function()) { generator.perform_needed_unwinds(); generator.emit(nullptr); } else { generator.perform_needed_unwinds(); generator.emit(); } return {}; } Bytecode::CodeGenerationErrorOr YieldExpression::generate_bytecode(Bytecode::Generator& generator) const { VERIFY(generator.is_in_generator_function()); if (m_is_yield_from) { return Bytecode::CodeGenerationError { this, "Unimplemented form: `yield*`"sv, }; } if (m_argument) TRY(m_argument->generate_bytecode(generator)); auto& continuation_block = generator.make_block(); generator.emit(Bytecode::Label { continuation_block }); generator.switch_to_basic_block(continuation_block); return {}; } Bytecode::CodeGenerationErrorOr IfStatement::generate_bytecode(Bytecode::Generator& generator) const { // test // jump if_true (true) true (false) false // true // jump always (true) end // false // jump always (true) end // end auto& true_block = generator.make_block(); auto& false_block = generator.make_block(); TRY(m_predicate->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { true_block }, Bytecode::Label { false_block }); Bytecode::Op::Jump* true_block_jump { nullptr }; generator.switch_to_basic_block(true_block); generator.emit(js_undefined()); TRY(m_consequent->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) true_block_jump = &generator.emit(); generator.switch_to_basic_block(false_block); auto& end_block = generator.make_block(); generator.emit(js_undefined()); if (m_alternate) TRY(m_alternate->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) generator.emit().set_targets(Bytecode::Label { end_block }, {}); if (true_block_jump) true_block_jump->set_targets(Bytecode::Label { end_block }, {}); generator.switch_to_basic_block(end_block); return {}; } Bytecode::CodeGenerationErrorOr ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const { generator.perform_needed_unwinds(); generator.emit().set_targets( generator.nearest_continuable_scope(), {}); return {}; } Bytecode::CodeGenerationErrorOr DebuggerStatement::generate_bytecode(Bytecode::Generator&) const { return {}; } Bytecode::CodeGenerationErrorOr ConditionalExpression::generate_bytecode(Bytecode::Generator& generator) const { // test // jump if_true (true) true (false) false // true // jump always (true) end // false // jump always (true) end // end auto& true_block = generator.make_block(); auto& false_block = generator.make_block(); auto& end_block = generator.make_block(); TRY(m_test->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { true_block }, Bytecode::Label { false_block }); generator.switch_to_basic_block(true_block); TRY(m_consequent->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { end_block }, {}); generator.switch_to_basic_block(false_block); TRY(m_alternate->generate_bytecode(generator)); generator.emit().set_targets( Bytecode::Label { end_block }, {}); generator.switch_to_basic_block(end_block); return {}; } Bytecode::CodeGenerationErrorOr SequenceExpression::generate_bytecode(Bytecode::Generator& generator) const { for (auto& expression : m_expressions) TRY(expression.generate_bytecode(generator)); return {}; } Bytecode::CodeGenerationErrorOr TemplateLiteral::generate_bytecode(Bytecode::Generator& generator) const { auto string_reg = generator.allocate_register(); for (size_t i = 0; i < m_expressions.size(); i++) { TRY(m_expressions[i].generate_bytecode(generator)); if (i == 0) { generator.emit(string_reg); } else { generator.emit(string_reg); } } generator.emit(string_reg); return {}; } Bytecode::CodeGenerationErrorOr TaggedTemplateLiteral::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_tag->generate_bytecode(generator)); auto tag_reg = generator.allocate_register(); generator.emit(tag_reg); Vector string_regs; auto& expressions = m_template_literal->expressions(); for (size_t i = 0; i < expressions.size(); ++i) { if (i % 2 != 0) continue; string_regs.append(generator.allocate_register()); } size_t reg_index = 0; for (size_t i = 0; i < expressions.size(); ++i) { if (i % 2 != 0) continue; TRY(expressions[i].generate_bytecode(generator)); auto string_reg = string_regs[reg_index++]; generator.emit(string_reg); } if (string_regs.is_empty()) { generator.emit(); } else { generator.emit_with_extra_register_slots(2u, AK::Array { string_regs.first(), string_regs.last() }); } auto strings_reg = generator.allocate_register(); generator.emit(strings_reg); Vector argument_regs; argument_regs.append(strings_reg); for (size_t i = 0; i < expressions.size(); ++i) { if (i % 2 == 0) continue; TRY(expressions[i].generate_bytecode(generator)); auto string_reg = generator.allocate_register(); generator.emit(string_reg); argument_regs.append(string_reg); } Vector raw_string_regs; for ([[maybe_unused]] auto& raw_string : m_template_literal->raw_strings()) string_regs.append(generator.allocate_register()); reg_index = 0; for (auto& raw_string : m_template_literal->raw_strings()) { TRY(raw_string.generate_bytecode(generator)); auto raw_string_reg = string_regs[reg_index++]; generator.emit(raw_string_reg); raw_string_regs.append(raw_string_reg); } if (raw_string_regs.is_empty()) { generator.emit(); } else { generator.emit_with_extra_register_slots(2u, AK::Array { raw_string_regs.first(), raw_string_regs.last() }); } auto raw_strings_reg = generator.allocate_register(); generator.emit(raw_strings_reg); generator.emit(strings_reg); generator.emit(raw_strings_reg, generator.intern_identifier("raw")); generator.emit(js_undefined()); auto this_reg = generator.allocate_register(); generator.emit(this_reg); generator.emit_with_extra_register_slots(argument_regs.size(), Bytecode::Op::Call::CallType::Call, tag_reg, this_reg, move(argument_regs)); return {}; } Bytecode::CodeGenerationErrorOr UpdateExpression::generate_bytecode(Bytecode::Generator& generator) const { TRY(generator.emit_load_from_reference(*m_argument)); Optional previous_value_for_postfix_reg; if (!m_prefixed) { previous_value_for_postfix_reg = generator.allocate_register(); generator.emit(*previous_value_for_postfix_reg); } if (m_op == UpdateOp::Increment) generator.emit(); else generator.emit(); TRY(generator.emit_store_to_reference(*m_argument)); if (!m_prefixed) generator.emit(*previous_value_for_postfix_reg); return {}; } Bytecode::CodeGenerationErrorOr ThrowStatement::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_argument->generate_bytecode(generator)); generator.perform_needed_unwinds(); generator.emit(); return {}; } Bytecode::CodeGenerationErrorOr BreakStatement::generate_bytecode(Bytecode::Generator& generator) const { generator.perform_needed_unwinds(true); generator.emit().set_targets( generator.nearest_breakable_scope(), {}); return {}; } Bytecode::CodeGenerationErrorOr TryStatement::generate_bytecode(Bytecode::Generator& generator) const { auto& saved_block = generator.current_block(); Optional handler_target; Optional finalizer_target; Bytecode::BasicBlock* next_block { nullptr }; if (m_finalizer) { auto& finalizer_block = generator.make_block(); generator.switch_to_basic_block(finalizer_block); TRY(m_finalizer->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) { next_block = &generator.make_block(); auto next_target = Bytecode::Label { *next_block }; generator.emit(next_target); } finalizer_target = Bytecode::Label { finalizer_block }; } if (m_handler) { auto& handler_block = generator.make_block(); generator.switch_to_basic_block(handler_block); generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical, Bytecode::Generator::SurroundingScopeKind::Block); TRY(m_handler->parameter().visit( [&](FlyString const& parameter) -> Bytecode::CodeGenerationErrorOr { if (!parameter.is_empty()) { auto parameter_identifier = generator.intern_identifier(parameter); generator.register_binding(parameter_identifier); generator.emit(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false); generator.emit(parameter_identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize); } return {}; }, [&](NonnullRefPtr const&) -> Bytecode::CodeGenerationErrorOr { // FIXME: Implement this path when the above DeclarativeEnvironment issue is dealt with. return Bytecode::CodeGenerationError { this, "Unimplemented catch argument: BindingPattern"sv, }; })); TRY(m_handler->body().generate_bytecode(generator)); handler_target = Bytecode::Label { handler_block }; generator.end_variable_scope(); if (!generator.is_current_block_terminated()) { if (m_finalizer) { generator.emit(); generator.emit(finalizer_target); } else { VERIFY(!next_block); next_block = &generator.make_block(); auto next_target = Bytecode::Label { *next_block }; generator.emit(next_target); } } } auto& target_block = generator.make_block(); generator.switch_to_basic_block(saved_block); generator.emit(Bytecode::Label { target_block }, handler_target, finalizer_target); generator.start_boundary(Bytecode::Generator::BlockBoundaryType::Unwind); generator.switch_to_basic_block(target_block); TRY(m_block->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) { if (m_finalizer) { generator.emit(finalizer_target); } else { auto& block = generator.make_block(); generator.emit(Bytecode::Label { block }); next_block = █ } } generator.end_boundary(Bytecode::Generator::BlockBoundaryType::Unwind); generator.switch_to_basic_block(next_block ? *next_block : saved_block); return {}; } Bytecode::CodeGenerationErrorOr SwitchStatement::generate_bytecode(Bytecode::Generator& generator) const { auto discriminant_reg = generator.allocate_register(); TRY(m_discriminant->generate_bytecode(generator)); generator.emit(discriminant_reg); Vector case_blocks; Bytecode::BasicBlock* default_block { nullptr }; Bytecode::BasicBlock* next_test_block = &generator.make_block(); generator.emit().set_targets(Bytecode::Label { *next_test_block }, {}); for (auto& switch_case : m_cases) { auto& case_block = generator.make_block(); if (switch_case.test()) { generator.switch_to_basic_block(*next_test_block); TRY(switch_case.test()->generate_bytecode(generator)); generator.emit(discriminant_reg); next_test_block = &generator.make_block(); generator.emit().set_targets(Bytecode::Label { case_block }, Bytecode::Label { *next_test_block }); } else { default_block = &case_block; } case_blocks.append(case_block); } generator.switch_to_basic_block(*next_test_block); auto& end_block = generator.make_block(); if (default_block != nullptr) { generator.emit().set_targets(Bytecode::Label { *default_block }, {}); } else { generator.emit(js_undefined()); generator.emit().set_targets(Bytecode::Label { end_block }, {}); } auto current_block = case_blocks.begin(); generator.begin_breakable_scope(Bytecode::Label { end_block }); for (auto& switch_case : m_cases) { generator.switch_to_basic_block(*current_block); generator.emit(js_undefined()); for (auto& statement : switch_case.children()) { TRY(statement.generate_bytecode(generator)); if (generator.is_current_block_terminated()) break; } if (!generator.is_current_block_terminated()) { auto next_block = current_block; next_block++; if (next_block.is_end()) { generator.emit().set_targets(Bytecode::Label { end_block }, {}); } else { generator.emit().set_targets(Bytecode::Label { *next_block }, {}); } } current_block++; } generator.end_breakable_scope(); generator.switch_to_basic_block(end_block); return {}; } Bytecode::CodeGenerationErrorOr ClassDeclaration::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_class_expression->generate_bytecode(generator)); generator.emit(generator.intern_identifier(m_class_expression.ptr()->name()), Bytecode::Op::SetVariable::InitializationMode::Initialize); return {}; } Bytecode::CodeGenerationErrorOr ClassExpression::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(*this); return {}; } Bytecode::CodeGenerationErrorOr ThisExpression::generate_bytecode(Bytecode::Generator& generator) const { generator.emit(); return {}; } Bytecode::CodeGenerationErrorOr AwaitExpression::generate_bytecode(Bytecode::Generator& generator) const { VERIFY(generator.is_in_async_function()); // Transform `await expr` to `yield expr` TRY(m_argument->generate_bytecode(generator)); auto& continuation_block = generator.make_block(); generator.emit(Bytecode::Label { continuation_block }); generator.switch_to_basic_block(continuation_block); return {}; } Bytecode::CodeGenerationErrorOr WithStatement::generate_bytecode(Bytecode::Generator& generator) const { TRY(m_object->generate_bytecode(generator)); generator.emit(); // EnterObjectEnvironment sets the running execution context's lexical_environment to a new Object Environment. generator.start_boundary(Bytecode::Generator::BlockBoundaryType::LeaveLexicalEnvironment); TRY(m_body->generate_bytecode(generator)); generator.end_boundary(Bytecode::Generator::BlockBoundaryType::LeaveLexicalEnvironment); if (!generator.is_current_block_terminated()) generator.emit(Bytecode::Op::EnvironmentMode::Lexical); 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 for_in_of_head_evaluation(Bytecode::Generator& generator, IterationKind iteration_kind, Variant, NonnullRefPtr> const& lhs, NonnullRefPtr const& rhs) { ForInOfHeadEvaluationResult result {}; if (auto* ast_ptr = lhs.get_pointer>(); ast_ptr && is(**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(**ast_ptr); result.is_destructuring = variable_declaration.declarations().first().target().has>(); 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(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(); 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(true); generator.emit().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(); } // 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(); } } 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(); } 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 for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant, NonnullRefPtr> 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(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::Label { loop_update }); generator.switch_to_basic_block(loop_update); // a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). generator.emit(iterator_register); generator.emit(); // 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(iterator_result_register); generator.emit(); // e. If done is true, return V. auto& loop_continue = generator.make_block(); generator.emit().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(iterator_result_register); generator.emit(); // 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(*lhs.get>()); 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>())); } } } // 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(*lhs.get>()); // 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(identifier, Bytecode::Op::EnvironmentMode::Lexical, true); } // b. Else, else { // i. Perform ! environment.CreateMutableBinding(name, false). generator.emit(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>()->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(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().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 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 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); } }