/* * Copyright (c) 2021, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace JS::Bytecode { Generator::Generator() : m_string_table(make()) , m_identifier_table(make()) { } CodeGenerationErrorOr> Generator::generate(ASTNode const& node, FunctionKind enclosing_function_kind) { Generator generator; generator.switch_to_basic_block(generator.make_block()); generator.m_enclosing_function_kind = enclosing_function_kind; if (generator.is_in_generator_or_async_function()) { // Immediately yield with no value. auto& start_block = generator.make_block(); generator.emit(Label { start_block }); generator.switch_to_basic_block(start_block); // NOTE: This doesn't have to handle received throw/return completions, as GeneratorObject::resume_abrupt // will not enter the generator from the SuspendedStart state and immediately completes the generator. } TRY(node.generate_bytecode(generator)); if (generator.is_in_generator_or_async_function()) { // Terminate all unterminated blocks with yield return for (auto& block : generator.m_root_basic_blocks) { if (block->is_terminated()) continue; generator.switch_to_basic_block(*block); generator.emit(js_undefined()); generator.emit(nullptr); } } bool is_strict_mode = false; if (is(node)) is_strict_mode = static_cast(node).is_strict_mode(); else if (is(node)) is_strict_mode = static_cast(node).in_strict_mode(); else if (is(node)) is_strict_mode = static_cast(node).is_strict_mode(); else if (is(node)) is_strict_mode = static_cast(node).is_strict_mode(); return adopt_own(*new Executable { .name = {}, .basic_blocks = move(generator.m_root_basic_blocks), .string_table = move(generator.m_string_table), .identifier_table = move(generator.m_identifier_table), .number_of_registers = generator.m_next_register, .is_strict_mode = is_strict_mode, }); } void Generator::grow(size_t additional_size) { VERIFY(m_current_basic_block); m_current_basic_block->grow(additional_size); } void* Generator::next_slot() { VERIFY(m_current_basic_block); return m_current_basic_block->next_slot(); } Register Generator::allocate_register() { VERIFY(m_next_register != NumericLimits::max()); return Register { m_next_register++ }; } Label Generator::nearest_continuable_scope() const { return m_continuable_scopes.last().bytecode_target; } void Generator::block_declaration_instantiation(ScopeNode const& scope_node) { start_boundary(BlockBoundaryType::LeaveLexicalEnvironment); emit(scope_node); } void Generator::begin_variable_scope() { start_boundary(BlockBoundaryType::LeaveLexicalEnvironment); emit(); } void Generator::end_variable_scope() { end_boundary(BlockBoundaryType::LeaveLexicalEnvironment); if (!m_current_basic_block->is_terminated()) { emit(); } } void Generator::begin_continuable_scope(Label continue_target, Vector const& language_label_set) { m_continuable_scopes.append({ continue_target, language_label_set }); start_boundary(BlockBoundaryType::Continue); } void Generator::end_continuable_scope() { m_continuable_scopes.take_last(); end_boundary(BlockBoundaryType::Continue); } Label Generator::nearest_breakable_scope() const { return m_breakable_scopes.last().bytecode_target; } void Generator::begin_breakable_scope(Label breakable_target, Vector const& language_label_set) { m_breakable_scopes.append({ breakable_target, language_label_set }); start_boundary(BlockBoundaryType::Break); } void Generator::end_breakable_scope() { m_breakable_scopes.take_last(); end_boundary(BlockBoundaryType::Break); } CodeGenerationErrorOr Generator::emit_super_reference(MemberExpression const& expression) { VERIFY(is(expression.object())); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation // 1. Let env be GetThisEnvironment(). // 2. Let actualThis be ? env.GetThisBinding(). auto actual_this_register = allocate_register(); emit(); emit(actual_this_register); Optional computed_property_value_register; if (expression.is_computed()) { // SuperProperty : super [ Expression ] // 3. Let propertyNameReference be ? Evaluation of Expression. // 4. Let propertyNameValue be ? GetValue(propertyNameReference). TRY(expression.property().generate_bytecode(*this)); computed_property_value_register = allocate_register(); emit(*computed_property_value_register); } // 5/7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). // https://tc39.es/ecma262/#sec-makesuperpropertyreference // 1. Let env be GetThisEnvironment(). // 2. Assert: env.HasSuperBinding() is true. // 3. Let baseValue be ? env.GetSuperBase(). auto super_base_register = allocate_register(); emit(); emit(super_base_register); // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. return ReferenceRegisters { .base = super_base_register, .referenced_name = move(computed_property_value_register), .this_value = actual_this_register, }; } CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode const& node) { if (is(node)) { auto& identifier = static_cast(node); TRY(identifier.generate_bytecode(*this)); return {}; } if (is(node)) { auto& expression = static_cast(node); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation if (is(expression.object())) { auto super_reference = TRY(emit_super_reference(expression)); if (super_reference.referenced_name.has_value()) { // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! emit(*super_reference.referenced_name); emit(super_reference.base, super_reference.this_value); } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(identifier_table_ref, super_reference.this_value); } } else { TRY(expression.object().generate_bytecode(*this)); if (expression.is_computed()) { auto object_reg = allocate_register(); emit(object_reg); TRY(expression.property().generate_bytecode(*this)); emit(object_reg); } else if (expression.property().is_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(identifier_table_ref); } else if (expression.property().is_private_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(identifier_table_ref); } else { return CodeGenerationError { &expression, "Unimplemented non-computed member expression"sv }; } } return {}; } VERIFY_NOT_REACHED(); } CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const& node) { if (is(node)) { auto& identifier = static_cast(node); emit_set_variable(identifier); return {}; } if (is(node)) { // NOTE: The value is in the accumulator, so we have to store that away first. auto value_reg = allocate_register(); emit(value_reg); auto& expression = static_cast(node); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation if (is(expression.object())) { auto super_reference = TRY(emit_super_reference(expression)); emit(value_reg); // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. if (super_reference.referenced_name.has_value()) { // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! emit(super_reference.base, *super_reference.referenced_name, super_reference.this_value); } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(super_reference.base, super_reference.this_value, identifier_table_ref); } } else { TRY(expression.object().generate_bytecode(*this)); auto object_reg = allocate_register(); emit(object_reg); if (expression.is_computed()) { TRY(expression.property().generate_bytecode(*this)); auto property_reg = allocate_register(); emit(property_reg); emit(value_reg); emit(object_reg, property_reg); } else if (expression.property().is_identifier()) { emit(value_reg); auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(object_reg, identifier_table_ref); } else if (expression.property().is_private_identifier()) { emit(value_reg); auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(object_reg, identifier_table_ref); } else { return CodeGenerationError { &expression, "Unimplemented non-computed member expression"sv }; } } return {}; } return CodeGenerationError { &node, "Unimplemented/invalid node used a reference"sv }; } CodeGenerationErrorOr Generator::emit_delete_reference(JS::ASTNode const& node) { if (is(node)) { auto& identifier = static_cast(node); emit(intern_identifier(identifier.string())); return {}; } if (is(node)) { auto& expression = static_cast(node); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation if (is(expression.object())) { auto super_reference = TRY(emit_super_reference(expression)); if (super_reference.referenced_name.has_value()) { emit(super_reference.this_value, *super_reference.referenced_name); } else { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(super_reference.this_value, identifier_table_ref); } return {}; } TRY(expression.object().generate_bytecode(*this)); if (expression.is_computed()) { auto object_reg = allocate_register(); emit(object_reg); TRY(expression.property().generate_bytecode(*this)); emit(object_reg); } else if (expression.property().is_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(identifier_table_ref); } else { // NOTE: Trying to delete a private field generates a SyntaxError in the parser. return CodeGenerationError { &expression, "Unimplemented non-computed member expression"sv }; } return {}; } // Though this will have no deletion effect, we still have to evaluate the node as it can have side effects. // For example: delete a(); delete ++c.b; etc. // 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation // 1. Let ref be the result of evaluating UnaryExpression. // 2. ReturnIfAbrupt(ref). TRY(node.generate_bytecode(*this)); // 3. If ref is not a Reference Record, return true. emit(Value(true)); // NOTE: The rest of the steps are handled by Delete{Variable,ByValue,Id}. return {}; } void Generator::emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Op::EnvironmentMode mode) { if (identifier.is_local()) { emit(identifier.local_variable_index()); } else { emit(intern_identifier(identifier.string()), initialization_mode, mode); } } void Generator::generate_break() { bool last_was_finally = false; // FIXME: Reduce code duplication for (size_t i = m_boundaries.size(); i > 0; --i) { auto boundary = m_boundaries[i - 1]; using enum BlockBoundaryType; switch (boundary) { case Break: emit().set_targets(nearest_breakable_scope(), {}); return; case Unwind: if (!last_was_finally) emit(); last_was_finally = false; break; case LeaveLexicalEnvironment: emit(); break; case Continue: break; case ReturnToFinally: { auto& block = make_block(DeprecatedString::formatted("{}.break", current_block().name())); emit(Label { block }); switch_to_basic_block(block); last_was_finally = true; break; }; } } VERIFY_NOT_REACHED(); } void Generator::generate_break(DeprecatedFlyString const& break_label) { size_t current_boundary = m_boundaries.size(); bool last_was_finally = false; for (auto const& breakable_scope : m_breakable_scopes.in_reverse()) { for (; current_boundary > 0; --current_boundary) { auto boundary = m_boundaries[current_boundary - 1]; if (boundary == BlockBoundaryType::Unwind) { if (!last_was_finally) emit(); last_was_finally = false; } else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) { emit(); } else if (boundary == BlockBoundaryType::ReturnToFinally) { auto& block = make_block(DeprecatedString::formatted("{}.break", current_block().name())); emit(Label { block }); switch_to_basic_block(block); last_was_finally = true; } else if (boundary == BlockBoundaryType::Break) { // Make sure we don't process this boundary twice if the current breakable scope doesn't contain the target label. --current_boundary; break; } } if (breakable_scope.language_label_set.contains_slow(break_label)) { emit().set_targets(breakable_scope.bytecode_target, {}); return; } } // We must have a breakable scope available that contains the label, as this should be enforced by the parser. VERIFY_NOT_REACHED(); } void Generator::generate_continue() { bool last_was_finally = false; // FIXME: Reduce code duplication for (size_t i = m_boundaries.size(); i > 0; --i) { auto boundary = m_boundaries[i - 1]; using enum BlockBoundaryType; switch (boundary) { case Continue: emit().set_targets(nearest_continuable_scope(), {}); return; case Unwind: if (!last_was_finally) emit(); last_was_finally = false; break; case LeaveLexicalEnvironment: emit(); break; case Break: break; case ReturnToFinally: { auto& block = make_block(DeprecatedString::formatted("{}.continue", current_block().name())); emit(Label { block }); switch_to_basic_block(block); last_was_finally = true; break; }; } } VERIFY_NOT_REACHED(); } void Generator::generate_continue(DeprecatedFlyString const& continue_label) { size_t current_boundary = m_boundaries.size(); bool last_was_finally = false; for (auto const& continuable_scope : m_continuable_scopes.in_reverse()) { for (; current_boundary > 0; --current_boundary) { auto boundary = m_boundaries[current_boundary - 1]; if (boundary == BlockBoundaryType::Unwind) { if (!last_was_finally) emit(); last_was_finally = false; } else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) { emit(); } else if (boundary == BlockBoundaryType::ReturnToFinally) { auto& block = make_block(DeprecatedString::formatted("{}.continue", current_block().name())); emit(Label { block }); switch_to_basic_block(block); last_was_finally = true; } else if (boundary == BlockBoundaryType::Continue) { // Make sure we don't process this boundary twice if the current continuable scope doesn't contain the target label. --current_boundary; break; } } if (continuable_scope.language_label_set.contains_slow(continue_label)) { emit().set_targets(continuable_scope.bytecode_target, {}); return; } } // We must have a continuable scope available that contains the label, as this should be enforced by the parser. VERIFY_NOT_REACHED(); } void Generator::push_home_object(Register register_) { m_home_objects.append(register_); } void Generator::pop_home_object() { m_home_objects.take_last(); } void Generator::emit_new_function(FunctionExpression const& function_node, Optional lhs_name) { if (m_home_objects.is_empty()) emit(function_node, lhs_name); else emit(function_node, lhs_name, m_home_objects.last()); } CodeGenerationErrorOr Generator::emit_named_evaluation_if_anonymous_function(Expression const& expression, Optional lhs_name) { if (is(expression)) { auto const& function_expression = static_cast(expression); if (!function_expression.has_name()) { TRY(function_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name))); return {}; } } if (is(expression)) { auto const& class_expression = static_cast(expression); if (!class_expression.has_name()) { TRY(class_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name))); return {}; } } TRY(expression.generate_bytecode(*this)); return {}; } }