/* * 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::begin_variable_scope(BindingMode mode, SurroundingScopeKind kind) { m_variable_scopes.append({ kind, mode, {} }); if (mode != BindingMode::Global) { start_boundary(mode == BindingMode::Lexical ? BlockBoundaryType::LeaveLexicalEnvironment : BlockBoundaryType::LeaveVariableEnvironment); emit( mode == BindingMode::Lexical ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var); } } void Generator::end_variable_scope() { auto mode = m_variable_scopes.take_last().mode; if (mode != BindingMode::Global) { end_boundary(mode == BindingMode::Lexical ? BlockBoundaryType::LeaveLexicalEnvironment : BlockBoundaryType::LeaveVariableEnvironment); if (!m_current_basic_block->is_terminated()) { emit( mode == BindingMode::Lexical ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var); } } } 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_load_from_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); 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 { 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(intern_identifier(identifier.string())); 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); 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 { 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); 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 {}; } Label Generator::perform_needed_unwinds_for_labelled_break_and_return_target_block(FlyString const& break_label) { size_t current_boundary = m_boundaries.size(); for (auto& breakable_scope : m_breakable_scopes.in_reverse()) { for (; current_boundary > 0; --current_boundary) { auto boundary = m_boundaries[current_boundary - 1]; // FIXME: Handle ReturnToFinally in a graceful manner // We need to execute the finally block, but tell it to resume // execution at the designated label if (boundary == BlockBoundaryType::Unwind) { emit(); } else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) { emit(Bytecode::Op::EnvironmentMode::Lexical); } else if (boundary == BlockBoundaryType::LeaveVariableEnvironment) { emit(Bytecode::Op::EnvironmentMode::Var); } else if (boundary == BlockBoundaryType::ReturnToFinally) { // FIXME: We need to enter the `finally`, while still scheduling the break to happen } 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)) return breakable_scope.bytecode_target; } // We must have a breakable scope available that contains the label, as this should be enforced by the parser. VERIFY_NOT_REACHED(); } Label Generator::perform_needed_unwinds_for_labelled_continue_and_return_target_block(FlyString const& continue_label) { size_t current_boundary = m_boundaries.size(); for (auto& continuable_scope : m_continuable_scopes.in_reverse()) { for (; current_boundary > 0; --current_boundary) { auto boundary = m_boundaries[current_boundary - 1]; // FIXME: Handle ReturnToFinally in a graceful manner // We need to execute the finally block, but tell it to resume // execution at the designated label if (boundary == BlockBoundaryType::Unwind) { emit(); } else if (boundary == BlockBoundaryType::LeaveLexicalEnvironment) { emit(Bytecode::Op::EnvironmentMode::Lexical); } else if (boundary == BlockBoundaryType::LeaveVariableEnvironment) { emit(Bytecode::Op::EnvironmentMode::Var); } else if (boundary == BlockBoundaryType::ReturnToFinally) { // FIXME: We need to enter the `finally`, while still scheduling the continue to happen } 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)) return continuable_scope.bytecode_target; } // We must have a continuable scope available that contains the label, as this should be enforced by the parser. VERIFY_NOT_REACHED(); } DeprecatedString CodeGenerationError::to_deprecated_string() { return DeprecatedString::formatted("CodeGenerationError in {}: {}", failing_node ? failing_node->class_name() : "", reason_literal); } }