Przeglądaj źródła

LibJS: Pre-calculate the number of bindings for function environments

We can use `ensure_capacity` for binding vectors if we know their sizes
in advance. This ensures that binding vectors aren't reallocated during
the `function_declaration_instantiation` execution.

With this change, `try_grow_capacity()` and `shrink_to_fit()` are no
longer visible in the `function_declaration_instantiation()` profiles
when running React-Redux-TodoMVC from Speedometer.
Aliaksandr Kalenik 1 rok temu
rodzic
commit
42e9dfedc2

+ 5 - 0
Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h

@@ -60,6 +60,11 @@ public:
 
 
     void shrink_to_fit();
     void shrink_to_fit();
 
 
+    void ensure_capacity(size_t needed_capacity)
+    {
+        m_bindings.ensure_capacity(needed_capacity);
+    }
+
     [[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; }
     [[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; }
 
 
 private:
 private:

+ 69 - 0
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -182,6 +182,26 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
         m_arguments_object_needed = false;
         m_arguments_object_needed = false;
     }
     }
 
 
+    size_t* environment_size = nullptr;
+
+    size_t parameter_environment_bindings_count = 0;
+    // 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
+        // NOTE: Here we are only interested in the size of the environment.
+        environment_size = &m_function_environment_bindings_count;
+    }
+    // 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.
+        // c. Let env be NewDeclarativeEnvironment(calleeEnv).
+        environment_size = &parameter_environment_bindings_count;
+    }
+
+    *environment_size += m_parameter_names.size();
+
     HashTable<DeprecatedFlyString> parameter_bindings;
     HashTable<DeprecatedFlyString> parameter_bindings;
 
 
     // 22. If argumentsObjectNeeded is true, then
     // 22. If argumentsObjectNeeded is true, then
@@ -189,6 +209,8 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
         // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
         // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
         parameter_bindings = m_parameter_names;
         parameter_bindings = m_parameter_names;
         parameter_bindings.set(vm().names.arguments.as_string());
         parameter_bindings.set(vm().names.arguments.as_string());
+
+        (*environment_size)++;
     } else {
     } else {
         parameter_bindings = m_parameter_names;
         parameter_bindings = m_parameter_names;
         // a. Let parameterBindings be parameterNames.
         // a. Let parameterBindings be parameterNames.
@@ -196,6 +218,8 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
 
 
     HashTable<DeprecatedFlyString> instantiated_var_names;
     HashTable<DeprecatedFlyString> instantiated_var_names;
 
 
+    size_t* var_environment_size = nullptr;
+
     // 27. If hasParameterExpressions is false, then
     // 27. If hasParameterExpressions is false, then
     if (!m_has_parameter_expressions) {
     if (!m_has_parameter_expressions) {
         // b. Let instantiatedVarNames be a copy of the List parameterBindings.
         // b. Let instantiatedVarNames be a copy of the List parameterBindings.
@@ -215,10 +239,22 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
                         .parameter_binding = parameter_bindings.contains(id.string()),
                         .parameter_binding = parameter_bindings.contains(id.string()),
                         .function_name = function_names.contains(id.string()),
                         .function_name = function_names.contains(id.string()),
                     });
                     });
+
+                    if (!id.is_local())
+                        (*environment_size)++;
                 }
                 }
             }));
             }));
         }
         }
+
+        // d. Let varEnv be env
+        var_environment_size = environment_size;
     } 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).
+        // NOTE: Here we are only interested in the size of the environment.
+        var_environment_size = &m_var_environment_bindings_count;
+
         // 28. Else,
         // 28. Else,
         // NOTE: Steps a, b, c and d are executed in function_declaration_instantiation.
         // NOTE: Steps a, b, c and d are executed in function_declaration_instantiation.
         // e. For each element n of varNames, do
         // e. For each element n of varNames, do
@@ -234,11 +270,16 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
                         .parameter_binding = parameter_bindings.contains(id.string()),
                         .parameter_binding = parameter_bindings.contains(id.string()),
                         .function_name = function_names.contains(id.string()),
                         .function_name = function_names.contains(id.string()),
                     });
                     });
+
+                    if (!id.is_local())
+                        (*var_environment_size)++;
                 }
                 }
             }));
             }));
         }
         }
     }
     }
 
 
+    // 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) {
     if (!m_strict && scope_body) {
         MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
         MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
             auto function_name = function_declaration.name();
             auto function_name = function_declaration.name();
@@ -248,11 +289,36 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
             if (!instantiated_var_names.contains(function_name) && function_name != vm().names.arguments.as_string()) {
             if (!instantiated_var_names.contains(function_name) && function_name != vm().names.arguments.as_string()) {
                 m_function_names_to_initialize_binding.append(function_name);
                 m_function_names_to_initialize_binding.append(function_name);
                 instantiated_var_names.set(function_name);
                 instantiated_var_names.set(function_name);
+                (*var_environment_size)++;
             }
             }
 
 
             function_declaration.set_should_do_additional_annexB_steps();
             function_declaration.set_should_do_additional_annexB_steps();
         }));
         }));
     }
     }
+
+    size_t* lex_environment_size = nullptr;
+
+    // 30. If strict is false, then
+    if (!m_strict) {
+        bool can_elide_declarative_environment = !m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_lexical_declarations());
+        if (can_elide_declarative_environment) {
+            lex_environment_size = var_environment_size;
+        } else {
+            // a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
+            lex_environment_size = &m_lex_environment_bindings_count;
+        }
+    } else {
+        // a. let lexEnv be varEnv.
+        // NOTE: Here we are only interested in the size of the environment.
+        lex_environment_size = var_environment_size;
+    }
+
+    if (scope_body) {
+        MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& id) {
+            if (!id.is_local())
+                (*lex_environment_size)++;
+        }));
+    }
 }
 }
 
 
 void ECMAScriptFunctionObject::initialize(Realm& realm)
 void ECMAScriptFunctionObject::initialize(Realm& realm)
@@ -694,6 +760,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
 
 
         // b. Let varEnv be NewDeclarativeEnvironment(env).
         // b. Let varEnv be NewDeclarativeEnvironment(env).
         var_environment = new_declarative_environment(*environment);
         var_environment = new_declarative_environment(*environment);
+        static_cast<DeclarativeEnvironment*>(var_environment.ptr())->ensure_capacity(m_var_environment_bindings_count);
 
 
         // c. Set the VariableEnvironment of calleeContext to varEnv.
         // c. Set the VariableEnvironment of calleeContext to varEnv.
         callee_context.variable_environment = var_environment;
         callee_context.variable_environment = var_environment;
@@ -776,6 +843,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
             //          lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places
             //          lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places
             //          all declarations into a new Environment Record.
             //          all declarations into a new Environment Record.
             lex_environment = new_declarative_environment(*var_environment);
             lex_environment = new_declarative_environment(*var_environment);
+            static_cast<DeclarativeEnvironment*>(lex_environment.ptr())->ensure_capacity(m_lex_environment_bindings_count);
         }
         }
     }
     }
     // 31. Else,
     // 31. Else,
@@ -883,6 +951,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::prepare_for_ordinary_call(Exec
 
 
     // 7. Let localEnv be NewFunctionEnvironment(F, newTarget).
     // 7. Let localEnv be NewFunctionEnvironment(F, newTarget).
     auto local_environment = new_function_environment(*this, new_target);
     auto local_environment = new_function_environment(*this, new_target);
+    local_environment->ensure_capacity(m_function_environment_bindings_count);
 
 
     // 8. Set the LexicalEnvironment of calleeContext to localEnv.
     // 8. Set the LexicalEnvironment of calleeContext to localEnv.
     callee_context.lexical_environment = local_environment;
     callee_context.lexical_environment = local_environment;

+ 4 - 0
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h

@@ -152,6 +152,10 @@ private:
     bool m_arguments_object_needed { false };
     bool m_arguments_object_needed { false };
     Vector<VariableNameToInitialize> m_var_names_to_initialize_binding;
     Vector<VariableNameToInitialize> m_var_names_to_initialize_binding;
     Vector<DeprecatedFlyString> m_function_names_to_initialize_binding;
     Vector<DeprecatedFlyString> m_function_names_to_initialize_binding;
+
+    size_t m_function_environment_bindings_count { 0 };
+    size_t m_var_environment_bindings_count { 0 };
+    size_t m_lex_environment_bindings_count { 0 };
 };
 };
 
 
 template<>
 template<>