From b0a533dbc0f67de47fb0c7886180a7fde40818d2 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 12 Jul 2023 04:02:27 +0200 Subject: [PATCH] LibJS: Identify global variables during parsing Identifying global variables during parsing will make it possible to generate special optimized instruction to access them in upcoming changes. --- Userland/Libraries/LibJS/AST.cpp | 2 + Userland/Libraries/LibJS/AST.h | 4 + Userland/Libraries/LibJS/Parser.cpp | 85 ++++++++++++++----- Userland/Libraries/LibJS/Parser.h | 2 + .../Libraries/LibJS/Runtime/ShadowRealm.cpp | 2 +- 5 files changed, 75 insertions(+), 20 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 6b0f1857c6..b6355c4385 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -2717,6 +2717,8 @@ void Identifier::dump(int indent) const print_indent(indent); if (is_local()) { outln("Identifier \"{}\" is_local=(true) index=({})", m_string, m_local_variable_index); + } else if (is_global()) { + outln("Identifier \"{}\" is_global=(true)", m_string); } else { outln("Identifier \"{}\"", m_string); } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 5fb4ae9458..74154e1dbe 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -684,6 +684,9 @@ public: } void set_local_variable_index(size_t index) { m_local_variable_index = index; } + bool is_global() const { return m_is_global; } + void set_is_global() { m_is_global = true; } + virtual Completion execute(Interpreter&) const override; virtual void dump(int indent) const override; virtual ThrowCompletionOr to_reference(Interpreter&) const override; @@ -696,6 +699,7 @@ private: mutable EnvironmentCoordinate m_cached_environment_coordinate; Optional m_local_variable_index; + bool m_is_global { false }; }; struct FunctionParameter { diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 7bd6fa01ba..4b00bc3f8d 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -75,9 +75,13 @@ private: } public: - static ScopePusher function_scope(Parser& parser) + static ScopePusher function_scope(Parser& parser, RefPtr function_name = nullptr) { - return ScopePusher(parser, nullptr, ScopeLevel::FunctionTopLevel, ScopeType::Function); + ScopePusher scope_pusher(parser, nullptr, ScopeLevel::FunctionTopLevel, ScopeType::Function); + if (function_name) { + scope_pusher.m_bound_names.set(function_name->string()); + } + return scope_pusher; } static ScopePusher program_scope(Parser& parser, Program& program) @@ -237,7 +241,7 @@ public: void set_contains_direct_call_to_eval() { m_contains_direct_call_to_eval = true; - m_can_use_local_variables = false; + m_screwed_by_eval_in_scope_chain = true; } void set_contains_access_to_arguments_object() { m_contains_access_to_arguments_object = true; } void set_scope_node(ScopeNode* node) { m_node = node; } @@ -270,12 +274,8 @@ public: m_parent_scope->m_contains_await_expression |= m_contains_await_expression; } - if (m_parent_scope) { - if (m_contains_direct_call_to_eval) { - m_parent_scope->m_can_use_local_variables = false; - } else { - m_can_use_local_variables = m_parent_scope->m_can_use_local_variables && m_can_use_local_variables; - } + if (m_parent_scope && m_contains_direct_call_to_eval) { + m_parent_scope->m_screwed_by_eval_in_scope_chain = true; } if (!m_node) { @@ -287,6 +287,12 @@ public: auto const& identifier_group_name = it.key; auto& identifier_group = it.value; + if (m_parser.m_state.in_catch_parameter_context) { + // NOTE: The parser currently cannot determine if an identifier captured by a function belongs to the environment created by a catch parameter. + // As a result, any identifiers used inside the catch parameter are not considered as candidates for optimization in local or global variable access. + continue; + } + if (identifier_group_name == "arguments"sv) { // NOTE: arguments is a special variable that should not be treated as a candidate to become local continue; @@ -320,27 +326,57 @@ public: } if ((m_type == ScopeType::ClassDeclaration || m_type == ScopeType::Catch) && m_bound_names.contains(identifier_group_name)) { - // NOTE: Currently class names and catch section parameters are not considered to become local variables - // but this might be fixed in the future + // NOTE: Currently, the parser cannot recognize that assigning a named function expression creates a scope with a binding for the function name. + // As a result, function names are not considered as candidates for optimization in global variable access. continue; } + if (m_type == ScopeType::Function && m_bound_names.contains(identifier_group_name)) { + // NOTE: Currently parser can't determine that named function expression assigment creates scope with binding for funciton name so function names are not considered as candidates to be optmized in global variables access + identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment = true; + } + if (m_type == ScopeType::ClassDeclaration || m_type == ScopeType::Catch) { // NOTE: Class declaration and catch scopes do not have own ScopeNode hence can't contain declaration of any variable scope_has_declaration = false; } - if (m_type == ScopeType::Function && !m_contains_access_to_arguments_object && m_function_parameters_candidates_for_local_variables.contains(identifier_group_name)) { - scope_has_declaration = true; + if (m_type == ScopeType::Function) { + if (!m_contains_access_to_arguments_object && m_function_parameters_candidates_for_local_variables.contains(identifier_group_name)) { + scope_has_declaration = true; + } else if (m_forbidden_lexical_names.contains(identifier_group_name)) { + // NOTE: If an identifier is used as a function parameter that cannot be optimized locally or globally, it is simply ignored. + continue; + } } - if (scope_has_declaration) { + if (m_type == ScopeType::Function && hoistable_function_declaration) { + // NOTE: Hoistable function declarations are currently not optimized into global or local variables, but future improvements may change that. + continue; + } + + if (m_type == ScopeType::Program) { + auto can_use_global_for_identifier = true; + if (identifier_group.used_inside_with_statement) + can_use_global_for_identifier = false; + else if (identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment) + can_use_global_for_identifier = false; + else if (m_screwed_by_eval_in_scope_chain) + can_use_global_for_identifier = false; + else if (m_parser.m_state.initiated_by_eval) + can_use_global_for_identifier = false; + + if (can_use_global_for_identifier) { + for (auto& identifier : identifier_group.identifiers) + identifier->set_is_global(); + } + } else if (scope_has_declaration) { if (hoistable_function_declaration) continue; - if (!identifier_group.captured_by_nested_function) { + if (!identifier_group.captured_by_nested_function && !identifier_group.used_inside_with_statement) { auto function_scope = last_function_scope(); - if (!function_scope || !m_can_use_local_variables) { + if (!function_scope || m_screwed_by_eval_in_scope_chain) { continue; } @@ -349,16 +385,23 @@ public: identifier->set_local_variable_index(local_variable_index); } } else { - if (m_function_parameters.has_value() || m_type == ScopeType::ClassField || m_type == ScopeType::ClassStaticInit || m_type == ScopeType::With) { + if (m_function_parameters.has_value() || m_type == ScopeType::ClassField || m_type == ScopeType::ClassStaticInit) { // NOTE: Class fields and class static initialization sections implicitly create functions identifier_group.captured_by_nested_function = true; } + if (m_type == ScopeType::With) + identifier_group.used_inside_with_statement = true; + if (m_parent_scope) { if (auto maybe_parent_scope_identifier_group = m_parent_scope->m_identifier_groups.get(identifier_group_name); maybe_parent_scope_identifier_group.has_value()) { maybe_parent_scope_identifier_group.value().identifiers.extend(identifier_group.identifiers); if (identifier_group.captured_by_nested_function) maybe_parent_scope_identifier_group.value().captured_by_nested_function = true; + if (identifier_group.used_inside_with_statement) + maybe_parent_scope_identifier_group.value().used_inside_with_statement = true; + if (identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment) + maybe_parent_scope_identifier_group.value().might_be_variable_in_lexical_scope_in_named_function_assignment = true; } else { m_parent_scope->m_identifier_groups.set(identifier_group_name, identifier_group); } @@ -436,6 +479,8 @@ private: struct IdentifierGroup { bool captured_by_nested_function { false }; + bool used_inside_with_statement { false }; + bool might_be_variable_in_lexical_scope_in_named_function_assignment { false }; Vector> identifiers; }; HashMap m_identifier_groups; @@ -445,7 +490,7 @@ private: bool m_contains_access_to_arguments_object { false }; bool m_contains_direct_call_to_eval { false }; bool m_contains_await_expression { false }; - bool m_can_use_local_variables { true }; + bool m_screwed_by_eval_in_scope_chain { false }; }; class OperatorPrecedenceTable { @@ -576,6 +621,7 @@ Parser::Parser(Lexer lexer, Program::Type program_type, Optionalin_eval_function_context; m_state.allow_super_property_lookup = initial_state_for_eval->allow_super_property_lookup; m_state.allow_super_constructor_call = initial_state_for_eval->allow_super_constructor_call; @@ -2831,7 +2877,7 @@ NonnullRefPtr Parser::parse_function_node(u16 parse_options, O Vector parameters; bool contains_direct_call_to_eval = false; auto body = [&] { - ScopePusher function_scope = ScopePusher::function_scope(*this); + ScopePusher function_scope = ScopePusher::function_scope(*this, name); consume(TokenType::ParenOpen); parameters = parse_formal_parameters(function_length, parse_options); @@ -3634,6 +3680,7 @@ NonnullRefPtr Parser::parse_catch_clause() RefPtr pattern_parameter; auto should_expect_parameter = false; if (match(TokenType::ParenOpen)) { + TemporaryChange catch_parameter_context_change { m_state.in_catch_parameter_context, true }; should_expect_parameter = true; consume(); if (match_identifier_name() diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index ebf5b9b0fb..0a08894ffd 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -316,8 +316,10 @@ private: bool allow_super_property_lookup { false }; bool allow_super_constructor_call { false }; bool in_function_context { false }; + bool initiated_by_eval { false }; bool in_eval_function_context { false }; // This controls if we allow new.target or not. Note that eval("return") is not allowed, so we have to have a separate state variable for eval. bool in_formal_parameter_context { false }; + bool in_catch_parameter_context { false }; bool in_generator_function_context { false }; bool await_expression_is_valid { false }; bool in_arrow_function_context { false }; diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index 03395d2f69..5397e6ec71 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -103,7 +103,7 @@ ThrowCompletionOr perform_shadow_realm_eval(VM& vm, StringView source_tex // 2. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection: // a. Let script be ParseText(StringToCodePoints(sourceText), Script). - auto parser = Parser(Lexer(source_text)); + auto parser = Parser(Lexer(source_text), Program::Type::Script, Parser::EvalInitialState {}); auto program = parser.parse_program(); // b. If script is a List of errors, throw a SyntaxError exception.