From 3e680d514d9b009d0443386a4571c116aa6b9694 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Tue, 21 May 2024 16:51:21 +0100 Subject: [PATCH] LibJS: Get `this` from execution context for non-arrow functions Allows to skip function environment allocation for non-arrow functions if the only reason it is needed is to hold `this` binding. The parser is changed to do following: - If a function is an arrow function and uses `this` then all functions in a scope chain are marked to allocate function environment for `this` binding. - If a function uses `new.target` then all functions in a scope chain are marked to allocate function environment. `ordinary_call_bind_this()` is changed to put `this` value in execution context when function environment allocation is skipped. 35% improvement in Octane/typescript.js 50% improvement in Octane/deltablue.js 19% improvement in Octane/raytrace.js --- Userland/Libraries/LibJS/AST.cpp | 18 ++++--- Userland/Libraries/LibJS/AST.h | 18 +++---- .../LibJS/Bytecode/CommonImplementations.h | 3 +- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 9 +++- Userland/Libraries/LibJS/Parser.cpp | 54 +++++++++++++------ Userland/Libraries/LibJS/Parser.h | 2 +- .../LibJS/Runtime/AbstractOperations.cpp | 3 +- .../Runtime/ECMAScriptFunctionObject.cpp | 22 ++++---- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 8 +-- .../LibJS/Runtime/FunctionConstructor.cpp | 6 +-- Userland/Libraries/LibJS/SourceTextModule.cpp | 5 +- Userland/Libraries/LibWeb/DOM/EventTarget.cpp | 3 +- .../LibWeb/WebDriver/ExecuteScript.cpp | 3 +- 13 files changed, 95 insertions(+), 59 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 223a33a749e4d1..9430bbc0bf72b8 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -106,7 +106,8 @@ Value FunctionExpression::instantiate_ordinary_function_expression(VM& vm, Depre auto private_environment = vm.running_execution_context().private_environment; - auto closure = ECMAScriptFunctionObject::create(realm, used_name, source_text(), body(), parameters(), function_length(), local_variables_names(), environment, private_environment, kind(), is_strict_mode(), uses_this(), might_need_arguments_object(), contains_direct_call_to_eval(), is_arrow_function()); + auto closure = ECMAScriptFunctionObject::create(realm, used_name, source_text(), body(), parameters(), function_length(), local_variables_names(), environment, private_environment, kind(), is_strict_mode(), + uses_this_from_environment(), might_need_arguments_object(), contains_direct_call_to_eval(), is_arrow_function()); // FIXME: 6. Perform SetFunctionName(closure, name). // FIXME: 7. Perform MakeConstructor(closure). @@ -151,7 +152,8 @@ ThrowCompletionOr ClassMethod::class_element_evaluatio { auto property_key_or_private_name = TRY(class_key_to_property_name(vm, *m_key, property_key)); - auto& method_function = *ECMAScriptFunctionObject::create(*vm.current_realm(), m_function->name(), m_function->source_text(), m_function->body(), m_function->parameters(), m_function->function_length(), m_function->local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function->kind(), m_function->is_strict_mode(), m_function->uses_this(), m_function->might_need_arguments_object(), m_function->contains_direct_call_to_eval(), m_function->is_arrow_function()); + auto& method_function = *ECMAScriptFunctionObject::create(*vm.current_realm(), m_function->name(), m_function->source_text(), m_function->body(), m_function->parameters(), m_function->function_length(), m_function->local_variables_names(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function->kind(), m_function->is_strict_mode(), + m_function->uses_this_from_environment(), m_function->might_need_arguments_object(), m_function->contains_direct_call_to_eval(), m_function->is_arrow_function()); auto method_value = Value(&method_function); method_function.make_method(target); @@ -238,7 +240,7 @@ ThrowCompletionOr ClassField::class_element_evaluation // FIXME: A potential optimization is not creating the functions here since these are never directly accessible. auto function_code = create_ast_node(m_initializer->source_range(), copy_initializer.release_nonnull(), name); - initializer = make_handle(*ECMAScriptFunctionObject::create(realm, "field", ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, UsesThis::Yes, false, m_contains_direct_call_to_eval, false, property_key_or_private_name)); + initializer = make_handle(*ECMAScriptFunctionObject::create(realm, "field", ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, UsesThisFromEnvironment::Yes, false, m_contains_direct_call_to_eval, false, property_key_or_private_name)); initializer->make_method(target); } @@ -282,7 +284,7 @@ ThrowCompletionOr StaticInitializer::class_element_eva // 4. Let formalParameters be an instance of the production FormalParameters : [empty] . // 5. Let bodyFunction be OrdinaryFunctionCreate(%Function.prototype%, sourceText, formalParameters, ClassStaticBlockBody, non-lexical-this, lex, privateEnv). // Note: The function bodyFunction is never directly accessible to ECMAScript code. - auto body_function = ECMAScriptFunctionObject::create(realm, ByteString::empty(), ByteString::empty(), *m_function_body, {}, 0, m_function_body->local_variables_names(), lexical_environment, private_environment, FunctionKind::Normal, true, UsesThis::Yes, false, m_contains_direct_call_to_eval, false); + auto body_function = ECMAScriptFunctionObject::create(realm, ByteString::empty(), ByteString::empty(), *m_function_body, {}, 0, m_function_body->local_variables_names(), lexical_environment, private_environment, FunctionKind::Normal, true, UsesThisFromEnvironment::Yes, false, m_contains_direct_call_to_eval, false); // 6. Perform MakeMethod(bodyFunction, homeObject). body_function->make_method(home_object); @@ -341,7 +343,7 @@ ThrowCompletionOr ClassExpression::create_class_const vm.running_execution_context().private_environment, constructor.kind(), constructor.is_strict_mode(), - UsesThis::Yes, + UsesThisFromEnvironment::Yes, constructor.might_need_arguments_object(), constructor.contains_direct_call_to_eval(), constructor.is_arrow_function()); @@ -1620,7 +1622,8 @@ void ScopeNode::block_declaration_instantiation(VM& vm, Environment* environment auto& function_declaration = static_cast(declaration); // ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. - auto function = ECMAScriptFunctionObject::create(realm, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.uses_this(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval()); + auto function = ECMAScriptFunctionObject::create(realm, function_declaration.name(), function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), + function_declaration.uses_this_from_environment(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval()); // iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6. if (function_declaration.name_identifier()->is_local()) { @@ -1827,7 +1830,8 @@ ThrowCompletionOr Program::global_declaration_instantiation(VM& vm, Global for (auto& declaration : functions_to_initialize.in_reverse()) { // a. Let fn be the sole element of the BoundNames of f. // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. - auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.uses_this(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval()); + auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), + declaration.uses_this_from_environment(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval()); // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). TRY(global_environment.create_global_function_binding(declaration.name(), function, false)); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index e91ec30aef6c27..aba0d82d4b834c 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -693,7 +693,7 @@ struct FunctionParameter { Handle bytecode_executable {}; }; -enum class UsesThis { +enum class UsesThisFromEnvironment { Yes, No }; @@ -712,7 +712,7 @@ class FunctionNode { bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; } bool is_arrow_function() const { return m_is_arrow_function; } FunctionKind kind() const { return m_kind; } - UsesThis uses_this() const { return m_uses_this; } + UsesThisFromEnvironment uses_this_from_environment() const { return m_uses_this_from_environment; } virtual bool has_name() const = 0; virtual Value instantiate_ordinary_function_expression(VM&, DeprecatedFlyString given_name) const = 0; @@ -720,7 +720,7 @@ class FunctionNode { virtual ~FunctionNode() {}; protected: - FunctionNode(RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Vector local_variables_names, UsesThis uses_this) + FunctionNode(RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Vector local_variables_names, UsesThisFromEnvironment uses_this_from_environment) : m_name(move(name)) , m_source_text(move(source_text)) , m_body(move(body)) @@ -731,7 +731,7 @@ class FunctionNode { , m_might_need_arguments_object(might_need_arguments_object) , m_contains_direct_call_to_eval(contains_direct_call_to_eval) , m_is_arrow_function(is_arrow_function) - , m_uses_this(uses_this) + , m_uses_this_from_environment(uses_this_from_environment) , m_local_variables_names(move(local_variables_names)) { if (m_is_arrow_function) @@ -752,7 +752,7 @@ class FunctionNode { bool m_might_need_arguments_object : 1 { false }; bool m_contains_direct_call_to_eval : 1 { false }; bool m_is_arrow_function : 1 { false }; - UsesThis m_uses_this : 1 { UsesThis::No }; + UsesThisFromEnvironment m_uses_this_from_environment : 1 { UsesThisFromEnvironment::No }; Vector m_local_variables_names; }; @@ -763,9 +763,9 @@ class FunctionDeclaration final public: static bool must_have_name() { return true; } - FunctionDeclaration(SourceRange source_range, RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector local_variables_names, UsesThis uses_this) + FunctionDeclaration(SourceRange source_range, RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector local_variables_names, UsesThisFromEnvironment uses_this_from_environment) : Declaration(move(source_range)) - , FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false, move(local_variables_names), uses_this) + , FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, false, move(local_variables_names), uses_this_from_environment) { } @@ -793,9 +793,9 @@ class FunctionExpression final public: static bool must_have_name() { return false; } - FunctionExpression(SourceRange source_range, RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector local_variables_names, UsesThis uses_this = UsesThis::No, bool is_arrow_function = false) + FunctionExpression(SourceRange source_range, RefPtr name, ByteString source_text, NonnullRefPtr body, Vector parameters, i32 function_length, FunctionKind kind, bool is_strict_mode, bool might_need_arguments_object, bool contains_direct_call_to_eval, Vector local_variables_names, UsesThisFromEnvironment uses_this_from_environment = UsesThisFromEnvironment::No, bool is_arrow_function = false) : Expression(move(source_range)) - , FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(local_variables_names), uses_this) + , FunctionNode(move(name), move(source_text), move(body), move(parameters), function_length, kind, is_strict_mode, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(local_variables_names), uses_this_from_environment) { } diff --git a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h index b4eb4d7dda6b82..1f20d60f77fc40 100644 --- a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h +++ b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h @@ -417,7 +417,8 @@ inline Value new_function(VM& vm, FunctionNode const& function_node, Optional ResolveThisBinding::execute_impl(Bytecode::Interpreter& if (cached_this_value.is_empty()) { // OPTIMIZATION: Because the value of 'this' cannot be reassigned during a function execution, it's // resolved once and then saved for subsequent use. - auto& vm = interpreter.vm(); - cached_this_value = TRY(vm.resolve_this_binding()); + auto& running_execution_context = interpreter.running_execution_context(); + if (auto function = running_execution_context.function; function && is(*function) && !static_cast(*function).allocates_function_environment()) { + cached_this_value = running_execution_context.this_value; + } else { + auto& vm = interpreter.vm(); + cached_this_value = TRY(vm.resolve_this_binding()); + } } interpreter.set(dst(), cached_this_value); return {}; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index b78656b15c05e3..4868d8c62de0d2 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -433,16 +433,32 @@ class ScopePusher { } } - bool uses_this() + bool uses_this_from_environment() const { - return m_uses_this; + return m_uses_this_from_environment; } void set_uses_this() { - for (auto scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) { + auto const* closest_function_scope = last_function_scope(); + if (!closest_function_scope || !closest_function_scope->m_is_arrow_function) + return; + + for (auto* scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) { + if (scope_ptr->m_uses_this_from_environment) + break; + if (scope_ptr->m_type == ScopeType::Function) + scope_ptr->m_uses_this_from_environment = true; + } + } + + void set_uses_new_target() + { + for (auto* scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) { + if (scope_ptr->m_uses_this_from_environment) + break; if (scope_ptr->m_type == ScopeType::Function) - scope_ptr->m_uses_this = true; + scope_ptr->m_uses_this_from_environment = true; } } @@ -491,7 +507,11 @@ class ScopePusher { bool m_contains_direct_call_to_eval { false }; bool m_contains_await_expression { false }; bool m_screwed_by_eval_in_scope_chain { false }; - bool m_uses_this { false }; + + // Function uses this binding from function environment if: + // 1. It's an arrow function or establish parent scope for an arrow function + // 2. Uses new.target + bool m_uses_this_from_environment { false }; bool m_is_arrow_function { false }; }; @@ -996,7 +1016,7 @@ RefPtr Parser::try_parse_arrow_function_expression(boo Vector parameters; i32 function_length = -1; bool contains_direct_call_to_eval = false; - bool uses_this = false; + bool uses_this_from_environment = false; auto function_body_result = [&]() -> RefPtr { ScopePusher function_scope = ScopePusher::function_scope(*this); function_scope.set_is_arrow_function(); @@ -1051,7 +1071,7 @@ RefPtr Parser::try_parse_arrow_function_expression(boo if (match(TokenType::CurlyOpen)) { // Parse a function body with statements consume(TokenType::CurlyOpen); - auto body = parse_function_body(parameters, function_kind, contains_direct_call_to_eval, uses_this); + auto body = parse_function_body(parameters, function_kind, contains_direct_call_to_eval, uses_this_from_environment); consume(TokenType::CurlyClose); return body; } @@ -1071,7 +1091,7 @@ RefPtr Parser::try_parse_arrow_function_expression(boo if (m_state.strict_mode) const_cast(*return_block).set_strict_mode(); contains_direct_call_to_eval = m_state.current_scope_pusher->contains_direct_call_to_eval(); - uses_this = m_state.current_scope_pusher->uses_this(); + uses_this_from_environment = m_state.current_scope_pusher->uses_this_from_environment(); return return_block; } // Invalid arrow function body @@ -1103,7 +1123,7 @@ RefPtr Parser::try_parse_arrow_function_expression(boo return create_ast_node( { m_source_code, rule_start.position(), position() }, nullptr, move(source_text), move(body), move(parameters), function_length, function_kind, body->in_strict_mode(), - /* might_need_arguments_object */ false, contains_direct_call_to_eval, move(local_variables_names), uses_this ? UsesThis::Yes : UsesThis::No, /* is_arrow_function */ true); + /* might_need_arguments_object */ false, contains_direct_call_to_eval, move(local_variables_names), uses_this_from_environment ? UsesThisFromEnvironment::Yes : UsesThisFromEnvironment::No, /* is_arrow_function */ true); } RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction allow_function) @@ -1217,7 +1237,7 @@ RefPtr Parser::try_parse_new_target_expression() discard_saved_state(); if (m_state.current_scope_pusher) - m_state.current_scope_pusher->set_uses_this(); + m_state.current_scope_pusher->set_uses_new_target(); return create_ast_node({ m_source_code, rule_start.position(), position() }, MetaProperty::Type::NewTarget); } @@ -1677,7 +1697,7 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() if (!m_state.allow_super_property_lookup) syntax_error("'super' keyword unexpected here"); if (m_state.current_scope_pusher) - m_state.current_scope_pusher->set_uses_this(); + m_state.current_scope_pusher->set_uses_new_target(); return { create_ast_node({ m_source_code, rule_start.position(), position() }) }; case TokenType::EscapedKeyword: if (match_invalid_escaped_keyword()) @@ -2822,7 +2842,7 @@ NonnullRefPtr Parser::parse_function_body(Vectortype() == ScopePusher::ScopeType::Function); contains_direct_call_to_eval = m_state.current_scope_pusher->contains_direct_call_to_eval(); - uses_this = m_state.current_scope_pusher->uses_this(); + uses_this = m_state.current_scope_pusher->uses_this_from_environment(); return function_body; } @@ -2908,7 +2928,7 @@ NonnullRefPtr Parser::parse_function_node(u16 parse_options, O i32 function_length = -1; Vector parameters; bool contains_direct_call_to_eval = false; - bool uses_this = false; + bool uses_this_from_environment = false; auto body = [&] { ScopePusher function_scope = ScopePusher::function_scope(*this, name); @@ -2928,7 +2948,7 @@ NonnullRefPtr Parser::parse_function_node(u16 parse_options, O consume(TokenType::CurlyOpen); - auto body = parse_function_body(parameters, function_kind, contains_direct_call_to_eval, uses_this); + auto body = parse_function_body(parameters, function_kind, contains_direct_call_to_eval, uses_this_from_environment); return body; }(); @@ -2949,7 +2969,7 @@ NonnullRefPtr Parser::parse_function_node(u16 parse_options, O function_kind, has_strict_directive, m_state.function_might_need_arguments_object, contains_direct_call_to_eval, move(local_variables_names), - uses_this ? UsesThis::Yes : UsesThis::No); + uses_this_from_environment ? UsesThisFromEnvironment::Yes : UsesThisFromEnvironment::No); } Vector Parser::parse_formal_parameters(int& function_length, u16 parse_options) @@ -5133,7 +5153,7 @@ NonnullRefPtr Parser::create_identifier_and_register_in_curren return id; } -Parser Parser::parse_function_body_from_string(ByteString const& body_string, u16 parse_options, Vector const& parameters, FunctionKind kind, bool& contains_direct_call_to_eval, bool& uses_this) +Parser Parser::parse_function_body_from_string(ByteString const& body_string, u16 parse_options, Vector const& parameters, FunctionKind kind, bool& contains_direct_call_to_eval, bool& uses_this_from_environment) { RefPtr function_body; @@ -5146,7 +5166,7 @@ Parser Parser::parse_function_body_from_string(ByteString const& body_string, u1 body_parser.m_state.await_expression_is_valid = true; if ((parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0) body_parser.m_state.in_generator_function_context = true; - function_body = body_parser.parse_function_body(parameters, kind, contains_direct_call_to_eval, uses_this); + function_body = body_parser.parse_function_body(parameters, kind, contains_direct_call_to_eval, uses_this_from_environment); } return body_parser; diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index dcc537250bbdeb..fafd3b92e7d3f6 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -211,7 +211,7 @@ class Parser { // Needs to mess with m_state, and we're not going to expose a non-const getter for that :^) friend ThrowCompletionOr FunctionConstructor::create_dynamic_function(VM&, FunctionObject&, FunctionObject*, FunctionKind, MarkedVector const&); - static Parser parse_function_body_from_string(ByteString const& body_string, u16 parse_options, Vector const& parameters, FunctionKind kind, bool& contains_direct_call_to_eval, bool& uses_this); + static Parser parse_function_body_from_string(ByteString const& body_string, u16 parse_options, Vector const& parameters, FunctionKind kind, bool& contains_direct_call_to_eval, bool& uses_this_from_environment); private: friend class ScopePusher; diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 070258ffceeaf7..1135393dc0484b 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -972,7 +972,8 @@ ThrowCompletionOr eval_declaration_instantiation(VM& vm, Program const& pr for (auto& declaration : functions_to_initialize.in_reverse()) { // 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(), lexical_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.uses_this(), declaration.might_need_arguments_object()); + auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), lexical_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), + declaration.uses_this_from_environment(), declaration.might_need_arguments_object()); // c. If varEnv is a global Environment Record, then if (global_var_environment) { diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 5c5d56905e95c9..eeef533ba8a703 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -33,7 +33,7 @@ namespace JS { JS_DEFINE_ALLOCATOR(ECMAScriptFunctionObject); -NonnullGCPtr ECMAScriptFunctionObject::create(Realm& realm, DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, UsesThis uses_this, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) +NonnullGCPtr ECMAScriptFunctionObject::create(Realm& realm, DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, UsesThisFromEnvironment uses_this_from_environment, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) { Object* prototype = nullptr; switch (kind) { @@ -50,15 +50,15 @@ NonnullGCPtr ECMAScriptFunctionObject::create(Realm& r prototype = realm.intrinsics().async_generator_function_prototype(); break; } - return realm.heap().allocate(realm, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, *prototype, kind, is_strict, uses_this, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name)); + return realm.heap().allocate(realm, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, *prototype, kind, is_strict, uses_this_from_environment, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name)); } -NonnullGCPtr ECMAScriptFunctionObject::create(Realm& realm, DeprecatedFlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, UsesThis uses_this, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) +NonnullGCPtr ECMAScriptFunctionObject::create(Realm& realm, DeprecatedFlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, UsesThisFromEnvironment uses_this_from_environment, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) { - return realm.heap().allocate(realm, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, prototype, kind, is_strict, uses_this, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name)); + return realm.heap().allocate(realm, move(name), move(source_text), ecmascript_code, move(parameters), m_function_length, move(local_variables_names), parent_environment, private_environment, prototype, kind, is_strict, uses_this_from_environment, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function, move(class_field_initializer_name)); } -ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector formal_parameters, i32 function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind kind, bool strict, UsesThis uses_this, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) +ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector formal_parameters, i32 function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind kind, bool strict, UsesThisFromEnvironment uses_this_from_environment, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name) : FunctionObject(prototype) , m_name(move(name)) , m_function_length(function_length) @@ -331,7 +331,7 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Byt })); } - m_function_environment_needed = arguments_object_needs_binding || m_function_environment_bindings_count > 0 || m_var_environment_bindings_count > 0 || m_lex_environment_bindings_count > 0 || uses_this == UsesThis::Yes || m_contains_direct_call_to_eval; + m_function_environment_needed = arguments_object_needs_binding || m_function_environment_bindings_count > 0 || m_var_environment_bindings_count > 0 || m_lex_environment_bindings_count > 0 || uses_this_from_environment == UsesThisFromEnvironment::Yes || m_contains_direct_call_to_eval; } void ECMAScriptFunctionObject::initialize(Realm& realm) @@ -414,8 +414,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::internal_call(Value this_argu } // 5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). - if (m_function_environment_needed) - ordinary_call_bind_this(*callee_context, this_argument); + ordinary_call_bind_this(*callee_context, this_argument); // 6. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)). auto result = ordinary_call_evaluate_body(); @@ -478,8 +477,7 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_const // 6. If kind is base, then if (kind == ConstructorKind::Base) { // a. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). - if (m_function_environment_needed) - ordinary_call_bind_this(*callee_context, this_argument); + ordinary_call_bind_this(*callee_context, this_argument); // b. Let initializeResult be Completion(InitializeInstanceElements(thisArgument, F)). auto initialize_result = this_argument->initialize_instance_elements(*this); @@ -699,7 +697,9 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_ // 7. Assert: localEnv is a function Environment Record. // 8. Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized. // 9. Perform ! localEnv.BindThisValue(thisValue). - MUST(verify_cast(*local_env).bind_this_value(vm, this_value)); + callee_context.this_value = this_value; + if (m_function_environment_needed) + MUST(verify_cast(*local_env).bind_this_value(vm, this_value)); // 10. Return unused. } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 360e2f4deaa1f8..9dbfef6eec2bc7 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -39,8 +39,8 @@ class ECMAScriptFunctionObject final : public FunctionObject { Global, }; - static NonnullGCPtr create(Realm&, DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, UsesThis uses_this, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant class_field_initializer_name = {}); - static NonnullGCPtr create(Realm&, DeprecatedFlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, UsesThis uses_this, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant class_field_initializer_name = {}); + static NonnullGCPtr create(Realm&, DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, UsesThisFromEnvironment, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant class_field_initializer_name = {}); + static NonnullGCPtr create(Realm&, DeprecatedFlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind, bool is_strict, UsesThisFromEnvironment, bool might_need_arguments_object = true, bool contains_direct_call_to_eval = true, bool is_arrow_function = false, Variant class_field_initializer_name = {}); virtual void initialize(Realm&) override; virtual ~ECMAScriptFunctionObject() override = default; @@ -99,6 +99,8 @@ class ECMAScriptFunctionObject final : public FunctionObject { Variant const& class_field_initializer_name() const { return m_class_field_initializer_name; } + bool allocates_function_environment() const { return m_function_environment_needed; } + friend class Bytecode::Generator; protected: @@ -107,7 +109,7 @@ class ECMAScriptFunctionObject final : public FunctionObject { virtual Completion ordinary_call_evaluate_body(); private: - ECMAScriptFunctionObject(DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind, bool is_strict, UsesThis uses_this, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name); + ECMAScriptFunctionObject(DeprecatedFlyString name, ByteString source_text, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Vector local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, Object& prototype, FunctionKind, bool is_strict, UsesThisFromEnvironment, bool might_need_arguments_object, bool contains_direct_call_to_eval, bool is_arrow_function, Variant class_field_initializer_name); virtual bool is_ecmascript_function_object() const override { return true; } virtual void visit_edges(Visitor&) override; diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp index bf53edc1a03d00..f7033162992417 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp @@ -183,8 +183,8 @@ ThrowCompletionOr FunctionConstructor::create_dynamic // 18. Let body be ParseText(StringToCodePoints(bodyString), bodySym). bool contains_direct_call_to_eval = false; - bool uses_this = false; - auto body_parser = Parser::parse_function_body_from_string(body_string, parse_options, parameters, kind, contains_direct_call_to_eval, uses_this); + bool uses_this_from_environment = false; + auto body_parser = Parser::parse_function_body_from_string(body_string, parse_options, parameters, kind, contains_direct_call_to_eval, uses_this_from_environment); // 19. If body is a List of errors, throw a SyntaxError exception. if (body_parser.has_errors()) { @@ -219,7 +219,7 @@ ThrowCompletionOr FunctionConstructor::create_dynamic PrivateEnvironment* private_environment = nullptr; // 28. Let F be OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, env, privateEnv). - auto function = ECMAScriptFunctionObject::create(realm, "anonymous", *prototype, move(source_text), expr->body(), expr->parameters(), expr->function_length(), expr->local_variables_names(), &environment, private_environment, expr->kind(), expr->is_strict_mode(), uses_this ? UsesThis::Yes : UsesThis::No, expr->might_need_arguments_object(), contains_direct_call_to_eval); + auto function = ECMAScriptFunctionObject::create(realm, "anonymous", *prototype, move(source_text), expr->body(), expr->parameters(), expr->function_length(), expr->local_variables_names(), &environment, private_environment, expr->kind(), expr->is_strict_mode(), uses_this_from_environment ? UsesThisFromEnvironment::Yes : UsesThisFromEnvironment::No, expr->might_need_arguments_object(), contains_direct_call_to_eval); // FIXME: Remove the name argument from create() and do this instead. // 29. Perform SetFunctionName(F, "anonymous"). diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index 65da8ffd6ce9db..b917d613b0034f 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -498,7 +498,8 @@ ThrowCompletionOr SourceTextModule::initialize_environment(VM& vm) DeprecatedFlyString function_name = function_declaration.name(); if (function_name == ExportStatement::local_name_for_default) function_name = "default"sv; - auto function = ECMAScriptFunctionObject::create(realm(), function_name, function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), function_declaration.uses_this(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval()); + auto function = ECMAScriptFunctionObject::create(realm(), function_name, function_declaration.source_text(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), function_declaration.local_variables_names(), environment, private_environment, function_declaration.kind(), function_declaration.is_strict_mode(), + function_declaration.uses_this_from_environment(), function_declaration.might_need_arguments_object(), function_declaration.contains_direct_call_to_eval()); // 2. Perform ! env.InitializeBinding(dn, fo, normal). MUST(environment->initialize_binding(vm, name, function, Environment::InitializeBindingHint::Normal)); @@ -759,7 +760,7 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtrm_ecmascript_code, - {}, 0, {}, environment(), nullptr, FunctionKind::Async, true, UsesThis::Yes, false, false); + {}, 0, {}, environment(), nullptr, FunctionKind::Async, true, UsesThisFromEnvironment::Yes, false, false); module_wrapper_function->set_is_module_wrapper(true); // AD-HOC: We push/pop the moduleContext around the call to ensure that the async execution context diff --git a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp index cac43d38835c72..7961a12c0d3325 100644 --- a/Userland/Libraries/LibWeb/DOM/EventTarget.cpp +++ b/Userland/Libraries/LibWeb/DOM/EventTarget.cpp @@ -485,7 +485,8 @@ WebIDL::CallbackType* EventTarget::get_current_value_of_event_handler(FlyString // 6. Return scope. (NOTE: Not necessary) - auto function = JS::ECMAScriptFunctionObject::create(realm, name.to_deprecated_fly_string(), builder.to_byte_string(), program->body(), program->parameters(), program->function_length(), program->local_variables_names(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(), program->uses_this(), program->might_need_arguments_object(), is_arrow_function); + auto function = JS::ECMAScriptFunctionObject::create(realm, name.to_deprecated_fly_string(), builder.to_byte_string(), program->body(), program->parameters(), program->function_length(), program->local_variables_names(), scope, nullptr, JS::FunctionKind::Normal, program->is_strict_mode(), + program->uses_this_from_environment(), program->might_need_arguments_object(), is_arrow_function); // 10. Remove settings object's realm execution context from the JavaScript execution context stack. VERIFY(vm.execution_context_stack().last() == &settings_object.realm_execution_context()); diff --git a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp index a18b2f5ec611a0..8b7f867656c800 100644 --- a/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp +++ b/Userland/Libraries/LibWeb/WebDriver/ExecuteScript.cpp @@ -266,7 +266,8 @@ static JS::ThrowCompletionOr execute_a_function_body(Web::Page& page, // The result of parsing global scope above. // strict // The result of parsing strict above. - auto function = JS::ECMAScriptFunctionObject::create(realm, "", move(source_text), function_expression->body(), function_expression->parameters(), function_expression->function_length(), function_expression->local_variables_names(), &global_scope, nullptr, function_expression->kind(), function_expression->is_strict_mode(), function_expression->uses_this(), function_expression->might_need_arguments_object(), contains_direct_call_to_eval); + auto function = JS::ECMAScriptFunctionObject::create(realm, "", move(source_text), function_expression->body(), function_expression->parameters(), function_expression->function_length(), function_expression->local_variables_names(), &global_scope, nullptr, function_expression->kind(), function_expression->is_strict_mode(), + function_expression->uses_this_from_environment(), function_expression->might_need_arguments_object(), contains_direct_call_to_eval); // 9. Let completion be Function.[[Call]](window, parameters) with function as the this value. // NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as