From 12b283f32fe177bf3acafb2d688a75180da4bac7 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Thu, 7 Oct 2021 20:13:22 +0330 Subject: [PATCH] LibJS: Make accessing the current function's arguments cheaper Instead of going through an environment record, make arguments of the currently executing function generate references via the argument index, which can later be resolved directly through the ExecutionContext. --- Userland/Libraries/LibJS/AST.cpp | 22 +++- Userland/Libraries/LibJS/AST.h | 2 + Userland/Libraries/LibJS/Parser.cpp | 118 +++++++++++++++++- .../Libraries/LibJS/Runtime/Reference.cpp | 42 +++++++ Userland/Libraries/LibJS/Runtime/Reference.h | 27 +++- 5 files changed, 207 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 3b9ca881fc..df17066a04 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1011,10 +1011,30 @@ Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) cons environment = environment->outer_environment(); VERIFY(environment); VERIFY(environment->is_declarative_environment()); - if (!environment->is_permanently_screwed_by_eval()) + if (!environment->is_permanently_screwed_by_eval()) { + if (m_lexically_bound_function_argument.has_value()) { + return Reference { + *environment, + string(), + *m_lexically_bound_function_argument, + interpreter.vm().in_strict_mode(), + m_cached_environment_coordinate, + &interpreter.vm().running_execution_context(), + }; + } return Reference { *environment, string(), interpreter.vm().in_strict_mode(), m_cached_environment_coordinate }; + } m_cached_environment_coordinate = {}; } + if (m_lexically_bound_function_argument.has_value()) { + return Reference { + string(), + *m_lexically_bound_function_argument, + interpreter.vm().in_strict_mode(), + &interpreter.vm().running_execution_context(), + }; + } + auto reference = interpreter.vm().resolve_binding(string()); if (reference.environment_coordinate().has_value()) m_cached_environment_coordinate = reference.environment_coordinate(); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 3952c5a8db..27a1797871 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -982,6 +982,7 @@ public: } FlyString const& string() const { return m_string; } + void set_lexically_bound_function_argument_index(size_t index) { m_lexically_bound_function_argument = index; } virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; @@ -992,6 +993,7 @@ private: virtual bool is_identifier() const override { return true; } FlyString m_string; + Optional m_lexically_bound_function_argument; mutable Optional m_cached_environment_coordinate; }; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 0bd68ee8b9..bf33bfcb52 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020, Stephan Unverwerth * Copyright (c) 2020-2021, Linus Groh * Copyright (c) 2021, David Tuin + * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ @@ -43,6 +44,7 @@ public: static ScopePusher function_scope(Parser& parser, FunctionBody& function_body, Vector const& parameters) { ScopePusher scope_pusher(parser, &function_body, true); + scope_pusher.m_function_parameters = parameters; for (auto& parameter : parameters) { parameter.binding.visit( [&](FlyString const& name) { @@ -160,10 +162,46 @@ public: } } + ScopePusher const* last_function_scope() const + { + for (auto scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) { + if (scope_ptr->m_function_parameters.has_value()) + return scope_ptr; + } + return nullptr; + } + + Vector const& function_parameters() const + { + return *m_function_parameters; + } + + ScopePusher* parent_scope() { return m_parent_scope; } + ScopePusher const* parent_scope() const { return m_parent_scope; } + + [[nodiscard]] bool has_declaration(StringView name) const + { + return m_lexical_names.contains(name) || m_var_names.contains(name) || !m_functions_to_hoist.find_if([&name](auto& function) { return function->name() == name; }).is_end(); + } + + bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; } + bool contains_access_to_arguments_object() const { return m_contains_access_to_arguments_object; } + void set_contains_direct_call_to_eval() { m_contains_direct_call_to_eval = true; } + void set_contains_access_to_arguments_object() { m_contains_access_to_arguments_object = true; } + ~ScopePusher() { VERIFY(m_is_top_level || m_parent_scope); + if (!m_contains_access_to_arguments_object) { + for (auto& it : m_identifier_and_argument_index_associations) { + for (auto& identifier : it.value) { + if (!has_declaration(identifier.string())) + identifier.set_lexically_bound_function_argument_index(it.key); + } + } + } + for (size_t i = 0; i < m_functions_to_hoist.size(); i++) { auto const& function_declaration = m_functions_to_hoist[i]; if (m_lexical_names.contains(function_declaration.name()) || m_forbidden_var_names.contains(function_declaration.name())) @@ -174,10 +212,20 @@ public: m_parent_scope->m_functions_to_hoist.append(move(m_functions_to_hoist[i])); } + if (m_parent_scope && !m_function_parameters.has_value()) { + m_parent_scope->m_contains_access_to_arguments_object |= m_contains_access_to_arguments_object; + m_parent_scope->m_contains_direct_call_to_eval |= m_contains_direct_call_to_eval; + } + VERIFY(m_parser.m_state.current_scope_pusher == this); m_parser.m_state.current_scope_pusher = m_parent_scope; } + void associate_identifier_with_argument_index(NonnullRefPtr identifier, size_t index) + { + m_identifier_and_argument_index_associations.ensure(index).append(move(identifier)); + } + private: void throw_identifier_declared(FlyString const& name, NonnullRefPtr const& declaration) { @@ -198,6 +246,12 @@ private: HashTable m_forbidden_lexical_names; HashTable m_forbidden_var_names; NonnullRefPtrVector m_functions_to_hoist; + + Optional> m_function_parameters; + HashMap> m_identifier_and_argument_index_associations; + + bool m_contains_access_to_arguments_object { false }; + bool m_contains_direct_call_to_eval { false }; }; class OperatorPrecedenceTable { @@ -629,8 +683,9 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe // for arrow function bodies which are a single expression. // Esprima generates a single "ArrowFunctionExpression" // with a "body" property. - auto return_expression = parse_expression(2); auto return_block = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); + ScopePusher function_scope = ScopePusher::function_scope(*this, return_block, parameters); + auto return_expression = parse_expression(2); return_block->append({ m_filename, rule_start.position(), position() }, move(return_expression)); if (m_state.strict_mode) return_block->set_strict_mode(); @@ -1460,6 +1515,48 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati syntax_error("Invalid property in object literal", range->start); } }; + if (is(*expression) && m_state.current_scope_pusher) { + auto identifier_instance = static_ptr_cast(expression); + auto function_scope = m_state.current_scope_pusher->last_function_scope(); + auto function_parent_scope = function_scope ? function_scope->parent_scope() : nullptr; + bool has_not_been_declared_as_variable = true; + for (auto scope = m_state.current_scope_pusher; scope != function_parent_scope; scope = scope->parent_scope()) { + if (scope->has_declaration(identifier_instance->string())) { + has_not_been_declared_as_variable = false; + break; + } + } + + if (has_not_been_declared_as_variable) { + if (identifier_instance->string() == "arguments"sv) + m_state.current_scope_pusher->set_contains_access_to_arguments_object(); + } + + if (function_scope && has_not_been_declared_as_variable) { + + auto& parameters = function_scope->function_parameters(); + Optional argument_index; + size_t index = 0; + for (auto& parameter : parameters) { + auto current_index = index++; + if (parameter.is_rest) + break; + if (auto name_ptr = parameter.binding.get_pointer()) { + // Need VM assistance for this, so let's just pretend it's not there. + if (parameter.default_value) + break; + if (identifier_instance->string() == *name_ptr) { + argument_index = current_index; + if (m_state.strict_mode) + break; + } + } + } + if (argument_index.has_value()) + m_state.current_scope_pusher->associate_identifier_with_argument_index(identifier_instance, *argument_index); + } + } + while (match(TokenType::TemplateLiteralStart)) { auto template_literal = parse_template_literal(true); expression = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(template_literal)); @@ -1487,6 +1584,24 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati check_for_invalid_object_property(expression); + if (is(*expression) && m_state.current_scope_pusher) { + auto& callee = static_ptr_cast(expression)->callee(); + if (is(callee)) { + auto& identifier_instance = static_cast(callee); + if (identifier_instance.string() == "eval"sv) { + bool has_not_been_declared_as_variable = true; + for (auto scope = m_state.current_scope_pusher; scope; scope = scope->parent_scope()) { + if (scope->has_declaration(identifier_instance.string())) { + has_not_been_declared_as_variable = false; + break; + } + } + if (has_not_been_declared_as_variable) + m_state.current_scope_pusher->set_contains_direct_call_to_eval(); + } + } + } + if (match(TokenType::Comma) && min_precedence <= 1) { NonnullRefPtrVector expressions; expressions.append(expression); @@ -3646,5 +3761,4 @@ NonnullRefPtr Parser::parse_export_statement(Program& program) return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(entries)); } - } diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp index 3af87c9826..c0f71a5fb6 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.cpp +++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp @@ -48,6 +48,31 @@ void Reference::put_value(GlobalObject& global_object, Value value) } VERIFY(m_base_type == BaseType::Environment); + + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + // Note: Modifying this binding requires us to sync with the environment. + if (!m_base_environment) { + auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment); + m_base_environment = real_reference.m_base_environment; + } + + if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context()) { + auto& arguments = m_referenced_function_context->arguments; + auto index = m_function_argument_index.value(); + if (arguments.size() > index) { + arguments[index] = value; + } else { + arguments.ensure_capacity(index + 1); + for (size_t i = arguments.size(); i < index; ++i) + arguments.append(js_undefined()); + arguments.append(value); + } + m_base_environment->set_mutable_binding(global_object, name().as_string(), value, is_strict()); + return; + } + } + VERIFY(m_base_environment); if (m_environment_coordinate.has_value()) static_cast(m_base_environment)->set_mutable_binding_direct(global_object, m_environment_coordinate->index, value, m_strict); @@ -80,6 +105,17 @@ Value Reference::get_value(GlobalObject& global_object) const } VERIFY(m_base_type == BaseType::Environment); + + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context()) + return global_object.vm().argument(m_function_argument_index.value()); + if (!m_base_environment) { + auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment); + m_base_environment = real_reference.m_base_environment; + } + } + VERIFY(m_base_environment); if (m_environment_coordinate.has_value()) return static_cast(m_base_environment)->get_binding_value_direct(global_object, m_environment_coordinate->index, m_strict); @@ -141,6 +177,12 @@ bool Reference::delete_(GlobalObject& global_object) VERIFY(m_base_type == BaseType::Environment); + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + // This is a direct reference to a function argument. + return false; + } + // c. Return ? base.DeleteBinding(ref.[[ReferencedName]]). return m_base_environment->delete_binding(global_object, m_name.as_string()); } diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h index 8749fe5dff..8d47fbaf09 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.h +++ b/Userland/Libraries/LibJS/Runtime/Reference.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,28 @@ public: { } + Reference(FlyString referenced_name, size_t function_argument_index, bool strict = false, ExecutionContext* function_context = nullptr) + : m_base_type(BaseType::Environment) + , m_base_environment(nullptr) + , m_name(move(referenced_name)) + , m_strict(strict) + , m_environment_coordinate({}) + , m_function_argument_index(function_argument_index) + , m_referenced_function_context(function_context) + { + } + + Reference(Environment& base, FlyString referenced_name, size_t function_argument_index, bool strict = false, Optional environment_coordinate = {}, ExecutionContext* function_context = nullptr) + : m_base_type(BaseType::Environment) + , m_base_environment(&base) + , m_name(move(referenced_name)) + , m_strict(strict) + , m_environment_coordinate(move(environment_coordinate)) + , m_function_argument_index(function_argument_index) + , m_referenced_function_context(function_context) + { + } + Value base() const { VERIFY(m_base_type == BaseType::Value); @@ -128,12 +151,14 @@ private: BaseType m_base_type { BaseType::Unresolvable }; union { Value m_base_value {}; - Environment* m_base_environment; + mutable Environment* m_base_environment; }; PropertyName m_name; Value m_this_value; bool m_strict { false }; Optional m_environment_coordinate; + Optional m_function_argument_index; + ExecutionContext* m_referenced_function_context { nullptr }; }; }