/* * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include namespace JS::Bytecode { Generator::Generator(VM& vm, GC::Ptr function, MustPropagateCompletion must_propagate_completion) : m_vm(vm) , m_string_table(make()) , m_identifier_table(make()) , m_regex_table(make()) , m_constants(vm.heap()) , m_accumulator(*this, Operand(Register::accumulator())) , m_this_value(*this, Operand(Register::this_value())) , m_must_propagate_completion(must_propagate_completion == MustPropagateCompletion::Yes) , m_function(function) { } CodeGenerationErrorOr Generator::emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function) { if (function.m_has_parameter_expressions) { emit(); } for (auto const& parameter_name : function.m_parameter_names) { if (parameter_name.value == ECMAScriptFunctionObject::ParameterIsLocal::No) { auto id = intern_identifier(parameter_name.key); emit(id, Op::EnvironmentMode::Lexical, false); if (function.m_has_duplicates) { emit(id, add_constant(js_undefined())); } } } if (function.m_arguments_object_needed) { Optional dst; auto local_var_index = function.m_local_variables_names.find_first_index("arguments"sv); if (local_var_index.has_value()) dst = local(local_var_index.value()); if (function.m_strict || !function.has_simple_parameter_list()) { emit(dst, Op::CreateArguments::Kind::Unmapped, function.m_strict); } else { emit(dst, Op::CreateArguments::Kind::Mapped, function.m_strict); } } auto const& formal_parameters = function.formal_parameters(); for (u32 param_index = 0; param_index < formal_parameters.size(); ++param_index) { auto const& parameter = formal_parameters[param_index]; if (parameter.is_rest) { auto argument_reg = allocate_register(); emit(argument_reg.operand(), param_index); emit(param_index, argument_reg.operand()); } else if (parameter.default_value) { auto& if_undefined_block = make_block(); auto& if_not_undefined_block = make_block(); auto argument_reg = allocate_register(); emit(argument_reg.operand(), param_index); emit( argument_reg.operand(), Label { if_undefined_block }, Label { if_not_undefined_block }); switch_to_basic_block(if_undefined_block); auto operand = TRY(parameter.default_value->generate_bytecode(*this)); emit(param_index, *operand); emit(Label { if_not_undefined_block }); switch_to_basic_block(if_not_undefined_block); } if (auto const* identifier = parameter.binding.get_pointer>(); identifier) { if ((*identifier)->is_local()) { auto local_variable_index = (*identifier)->local_variable_index(); emit(local(local_variable_index), param_index); set_local_initialized((*identifier)->local_variable_index()); } else { auto id = intern_identifier((*identifier)->string()); auto argument_reg = allocate_register(); emit(argument_reg.operand(), param_index); if (function.m_has_duplicates) { emit(id, argument_reg.operand()); } else { emit(id, argument_reg.operand()); } } } else if (auto const* binding_pattern = parameter.binding.get_pointer>(); binding_pattern) { auto input_operand = allocate_register(); emit(input_operand.operand(), param_index); auto init_mode = function.m_has_duplicates ? Op::BindingInitializationMode::Set : Bytecode::Op::BindingInitializationMode::Initialize; TRY((*binding_pattern)->generate_bytecode(*this, init_mode, input_operand, false)); } } ScopeNode const* scope_body = nullptr; if (is(*function.m_ecmascript_code)) scope_body = static_cast(function.m_ecmascript_code.ptr()); if (!function.m_has_parameter_expressions) { if (scope_body) { for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) { auto const& id = variable_to_initialize.identifier; if (id.is_local()) { emit(local(id.local_variable_index()), add_constant(js_undefined())); } else { auto intern_id = intern_identifier(id.string()); emit(intern_id, Op::EnvironmentMode::Var, false); emit(intern_id, add_constant(js_undefined())); } } } } else { emit(function.m_var_environment_bindings_count); if (scope_body) { for (auto const& variable_to_initialize : function.m_var_names_to_initialize_binding) { auto const& id = variable_to_initialize.identifier; auto initial_value = allocate_register(); if (!variable_to_initialize.parameter_binding || variable_to_initialize.function_name) { emit(initial_value, add_constant(js_undefined())); } else { if (id.is_local()) { emit(initial_value, local(id.local_variable_index())); } else { emit(initial_value, intern_identifier(id.string())); } } if (id.is_local()) { emit(local(id.local_variable_index()), initial_value); } else { auto intern_id = intern_identifier(id.string()); emit(intern_id, Op::EnvironmentMode::Var, false); emit(intern_id, initial_value); } } } } if (!function.m_strict && scope_body) { for (auto const& function_name : function.m_function_names_to_initialize_binding) { auto intern_id = intern_identifier(function_name); emit(intern_id, Op::EnvironmentMode::Var, false); emit(intern_id, add_constant(js_undefined())); } } if (!function.m_strict) { bool can_elide_declarative_environment = !function.m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations()); if (!can_elide_declarative_environment) { emit(function.m_lex_environment_bindings_count); } } if (scope_body) { MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) { MUST(declaration.for_each_bound_identifier([&](auto const& id) { if (id.is_local()) { return; } emit(intern_identifier(id.string()), Op::EnvironmentMode::Lexical, declaration.is_constant_declaration(), false, declaration.is_constant_declaration()); })); })); } for (auto const& declaration : function.m_functions_to_initialize) { auto function = allocate_register(); emit(function, declaration, OptionalNone {}); if (declaration.name_identifier()->is_local()) { emit(local(declaration.name_identifier()->local_variable_index()), function); } else { emit(intern_identifier(declaration.name()), function); } } return {}; } CodeGenerationErrorOr> Generator::compile(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GC::Ptr function, MustPropagateCompletion must_propagate_completion, Vector local_variable_names) { Generator generator(vm, function, must_propagate_completion); generator.switch_to_basic_block(generator.make_block()); SourceLocationScope scope(generator, node); generator.m_enclosing_function_kind = enclosing_function_kind; if (generator.is_in_async_function() && !generator.is_in_generator_function()) { // Immediately yield with no value. auto& start_block = generator.make_block(); generator.emit(Label { start_block }, generator.add_constant(js_undefined())); 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. } if (function) TRY(generator.emit_function_declaration_instantiation(*function)); if (generator.is_in_generator_function()) { // Immediately yield with no value. auto& start_block = generator.make_block(); generator.emit(Label { start_block }, generator.add_constant(js_undefined())); 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. } auto last_value = TRY(node.generate_bytecode(generator)); if (!generator.current_block().is_terminated() && last_value.has_value()) { generator.emit(last_value.value()); } 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_return(generator.add_constant(js_undefined())); } } 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(); size_t size_needed = 0; for (auto& block : generator.m_root_basic_blocks) { size_needed += block->size(); } Vector bytecode; bytecode.ensure_capacity(size_needed); Vector basic_block_start_offsets; basic_block_start_offsets.ensure_capacity(generator.m_root_basic_blocks.size()); HashMap block_offsets; Vector label_offsets; struct UnlinkedExceptionHandlers { size_t start_offset; size_t end_offset; BasicBlock const* handler; BasicBlock const* finalizer; }; Vector unlinked_exception_handlers; HashMap source_map; Optional undefined_constant; for (auto& block : generator.m_root_basic_blocks) { if (!block->is_terminated()) { // NOTE: We must ensure that the "undefined" constant, which will be used by the not yet // emitted End instruction, is taken into account while shifting local operands by the // number of constants. undefined_constant = generator.add_constant(js_undefined()); break; } } auto number_of_registers = generator.m_next_register; auto number_of_constants = generator.m_constants.size(); // Pass: Rewrite the bytecode to use the correct register and constant indices. for (auto& block : generator.m_root_basic_blocks) { Bytecode::InstructionStreamIterator it(block->instruction_stream()); while (!it.at_end()) { auto& instruction = const_cast(*it); instruction.visit_operands([number_of_registers, number_of_constants](Operand& operand) { switch (operand.type()) { case Operand::Type::Register: break; case Operand::Type::Local: operand.offset_index_by(number_of_registers + number_of_constants); break; case Operand::Type::Constant: operand.offset_index_by(number_of_registers); break; default: VERIFY_NOT_REACHED(); } }); ++it; } } // Also rewrite the `undefined` constant if we have one for inserting End. if (undefined_constant.has_value()) undefined_constant.value().operand().offset_index_by(number_of_registers); for (auto& block : generator.m_root_basic_blocks) { basic_block_start_offsets.append(bytecode.size()); if (block->handler() || block->finalizer()) { unlinked_exception_handlers.append({ .start_offset = bytecode.size(), .end_offset = 0, .handler = block->handler(), .finalizer = block->finalizer(), }); } block_offsets.set(block.ptr(), bytecode.size()); for (auto& [offset, source_record] : block->source_map()) { source_map.set(bytecode.size() + offset, source_record); } Bytecode::InstructionStreamIterator it(block->instruction_stream()); while (!it.at_end()) { auto& instruction = const_cast(*it); if (instruction.type() == Instruction::Type::Jump) { auto& jump = static_cast(instruction); // OPTIMIZATION: Don't emit jumps that just jump to the next block. if (jump.target().basic_block_index() == block->index() + 1) { if (basic_block_start_offsets.last() == bytecode.size()) { // This block is empty, just skip it. basic_block_start_offsets.take_last(); } ++it; continue; } // OPTIMIZATION: For jumps to a return-or-end-only block, we can emit a `Return` or `End` directly instead. auto& target_block = *generator.m_root_basic_blocks[jump.target().basic_block_index()]; if (target_block.is_terminated()) { auto target_instruction_iterator = InstructionStreamIterator { target_block.instruction_stream() }; auto& target_instruction = *target_instruction_iterator; if (target_instruction.type() == Instruction::Type::Return) { auto& return_instruction = static_cast(target_instruction); Op::Return return_op(return_instruction.value()); bytecode.append(reinterpret_cast(&return_op), return_op.length()); ++it; continue; } if (target_instruction.type() == Instruction::Type::End) { auto& return_instruction = static_cast(target_instruction); Op::End end_op(return_instruction.value()); bytecode.append(reinterpret_cast(&end_op), end_op.length()); ++it; continue; } } } // OPTIMIZATION: For `JumpIf` where one of the targets is the very next block, // we can emit a `JumpTrue` or `JumpFalse` (to the other block) instead. if (instruction.type() == Instruction::Type::JumpIf) { auto& jump = static_cast(instruction); if (jump.true_target().basic_block_index() == block->index() + 1) { Op::JumpFalse jump_false(jump.condition(), Label { jump.false_target() }); auto& label = jump_false.target(); size_t label_offset = bytecode.size() + (bit_cast(&label) - bit_cast(&jump_false)); label_offsets.append(label_offset); bytecode.append(reinterpret_cast(&jump_false), jump_false.length()); ++it; continue; } if (jump.false_target().basic_block_index() == block->index() + 1) { Op::JumpTrue jump_true(jump.condition(), Label { jump.true_target() }); auto& label = jump_true.target(); size_t label_offset = bytecode.size() + (bit_cast(&label) - bit_cast(&jump_true)); label_offsets.append(label_offset); bytecode.append(reinterpret_cast(&jump_true), jump_true.length()); ++it; continue; } } instruction.visit_labels([&](Label& label) { size_t label_offset = bytecode.size() + (bit_cast(&label) - bit_cast(&instruction)); label_offsets.append(label_offset); }); bytecode.append(reinterpret_cast(&instruction), instruction.length()); ++it; } if (!block->is_terminated()) { Op::End end(*undefined_constant); bytecode.append(reinterpret_cast(&end), end.length()); } if (block->handler() || block->finalizer()) { unlinked_exception_handlers.last().end_offset = bytecode.size(); } } for (auto label_offset : label_offsets) { auto& label = *reinterpret_cast(bytecode.data() + label_offset); auto* block = generator.m_root_basic_blocks[label.basic_block_index()].ptr(); label.set_address(block_offsets.get(block).value()); } auto executable = vm.heap().allocate( move(bytecode), move(generator.m_identifier_table), move(generator.m_string_table), move(generator.m_regex_table), move(generator.m_constants), node.source_code(), generator.m_next_property_lookup_cache, generator.m_next_global_variable_cache, generator.m_next_register, is_strict_mode); Vector linked_exception_handlers; for (auto& unlinked_handler : unlinked_exception_handlers) { auto start_offset = unlinked_handler.start_offset; auto end_offset = unlinked_handler.end_offset; auto handler_offset = unlinked_handler.handler ? block_offsets.get(unlinked_handler.handler).value() : Optional {}; auto finalizer_offset = unlinked_handler.finalizer ? block_offsets.get(unlinked_handler.finalizer).value() : Optional {}; linked_exception_handlers.append({ start_offset, end_offset, handler_offset, finalizer_offset }); } quick_sort(linked_exception_handlers, [](auto const& a, auto const& b) { return a.start_offset < b.start_offset; }); executable->exception_handlers = move(linked_exception_handlers); executable->basic_block_start_offsets = move(basic_block_start_offsets); executable->source_map = move(source_map); executable->local_variable_names = move(local_variable_names); executable->local_index_base = number_of_registers + number_of_constants; executable->length_identifier = generator.m_length_identifier; generator.m_finished = true; return executable; } CodeGenerationErrorOr> Generator::generate_from_ast_node(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind) { Vector local_variable_names; if (is(node)) local_variable_names = static_cast(node).local_variables_names(); return compile(vm, node, enclosing_function_kind, {}, MustPropagateCompletion::Yes, move(local_variable_names)); } CodeGenerationErrorOr> Generator::generate_from_function(VM& vm, ECMAScriptFunctionObject const& function) { return compile(vm, function.ecmascript_code(), function.kind(), &function, MustPropagateCompletion::No, function.local_variables_names()); } void Generator::grow(size_t additional_size) { VERIFY(m_current_basic_block); m_current_basic_block->grow(additional_size); } ScopedOperand Generator::allocate_register() { if (!m_free_registers.is_empty()) { return ScopedOperand { *this, Operand { m_free_registers.take_last() } }; } VERIFY(m_next_register != NumericLimits::max()); return ScopedOperand { *this, Operand { Register { m_next_register++ } } }; } void Generator::free_register(Register reg) { m_free_registers.append(reg); } ScopedOperand Generator::local(u32 local_index) { return ScopedOperand { *this, Operand { Operand::Type::Local, static_cast(local_index) } }; } Generator::SourceLocationScope::SourceLocationScope(Generator& generator, ASTNode const& node) : m_generator(generator) , m_previous_node(m_generator.m_current_ast_node) { m_generator.m_current_ast_node = &node; } Generator::SourceLocationScope::~SourceLocationScope() { m_generator.m_current_ast_node = m_previous_node; } Generator::UnwindContext::UnwindContext(Generator& generator, Optional