/* * 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) : 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())) { } 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()), next_environment_variable_cache(), Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Lexical); } } } if (function.m_arguments_object_needed) { if (function.m_strict || !function.has_simple_parameter_list()) { emit(Op::CreateArguments::Kind::Unmapped, function.m_strict); } else { emit(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 init_mode = function.m_has_duplicates ? Op::SetVariable::InitializationMode::Set : Op::SetVariable::InitializationMode::Initialize; auto argument_reg = allocate_register(); emit(argument_reg.operand(), param_index); emit(id, argument_reg.operand(), next_environment_variable_cache(), init_mode, Op::EnvironmentMode::Lexical); } } 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::SetVariable::InitializationMode::Set : Bytecode::Op::SetVariable::InitializationMode::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()), next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var); } } } } else { emit(); 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()), next_environment_variable_cache()); } } 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, next_environment_variable_cache(), Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var); } } } } 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()), next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Op::EnvironmentMode::Var); } } 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(); } } 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, next_environment_variable_cache(), Op::SetVariable::InitializationMode::Set, Op::EnvironmentMode::Var); } } return {}; } CodeGenerationErrorOr> Generator::emit_function_body_bytecode(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GCPtr function) { Generator generator(vm); 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(nullptr, 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; 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); // OPTIMIZATION: Don't emit jumps that just jump to the next block. if (instruction.type() == Instruction::Type::Jump) { auto& jump = static_cast(instruction); 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 `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(generator.add_constant(js_undefined())); 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_without_realm( 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_environment_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); generator.m_finished = true; return executable; } CodeGenerationErrorOr> Generator::generate_from_ast_node(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind) { return emit_function_body_bytecode(vm, node, enclosing_function_kind, {}); } CodeGenerationErrorOr> Generator::generate_from_function(VM& vm, ECMAScriptFunctionObject const& function) { return emit_function_body_bytecode(vm, function.ecmascript_code(), function.kind(), &function); } 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