From a4f70986a0f778efb4f2b05e43cf704155203384 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sun, 5 May 2024 22:06:55 +0200 Subject: [PATCH] LibJS: Emit bytecode for function declaration instantiation By doing that all instructions required for instantiation are emitted once in compilation and then reused for subsequent calls, instead of running generic instantiation process for each call. --- .../Libraries/LibJS/Bytecode/Generator.cpp | 196 +++++++- Userland/Libraries/LibJS/Bytecode/Generator.h | 8 +- .../Libraries/LibJS/Bytecode/Instruction.h | 5 + .../Libraries/LibJS/Bytecode/Interpreter.cpp | 121 ++++- .../Libraries/LibJS/Bytecode/Interpreter.h | 4 +- Userland/Libraries/LibJS/Bytecode/Op.h | 90 ++++ .../LibJS/Runtime/AbstractOperations.cpp | 2 +- .../Runtime/ECMAScriptFunctionObject.cpp | 444 +----------------- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 8 +- .../LibJS/Runtime/ExecutionContext.cpp | 1 + .../LibJS/Runtime/ExecutionContext.h | 2 + .../Libraries/LibJS/Runtime/FunctionObject.h | 2 + .../Libraries/LibJS/Runtime/ShadowRealm.cpp | 2 +- Userland/Libraries/LibJS/Runtime/VM.cpp | 2 +- Userland/Libraries/LibJS/SourceTextModule.cpp | 2 +- 15 files changed, 440 insertions(+), 449 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index efa078f9142..290829b2b64 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace JS::Bytecode { @@ -26,21 +27,190 @@ Generator::Generator(VM& vm) { } -CodeGenerationErrorOr> Generator::generate(VM& vm, ASTNode const& node, ReadonlySpan parameters, FunctionKind enclosing_function_kind) +CodeGenerationErrorOr Generator::emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function) { - Generator generator(vm); + if (function.m_has_parameter_expressions) { + emit(); + } - for (auto const& parameter : parameters) { - if (auto const* identifier = parameter.binding.get_pointer>(); - identifier && (*identifier)->is_local()) { - generator.set_local_initialized((*identifier)->local_variable_index()); + 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_generator_or_async_function()) { + 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())); @@ -72,8 +242,6 @@ CodeGenerationErrorOr> Generator::generate(VM& vm, ASTN 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(); size_t size_needed = 0; for (auto& block : generator.m_root_basic_blocks) { @@ -214,6 +382,16 @@ CodeGenerationErrorOr> Generator::generate(VM& vm, ASTN 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); diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index eb632ff4005..f1e54fa2a04 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -32,7 +32,11 @@ public: Function, Block, }; - static CodeGenerationErrorOr> generate(VM&, ASTNode const&, ReadonlySpan parameters, FunctionKind = FunctionKind::Normal); + + static CodeGenerationErrorOr> generate_from_ast_node(VM&, ASTNode const&, FunctionKind = FunctionKind::Normal); + static CodeGenerationErrorOr> generate_from_function(VM&, ECMAScriptFunctionObject const& function); + + CodeGenerationErrorOr emit_function_declaration_instantiation(ECMAScriptFunctionObject const& function); [[nodiscard]] ScopedOperand allocate_register(); [[nodiscard]] ScopedOperand local(u32 local_index); @@ -301,6 +305,8 @@ public: private: VM& m_vm; + static CodeGenerationErrorOr> emit_function_body_bytecode(VM&, ASTNode const&, FunctionKind, GCPtr); + enum class JumpType { Continue, Break, diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 7cfd3fee448..41281fb06ef 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -30,7 +30,10 @@ O(ContinuePendingUnwind) \ O(CopyObjectExcludingProperties) \ O(CreateLexicalEnvironment) \ + O(CreateVariableEnvironment) \ O(CreateVariable) \ + O(CreateRestParams) \ + O(CreateArguments) \ O(Decrement) \ O(DeleteById) \ O(DeleteByIdWithThis) \ @@ -43,6 +46,7 @@ O(EnterObjectEnvironment) \ O(EnterUnwindContext) \ O(Exp) \ + O(GetArgument) \ O(GetById) \ O(GetByIdWithThis) \ O(GetByValue) \ @@ -114,6 +118,7 @@ O(Return) \ O(RightShift) \ O(ScheduleJump) \ + O(SetArgument) \ O(SetVariable) \ O(SetLocal) \ O(StrictlyEquals) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index c06f7b37db7..22f2895c26e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -228,7 +228,7 @@ ThrowCompletionOr Interpreter::run(Script& script_record, JS::GCPtraccumulator(); auto& executable = current_executable(); @@ -387,6 +388,18 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) DISPATCH_NEXT(SetLocal); } + handle_GetArgument: { + auto const& instruction = *reinterpret_cast(&bytecode[program_counter]); + set(instruction.dst(), arguments[instruction.index()]); + DISPATCH_NEXT(GetArgument); + } + + handle_SetArgument: { + auto const& instruction = *reinterpret_cast(&bytecode[program_counter]); + arguments[instruction.index()] = get(instruction.src()); + DISPATCH_NEXT(SetArgument); + } + handle_Mov: { auto& instruction = *reinterpret_cast(&bytecode[program_counter]); set(instruction.dst(), get(instruction.src())); @@ -547,7 +560,10 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION(ConcatString); HANDLE_INSTRUCTION(CopyObjectExcludingProperties); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateLexicalEnvironment); + HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateVariableEnvironment); HANDLE_INSTRUCTION(CreateVariable); + HANDLE_INSTRUCTION(CreateRestParams); + HANDLE_INSTRUCTION(CreateArguments); HANDLE_INSTRUCTION(Decrement); HANDLE_INSTRUCTION(DeleteById); HANDLE_INSTRUCTION(DeleteByIdWithThis); @@ -687,6 +703,7 @@ Interpreter::ResultAndReturnRegister Interpreter::run_executable(Executable& exe if (running_execution_context.registers.size() < executable.number_of_registers) running_execution_context.registers.resize(executable.number_of_registers); + TemporaryChange restore_arguments { m_arguments, running_execution_context.arguments.span() }; TemporaryChange restore_registers { m_registers, running_execution_context.registers.span() }; TemporaryChange restore_locals { m_locals, running_execution_context.locals.span() }; TemporaryChange restore_constants { m_constants, executable.constants.span() }; @@ -776,9 +793,26 @@ void Interpreter::enter_object_environment(Object& object) running_execution_context.lexical_environment = new_object_environment(object, true, old_environment); } -ThrowCompletionOr> compile(VM& vm, ASTNode const& node, ReadonlySpan parameters, FunctionKind kind, DeprecatedFlyString const& name) +ThrowCompletionOr> compile(VM& vm, ASTNode const& node, FunctionKind kind, DeprecatedFlyString const& name) { - auto executable_result = Bytecode::Generator::generate(vm, node, parameters, kind); + auto executable_result = Bytecode::Generator::generate_from_ast_node(vm, node, kind); + if (executable_result.is_error()) + return vm.throw_completion(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string())); + + auto bytecode_executable = executable_result.release_value(); + bytecode_executable->name = name; + + if (Bytecode::g_dump_bytecode) + bytecode_executable->dump(); + + return bytecode_executable; +} + +ThrowCompletionOr> compile(VM& vm, ECMAScriptFunctionObject const& function) +{ + auto const& name = function.name(); + + auto executable_result = Bytecode::Generator::generate_from_function(vm, function); if (executable_result.is_error()) return vm.throw_completion(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string())); @@ -1230,6 +1264,15 @@ void CreateLexicalEnvironment::execute_impl(Bytecode::Interpreter& interpreter) running_execution_context.saved_lexical_environments.append(make_and_swap_envs(running_execution_context.lexical_environment)); } +ThrowCompletionOr CreateVariableEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& running_execution_context = interpreter.vm().running_execution_context(); + auto var_environment = new_declarative_environment(*running_execution_context.lexical_environment); + running_execution_context.variable_environment = var_environment; + running_execution_context.lexical_environment = var_environment; + return {}; +} + ThrowCompletionOr EnterObjectEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const { auto object = TRY(interpreter.get(m_object).to_object(interpreter.vm())); @@ -1258,6 +1301,41 @@ ThrowCompletionOr CreateVariable::execute_impl(Bytecode::Interpreter& inte return create_variable(interpreter.vm(), name, m_mode, m_is_global, m_is_immutable, m_is_strict); } +ThrowCompletionOr CreateRestParams::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto const& arguments = interpreter.vm().running_execution_context().arguments; + auto arguments_count = interpreter.vm().running_execution_context().passed_argument_count; + auto array = MUST(Array::create(interpreter.realm(), 0)); + for (size_t rest_index = m_rest_index; rest_index < arguments_count; ++rest_index) + array->indexed_properties().append(arguments[rest_index]); + interpreter.set(m_dst, array); + return {}; +} + +ThrowCompletionOr CreateArguments::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto const& function = interpreter.vm().running_execution_context().function; + auto const& arguments = interpreter.vm().running_execution_context().arguments; + auto const& environment = interpreter.vm().running_execution_context().lexical_environment; + + auto passed_arguments = ReadonlySpan { arguments.data(), interpreter.vm().running_execution_context().passed_argument_count }; + Object* arguments_object; + if (m_kind == Kind::Mapped) { + arguments_object = create_mapped_arguments_object(interpreter.vm(), *function, function->formal_parameters(), passed_arguments, *environment); + } else { + arguments_object = create_unmapped_arguments_object(interpreter.vm(), passed_arguments); + } + + if (m_is_immutable) { + MUST(environment->create_immutable_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), false)); + } else { + MUST(environment->create_mutable_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), false)); + } + MUST(environment->initialize_binding(interpreter.vm(), interpreter.vm().names.arguments.as_string(), arguments_object, Environment::InitializeBindingHint::Normal)); + + return {}; +} + ThrowCompletionOr SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -1277,6 +1355,18 @@ ThrowCompletionOr SetLocal::execute_impl(Bytecode::Interpreter&) const __builtin_unreachable(); } +ThrowCompletionOr SetArgument::execute_impl(Bytecode::Interpreter&) const +{ + // Handled in the interpreter loop. + __builtin_unreachable(); +} + +ThrowCompletionOr GetArgument::execute_impl(Bytecode::Interpreter&) const +{ + // Handled in the interpreter loop. + __builtin_unreachable(); +} + ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const { auto base_identifier = interpreter.current_executable().get_identifier(m_base_identifier); @@ -1959,12 +2049,27 @@ ByteString CreateLexicalEnvironment::to_byte_string_impl(Bytecode::Executable co return "CreateLexicalEnvironment"sv; } +ByteString CreateVariableEnvironment::to_byte_string_impl(Bytecode::Executable const&) const +{ + return "CreateVariableEnvironment"sv; +} + ByteString CreateVariable::to_byte_string_impl(Bytecode::Executable const& executable) const { auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable"; return ByteString::formatted("CreateVariable env:{} immutable:{} global:{} {}", mode_string, m_is_immutable, m_is_global, executable.identifier_table->get(m_identifier)); } +ByteString CreateRestParams::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("CreateRestParams {}, rest_index:{}", format_operand("dst"sv, m_dst, executable), m_rest_index); +} + +ByteString CreateArguments::to_byte_string_impl(Bytecode::Executable const&) const +{ + return ByteString::formatted("CreateArguments {} immutable:{}", m_kind == Kind::Mapped ? "mapped"sv : "unmapped"sv, m_is_immutable); +} + ByteString EnterObjectEnvironment::to_byte_string_impl(Executable const& executable) const { return ByteString::formatted("EnterObjectEnvironment {}", @@ -1988,6 +2093,16 @@ ByteString SetLocal::to_byte_string_impl(Bytecode::Executable const& executable) format_operand("src"sv, src(), executable)); } +ByteString GetArgument::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("GetArgument {}, {}", index(), format_operand("dst"sv, dst(), executable)); +} + +ByteString SetArgument::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("SetArgument {}, {}", index(), format_operand("src"sv, src(), executable)); +} + static StringView property_kind_to_string(PropertyKind kind) { switch (kind) { diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 38b26ad4f6b..ae32e8fa4d5 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -96,6 +96,7 @@ private: GCPtr m_global_object { nullptr }; GCPtr m_global_declarative_environment { nullptr }; Optional m_program_counter; + Span m_arguments; Span m_registers; Span m_locals; Span m_constants; @@ -103,6 +104,7 @@ private: extern bool g_dump_bytecode; -ThrowCompletionOr> compile(VM&, ASTNode const&, ReadonlySpan, JS::FunctionKind kind, DeprecatedFlyString const& name); +ThrowCompletionOr> compile(VM&, ASTNode const&, JS::FunctionKind kind, DeprecatedFlyString const& name); +ThrowCompletionOr> compile(VM&, ECMAScriptFunctionObject const&); } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index d74eb53b6a0..661a7b25d98 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -32,6 +32,45 @@ class FunctionExpression; namespace JS::Bytecode::Op { +class CreateRestParams final : public Instruction { +public: + CreateRestParams(Operand dst, u32 rest_index) + : Instruction(Type::CreateRestParams) + , m_dst(dst) + , m_rest_index(rest_index) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; + +private: + Operand m_dst; + u32 m_rest_index; +}; + +class CreateArguments final : public Instruction { +public: + enum class Kind { + Mapped, + Unmapped, + }; + + CreateArguments(Kind kind, bool is_immutable) + : Instruction(Type::CreateArguments) + , m_kind(kind) + , m_is_immutable(is_immutable) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; + +private: + Kind m_kind; + bool m_is_immutable { false }; +}; + class Mov final : public Instruction { public: Mov(Operand dst, Operand src) @@ -413,6 +452,17 @@ public: ByteString to_byte_string_impl(Bytecode::Executable const&) const; }; +class CreateVariableEnvironment final : public Instruction { +public: + explicit CreateVariableEnvironment() + : Instruction(Type::CreateVariableEnvironment) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; +}; + class EnterObjectEnvironment final : public Instruction { public: explicit EnterObjectEnvironment(Operand object) @@ -552,6 +602,46 @@ private: Operand m_src; }; +class SetArgument final : public Instruction { +public: + SetArgument(size_t index, Operand src) + : Instruction(Type::SetArgument) + , m_index(index) + , m_src(src) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + size_t index() const { return m_index; } + Operand src() const { return m_src; } + +private: + u32 m_index; + Operand m_src; +}; + +class GetArgument final : public Instruction { +public: + GetArgument(Operand dst, size_t index) + : Instruction(Type::GetArgument) + , m_index(index) + , m_dst(dst) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + u32 index() const { return m_index; } + Operand dst() const { return m_dst; } + +private: + u32 m_index; + Operand m_dst; +}; + class GetCalleeAndThisFromEnvironment final : public Instruction { public: explicit GetCalleeAndThisFromEnvironment(Operand callee, Operand this_value, IdentifierTableIndex identifier, u32 cache_index) diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 07a8000c854..070258ffcee 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -685,7 +685,7 @@ ThrowCompletionOr perform_eval(VM& vm, Value x, CallerMode strict_caller, // 29. If result.[[Type]] is normal, then // a. Set result to the result of evaluating body. - auto executable_result = Bytecode::Generator::generate(vm, program, {}); + auto executable_result = Bytecode::Generator::generate_from_ast_node(vm, program, {}); if (executable_result.is_error()) return vm.throw_completion(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string())); diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 2ff33159193..822450d0163 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -383,8 +383,14 @@ ThrowCompletionOr ECMAScriptFunctionObject::internal_call(Value this_argu callee_context->locals.resize(m_local_variables_names.size()); // Non-standard + callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size())); callee_context->arguments.append(arguments_list.data(), arguments_list.size()); callee_context->program_counter = vm.bytecode_interpreter().program_counter(); + callee_context->passed_argument_count = arguments_list.size(); + if (arguments_list.size() < m_formal_parameters.size()) { + for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i) + callee_context->arguments.append(js_undefined()); + } // 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined). // NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check. @@ -454,8 +460,14 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_const callee_context->locals.resize(m_local_variables_names.size()); // Non-standard + callee_context->arguments.ensure_capacity(max(arguments_list.size(), m_formal_parameters.size())); callee_context->arguments.append(arguments_list.data(), arguments_list.size()); callee_context->program_counter = vm.bytecode_interpreter().program_counter(); + callee_context->passed_argument_count = arguments_list.size(); + if (arguments_list.size() < m_formal_parameters.size()) { + for (size_t i = arguments_list.size(); i < m_formal_parameters.size(); ++i) + callee_context->arguments.append(js_undefined()); + } // 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget). // NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check. @@ -541,7 +553,6 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor) visitor.visit(m_name_string); visitor.visit(m_bytecode_executable); - visitor.visit(m_default_parameter_bytecode_executables); for (auto& field : m_fields) { if (auto* property_key_ptr = field.name.get_pointer(); property_key_ptr && property_key_ptr->is_symbol()) @@ -567,391 +578,6 @@ void ECMAScriptFunctionObject::make_method(Object& home_object) // 2. Return unused. } -// 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ), https://tc39.es/ecma262/#sec-functiondeclarationinstantiation -ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantiation() -{ - auto& vm = this->vm(); - auto& realm = *vm.current_realm(); - - // 1. Let calleeContext be the running execution context. - auto& callee_context = vm.running_execution_context(); - - // 2. Let code be func.[[ECMAScriptCode]]. - ScopeNode const* scope_body = nullptr; - if (is(*m_ecmascript_code)) - scope_body = static_cast(m_ecmascript_code.ptr()); - - // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. - // 3. Let strict be func.[[Strict]]. - // 4. Let formals be func.[[FormalParameters]]. - // 5. Let parameterNames be the BoundNames of formals. - // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. - - // 7. Let simpleParameterList be IsSimpleParameterList of formals. - bool const simple_parameter_list = has_simple_parameter_list(); - - // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. - // 8. Let hasParameterExpressions be ContainsExpression of formals. - // 9. Let varNames be the VarDeclaredNames of code. - // 10. Let varDeclarations be the VarScopedDeclarations of code. - // 11. Let lexicalNames be the LexicallyDeclaredNames of code. - // 12. Let functionNames be a new empty List. - // 13. Let functionsToInitialize be a new empty List. - // 14. For each element d of varDeclarations, in reverse List order, do - // 15. Let argumentsObjectNeeded be true. - // 16. If func.[[ThisMode]] is lexical, then - // 17. Else if parameterNames contains "arguments", then - // 18. Else if hasParameterExpressions is false, then - - GCPtr environment; - - // 19. If strict is true or hasParameterExpressions is false, then - if (m_strict || !m_has_parameter_expressions) { - // a. NOTE: Only a single Environment Record is needed for the parameters, since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. - // b. Let env be the LexicalEnvironment of calleeContext. - environment = callee_context.lexical_environment; - } - // 20. Else, - else { - // a. NOTE: A separate Environment Record is needed to ensure that bindings created by direct eval calls in the formal parameter list are outside the environment where parameters are declared. - - // b. Let calleeEnv be the LexicalEnvironment of calleeContext. - auto callee_env = callee_context.lexical_environment; - - // c. Let env be NewDeclarativeEnvironment(calleeEnv). - environment = new_declarative_environment(*callee_env); - - // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. - VERIFY(callee_context.variable_environment == callee_context.lexical_environment); - - // e. Set the LexicalEnvironment of calleeContext to env. - callee_context.lexical_environment = environment; - } - - // 21. For each String paramName of parameterNames, do - for (auto const& parameter_name : m_parameter_names) { - // OPTIMIZATION: Parameters that are locals don't need to be added to the environment. - if (parameter_name.value == ParameterIsLocal::Yes) - continue; - - // a. Let alreadyDeclared be ! env.HasBinding(paramName). - - // b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict functions that do not have parameter default values or rest parameters. - - // c. If alreadyDeclared is false, then - // NOTE: alreadyDeclared is always false because we use hash table for parameterNames - // i. Perform ! env.CreateMutableBinding(paramName, false). - MUST(environment->create_mutable_binding(vm, parameter_name.key, false)); - - // ii. If hasDuplicates is true, then - if (m_has_duplicates) { - // 1. Perform ! env.InitializeBinding(paramName, undefined). - MUST(environment->initialize_binding(vm, parameter_name.key, js_undefined(), Environment::InitializeBindingHint::Normal)); - } - } - - // 22. If argumentsObjectNeeded is true, then - if (m_arguments_object_needed) { - Object* arguments_object; - - // a. If strict is true or simpleParameterList is false, then - if (m_strict || !simple_parameter_list) { - // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). - arguments_object = create_unmapped_arguments_object(vm, vm.running_execution_context().arguments); - } - // b. Else, - else { - // i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters. - - // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). - arguments_object = create_mapped_arguments_object(vm, *this, formal_parameters(), vm.running_execution_context().arguments, *environment); - } - - // c. If strict is true, then - if (m_strict) { - // i. Perform ! env.CreateImmutableBinding("arguments", false). - MUST(environment->create_immutable_binding(vm, vm.names.arguments.as_string(), false)); - - // ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable. - } - // b. Else, - else { - // i. Perform ! env.CreateMutableBinding("arguments", false). - MUST(environment->create_mutable_binding(vm, vm.names.arguments.as_string(), false)); - } - - // c. Perform ! env.InitializeBinding("arguments", ao). - MUST(environment->initialize_binding(vm, vm.names.arguments.as_string(), arguments_object, Environment::InitializeBindingHint::Normal)); - - // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». - } - // 23. Else, - else { - // a. Let parameterBindings be parameterNames. - } - - // NOTE: We now treat parameterBindings as parameterNames. - - // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). - // 25. If hasDuplicates is true, then - // a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and undefined. - // 26. Else, - // a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and env. - // NOTE: The spec makes an iterator here to do IteratorBindingInitialization but we just do it manually - auto const& execution_context_arguments = vm.running_execution_context().arguments; - - size_t default_parameter_index = 0; - for (size_t i = 0; i < m_formal_parameters.size(); ++i) { - auto& parameter = m_formal_parameters[i]; - if (parameter.default_value) - ++default_parameter_index; - - TRY(parameter.binding.visit( - [&](auto const& param) -> ThrowCompletionOr { - Value argument_value; - if (parameter.is_rest) { - auto array = MUST(Array::create(realm, 0)); - for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index) - array->indexed_properties().append(execution_context_arguments[rest_index]); - argument_value = array; - } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) { - argument_value = execution_context_arguments[i]; - } else if (parameter.default_value) { - auto& running_execution_context = vm.running_execution_context(); - - // NOTE: Registers have to be saved and restored because executable created for default parameter uses - // running execution context. - // FIXME: This is a hack and instead instructions for default parameters should be a part of the function's bytecode. - auto saved_registers = running_execution_context.registers; - for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index) - saved_registers[register_index] = {}; - - auto result_and_return_register = vm.bytecode_interpreter().run_executable(*m_default_parameter_bytecode_executables[default_parameter_index - 1], {}); - - for (size_t register_index = 0; register_index < saved_registers.size(); ++register_index) - running_execution_context.registers[register_index] = saved_registers[register_index]; - - if (result_and_return_register.value.is_error()) - return result_and_return_register.value.release_error(); - argument_value = result_and_return_register.return_register_value; - } else { - argument_value = js_undefined(); - } - - Environment* used_environment = m_has_duplicates ? nullptr : environment; - - if constexpr (IsSame const&, decltype(param)>) { - if (param->is_local()) { - callee_context.locals[param->local_variable_index()] = argument_value; - return {}; - } - Reference reference = TRY(vm.resolve_binding(param->string(), used_environment)); - // Here the difference from hasDuplicates is important - if (m_has_duplicates) - return reference.put_value(vm, argument_value); - return reference.initialize_referenced_binding(vm, argument_value); - } - if constexpr (IsSame const&, decltype(param)>) { - // Here the difference from hasDuplicates is important - return vm.binding_initialization(param, argument_value, used_environment); - } - })); - } - - GCPtr var_environment; - - // 27. If hasParameterExpressions is false, then - if (!m_has_parameter_expressions) { - // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. - - // b. Let instantiatedVarNames be a copy of the List parameterBindings. - // NOTE: Done in implementation of step 27.c.i.1 below - - if (scope_body) { - // NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below, - // an exception should not result from `for_each_var_declared_name`. - - // c. For each element n of varNames, do - for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) { - auto const& id = variable_to_initialize.identifier; - // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. - // i. If instantiatedVarNames does not contain n, then - // 1. Append n to instantiatedVarNames. - if (id.is_local()) { - callee_context.locals[id.local_variable_index()] = js_undefined(); - } else { - // 2. Perform ! env.CreateMutableBinding(n, false). - // 3. Perform ! env.InitializeBinding(n, undefined). - MUST(environment->create_mutable_binding(vm, id.string(), false)); - MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal)); - } - } - } - - // d.Let varEnv be env - var_environment = environment; - } - // 28. Else, - else { - // a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. - - // b. Let varEnv be NewDeclarativeEnvironment(env). - var_environment = new_declarative_environment(*environment); - static_cast(var_environment.ptr())->ensure_capacity(m_var_environment_bindings_count); - - // c. Set the VariableEnvironment of calleeContext to varEnv. - callee_context.variable_environment = var_environment; - - // d. Let instantiatedVarNames be a new empty List. - // NOTE: Already done above. - - if (scope_body) { - // NOTE: Due to the use of MUST with `create_mutable_binding`, `get_binding_value` and `initialize_binding` below, - // an exception should not result from `for_each_var_declared_name`. - - // e. For each element n of varNames, do - for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) { - auto const& id = variable_to_initialize.identifier; - - // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. - // i. If instantiatedVarNames does not contain n, then - // 1. Append n to instantiatedVarNames. - - // 2. Perform ! varEnv.CreateMutableBinding(n, false). - // NOTE: We ignore locals because they are stored in ExecutionContext instead of environment. - if (!id.is_local()) - MUST(var_environment->create_mutable_binding(vm, id.string(), false)); - - Value initial_value; - - // 3. If parameterBindings does not contain n, or if functionNames contains n, then - if (!variable_to_initialize.parameter_binding || variable_to_initialize.function_name) { - // a. Let initialValue be undefined. - initial_value = js_undefined(); - } - // 4. Else, - else { - // a. Let initialValue be ! env.GetBindingValue(n, false). - if (id.is_local()) { - initial_value = callee_context.locals[id.local_variable_index()]; - } else { - initial_value = MUST(environment->get_binding_value(vm, id.string(), false)); - } - } - - // 5. Perform ! varEnv.InitializeBinding(n, initialValue). - if (id.is_local()) { - // NOTE: Local variables are supported only in bytecode interpreter - callee_context.locals[id.local_variable_index()] = initial_value; - } else { - MUST(var_environment->initialize_binding(vm, id.string(), initial_value, Environment::InitializeBindingHint::Normal)); - } - - // 6. NOTE: A var with the same name as a formal parameter initially has the same value as the corresponding initialized parameter. - } - } - } - - // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. - // B.3.2.1 Changes to FunctionDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation - if (!m_strict && scope_body) { - // NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below, - // an exception should not result from `for_each_function_hoistable_with_annexB_extension`. - for (auto const& function_name : m_function_names_to_initialize_binding) { - MUST(var_environment->create_mutable_binding(vm, function_name, false)); - MUST(var_environment->initialize_binding(vm, function_name, js_undefined(), Environment::InitializeBindingHint::Normal)); - } - } - - GCPtr lex_environment; - - // 30. If strict is false, then - if (!m_strict) { - // Optimization: We avoid creating empty top-level declarative environments in non-strict mode, if both of these conditions are true: - // 1. there is no direct call to eval() within this function - // 2. there are no lexical declarations that would go into the environment - bool can_elide_declarative_environment = !m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations()); - if (can_elide_declarative_environment) { - lex_environment = var_environment; - } else { - // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). - // b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval - // can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level - // lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places - // all declarations into a new Environment Record. - lex_environment = new_declarative_environment(*var_environment); - static_cast(lex_environment.ptr())->ensure_capacity(m_lex_environment_bindings_count); - } - } - // 31. Else, - else { - // a. let lexEnv be varEnv. - lex_environment = var_environment; - } - - // 32. Set the LexicalEnvironment of calleeContext to lexEnv. - callee_context.lexical_environment = lex_environment; - - if (!scope_body) - return {}; - - // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. - // 34. For each element d of lexDeclarations, do - // NOTE: Due to the use of MUST in the callback, an exception should not result from `for_each_lexically_scoped_declaration`. - if (scope_body->has_lexical_declarations()) { - MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) { - // NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below, - // an exception should not result from `for_each_bound_name`. - - // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. - - // b. For each element dn of the BoundNames of d, do - MUST(declaration.for_each_bound_identifier([&](auto const& id) { - if (id.is_local()) { - // NOTE: Local variables are supported only in bytecode interpreter - return; - } - - // i. If IsConstantDeclaration of d is true, then - if (declaration.is_constant_declaration()) { - // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). - MUST(lex_environment->create_immutable_binding(vm, id.string(), true)); - } - // ii. Else, - else { - // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). - MUST(lex_environment->create_mutable_binding(vm, id.string(), false)); - } - })); - })); - } - - // 35. Let privateEnv be the PrivateEnvironment of calleeContext. - auto private_environment = callee_context.private_environment; - - // 36. For each Parse Node f of functionsToInitialize, do - for (auto& declaration : m_functions_to_initialize) { - // a. Let fn be the sole element of the BoundNames of f. - // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. - auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), lex_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.uses_this(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval()); - - // c. Perform ! varEnv.SetMutableBinding(fn, fo, false). - if (declaration.name_identifier()->is_local()) { - callee_context.locals[declaration.name_identifier()->local_variable_index()] = function; - } else { - MUST(var_environment->set_mutable_binding(vm, declaration.name(), function, false)); - } - } - - if (is(*lex_environment)) - static_cast(lex_environment.ptr())->shrink_to_fit(); - if (lex_environment != var_environment && is(*var_environment)) - static_cast(var_environment.ptr())->shrink_to_fit(); - - // 37. Return unused. - return {}; -} - // 10.2.1.1 PrepareForOrdinaryCall ( F, newTarget ), https://tc39.es/ecma262/#sec-prepareforordinarycall ThrowCompletionOr ECMAScriptFunctionObject::prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target) { @@ -1120,7 +746,7 @@ void async_block_start(VM& vm, T const& async_body, PromiseCapability const& pro if constexpr (!IsCallableWithArguments) { // a. Let result be the result of evaluating asyncBody. // FIXME: Cache this executable somewhere. - auto maybe_executable = Bytecode::compile(vm, async_body, {}, FunctionKind::Async, "AsyncBlockStart"sv); + auto maybe_executable = Bytecode::compile(vm, async_body, FunctionKind::Async, "AsyncBlockStart"sv); if (maybe_executable.is_error()) result = maybe_executable.release_error(); else @@ -1204,53 +830,17 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() auto& vm = this->vm(); auto& realm = *vm.current_realm(); - // NOTE: There's a subtle ordering issue here: - // - We have to compile the default parameter values before instantiating the function. - // - We have to instantiate the function before compiling the function body. - // This is why FunctionDeclarationInstantiation is invoked in the middle. - // The issue is that FunctionDeclarationInstantiation may mark certain functions as hoisted - // per Annex B. This affects code generation for FunctionDeclaration nodes. - if (!m_bytecode_executable) { - size_t default_parameter_index = 0; - for (auto& parameter : m_formal_parameters) { - if (!parameter.default_value) - continue; - if (parameter.bytecode_executable.is_null()) { - auto executable = TRY(Bytecode::compile(vm, *parameter.default_value, {}, FunctionKind::Normal, ByteString::formatted("default parameter #{} for {}", default_parameter_index++, m_name))); - const_cast(parameter).bytecode_executable = executable; - m_default_parameter_bytecode_executables.append(move(executable)); + if (!m_ecmascript_code->bytecode_executable()) { + if (is_module_wrapper()) { + const_cast(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *m_ecmascript_code, m_kind, m_name))); } else { - m_default_parameter_bytecode_executables.append(*parameter.bytecode_executable); + const_cast(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *this))); } } - } - - auto declaration_result = [&]() -> ThrowCompletionOr { - if (is_module_wrapper()) - return {}; - return function_declaration_instantiation(); - }(); - - if (m_kind == FunctionKind::Normal || m_kind == FunctionKind::Generator || m_kind == FunctionKind::AsyncGenerator) { - if (declaration_result.is_error()) - return declaration_result.release_error(); - } - - if (!m_bytecode_executable) { - if (!m_ecmascript_code->bytecode_executable()) - const_cast(*m_ecmascript_code).set_bytecode_executable(TRY(Bytecode::compile(vm, *m_ecmascript_code, m_formal_parameters, m_kind, m_name))); m_bytecode_executable = m_ecmascript_code->bytecode_executable(); } - if (m_kind == FunctionKind::Async) { - if (declaration_result.is_throw_completion()) { - auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); - MUST(call(vm, *promise_capability->reject(), js_undefined(), *declaration_result.throw_completion().value())); - return Completion { Completion::Type::Return, promise_capability->promise() }; - } - } - auto result_and_frame = vm.bytecode_interpreter().run_executable(*m_bytecode_executable, {}); if (result_and_frame.value.is_error()) diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 425c126368f..360e2f4deaa 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include #include #include @@ -53,7 +54,7 @@ public: void set_is_module_wrapper(bool b) { m_is_module_wrapper = b; } Statement const& ecmascript_code() const { return m_ecmascript_code; } - Vector const& formal_parameters() const { return m_formal_parameters; } + Vector const& formal_parameters() const override { return m_formal_parameters; } virtual DeprecatedFlyString const& name() const override { return m_name; } void set_name(DeprecatedFlyString const& name); @@ -98,6 +99,8 @@ public: Variant const& class_field_initializer_name() const { return m_class_field_initializer_name; } + friend class Bytecode::Generator; + protected: virtual bool is_strict_mode() const final { return m_strict; } @@ -112,13 +115,10 @@ private: ThrowCompletionOr prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target); void ordinary_call_bind_this(ExecutionContext&, Value this_argument); - ThrowCompletionOr function_declaration_instantiation(); - DeprecatedFlyString m_name; GCPtr m_name_string; GCPtr m_bytecode_executable; - Vector> m_default_parameter_bytecode_executables; i32 m_function_length { 0 }; Vector m_local_variables_names; diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp index 52ed6025763..7208d0d5a31 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.cpp @@ -42,6 +42,7 @@ NonnullOwnPtr ExecutionContext::copy() const copy->is_strict_mode = is_strict_mode; copy->executable = executable; copy->arguments = arguments; + copy->passed_argument_count = passed_argument_count; copy->locals = locals; copy->registers = registers; copy->unwind_contexts = unwind_contexts; diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h index f5c9780bf1c..55bfb3e51b1 100644 --- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h @@ -73,6 +73,8 @@ public: return locals[index]; } + u32 passed_argument_count { 0 }; + Vector arguments; Vector locals; Vector registers; diff --git a/Userland/Libraries/LibJS/Runtime/FunctionObject.h b/Userland/Libraries/LibJS/Runtime/FunctionObject.h index 238be5c5de6..a5be65df29d 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/FunctionObject.h @@ -40,6 +40,8 @@ public: virtual Vector const& local_variables_names() const { VERIFY_NOT_REACHED(); } + virtual Vector const& formal_parameters() const { VERIFY_NOT_REACHED(); } + protected: explicit FunctionObject(Realm&, Object* prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No); explicit FunctionObject(Object& prototype, MayInterfereWithIndexedPropertyAccess = MayInterfereWithIndexedPropertyAccess::No); diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index 142b11a0a34..97f153cb0ed 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -174,7 +174,7 @@ ThrowCompletionOr perform_shadow_realm_eval(VM& vm, StringView source_tex // 17. If result.[[Type]] is normal, then if (!eval_result.is_throw_completion()) { // a. Set result to the result of evaluating body. - auto maybe_executable = Bytecode::compile(vm, program, {}, FunctionKind::Normal, "ShadowRealmEval"sv); + auto maybe_executable = Bytecode::compile(vm, program, FunctionKind::Normal, "ShadowRealmEval"sv); if (maybe_executable.is_error()) result = maybe_executable.release_error(); else { diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 9ad9d49211c..7baa92ad0d7 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -291,7 +291,7 @@ ThrowCompletionOr VM::execute_ast_node(ASTNode const& node) { // FIXME: This function should be gone once we will emit bytecode for everything before executing instructions. - auto executable = TRY(Bytecode::compile(*this, node, {}, FunctionKind::Normal, ""sv)); + auto executable = TRY(Bytecode::compile(*this, node, FunctionKind::Normal, ""sv)); auto& running_execution_context = this->running_execution_context(); // Registers have to be saved and restored because executable for compiled ASTNode does not have its own execution context diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index 90843971c49..65da8ffd6ce 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -711,7 +711,7 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtr