1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-18 21:25:07 +00:00

LibJS: Run reusable steps of FunctionDeclarationInstantiation only once

This change moves steps that can be executed only once and then reused
in subsequent function instantiations from
`function_declaration_instantiation` to the ECMAScriptFunctionObject:
- Determine if there are any parameters with duplicate names.
- Determine if there are any parameters with expressions.
- Determine if an arguments object needs to be created.
- Create a list of distinct function names for which bindings need to
  be created.
- Create a list of distinct variable names for which bindings need to
  be created.

This change makes React-Redux-TodoMVC test in Speedometer
run 10% faster :)
This commit is contained in:
Aliaksandr Kalenik 2023-09-19 02:46:28 +02:00 committed by Andreas Kling
parent d27e4732b6
commit edd2f8b37f
2 changed files with 244 additions and 169 deletions

View file

@ -99,6 +99,160 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep
return false;
return true;
});
// NOTE: The following steps are from FunctionDeclarationInstantiation that could be executed once
// and then reused in all subsequent function instantiations.
// 2. Let code be func.[[ECMAScriptCode]].
ScopeNode const* scope_body = nullptr;
if (is<ScopeNode>(*m_ecmascript_code))
scope_body = static_cast<ScopeNode const*>(m_ecmascript_code.ptr());
// 3. Let strict be func.[[Strict]].
// 4. Let formals be func.[[FormalParameters]].
auto const& formals = m_formal_parameters;
// 5. Let parameterNames be the BoundNames of formals.
// 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
// NOTE: This loop performs step 5, 6, and 8.
for (auto const& parameter : formals) {
if (parameter.default_value)
m_has_parameter_expressions = true;
parameter.binding.visit(
[&](Identifier const& identifier) {
if (m_parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry)
m_has_duplicates = true;
},
[&](NonnullRefPtr<BindingPattern const> const& pattern) {
if (pattern->contains_expression())
m_has_parameter_expressions = true;
// NOTE: Nothing in the callback throws an exception.
MUST(pattern->for_each_bound_identifier([&](auto& identifier) {
if (m_parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry)
m_has_duplicates = true;
}));
});
}
// 15. Let argumentsObjectNeeded be true.
m_arguments_object_needed = m_might_need_arguments_object;
// 16. If func.[[ThisMode]] is lexical, then
if (this_mode() == ThisMode::Lexical) {
// a. NOTE: Arrow functions never have an arguments object.
// b. Set argumentsObjectNeeded to false.
m_arguments_object_needed = false;
}
// 17. Else if parameterNames contains "arguments", then
else if (m_parameter_names.contains(vm().names.arguments.as_string())) {
// a. Set argumentsObjectNeeded to false.
m_arguments_object_needed = false;
}
HashTable<DeprecatedFlyString> function_names;
// 18. Else if hasParameterExpressions is false, then
// a. If functionNames contains "arguments" or lexicalNames contains "arguments", then
// i. Set argumentsObjectNeeded to false.
// NOTE: The block below is a combination of step 14 and step 18.
if (scope_body) {
// NOTE: Nothing in the callback throws an exception.
MUST(scope_body->for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
if (function_names.set(function.name()) == AK::HashSetResult::InsertedNewEntry)
m_functions_to_initialize.append(function);
}));
auto const& arguments_name = vm().names.arguments.as_string();
if (!m_has_parameter_expressions && function_names.contains(arguments_name))
m_arguments_object_needed = false;
if (!m_has_parameter_expressions && m_arguments_object_needed) {
// NOTE: Nothing in the callback throws an exception.
MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& identifier) {
if (identifier.string() == arguments_name)
m_arguments_object_needed = false;
}));
}
} else {
m_arguments_object_needed = false;
}
HashTable<DeprecatedFlyString> parameter_bindings;
// 22. If argumentsObjectNeeded is true, then
if (m_arguments_object_needed) {
// f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
parameter_bindings = m_parameter_names;
parameter_bindings.set(vm().names.arguments.as_string());
} else {
parameter_bindings = m_parameter_names;
// a. Let parameterBindings be parameterNames.
}
HashTable<DeprecatedFlyString> instantiated_var_names;
// 27. If hasParameterExpressions is false, then
if (!m_has_parameter_expressions) {
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
instantiated_var_names = parameter_bindings;
if (scope_body) {
// c. For each element n of varNames, do
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
// i. If instantiatedVarNames does not contain n, then
if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) {
// 1. Append n to instantiatedVarNames.
// Following steps will be executed in function_declaration_instantiation:
// 2. Perform ! env.CreateMutableBinding(n, false).
// 3. Perform ! env.InitializeBinding(n, undefined).
m_var_names_to_initialize_binding.append({
.identifier = id,
.parameter_binding = parameter_bindings.contains(id.string()),
.function_name = function_names.contains(id.string()),
});
}
}));
}
} else {
// 28. Else,
// NOTE: Steps a, b, c and d are executed in function_declaration_instantiation.
// e. For each element n of varNames, do
if (scope_body) {
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
// 1. Append n to instantiatedVarNames.
// Following steps will be executed in function_declaration_instantiation:
// 2. Perform ! env.CreateMutableBinding(n, false).
// 3. Perform ! env.InitializeBinding(n, undefined).
if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) {
m_var_names_to_initialize_binding.append({
.identifier = id,
.parameter_binding = parameter_bindings.contains(id.string()),
.function_name = function_names.contains(id.string()),
});
}
}));
}
}
if (!m_strict && scope_body) {
MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
auto function_name = function_declaration.name();
if (parameter_bindings.contains(function_name))
return;
if (!instantiated_var_names.contains(function_name) && function_name != vm().names.arguments.as_string()) {
m_function_names_to_initialize_binding.append(function_name);
instantiated_var_names.set(function_name);
}
function_declaration.set_should_do_additional_annexB_steps();
}));
}
}
void ECMAScriptFunctionObject::initialize(Realm& realm)
@ -340,115 +494,32 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
if (is<ScopeNode>(*m_ecmascript_code))
scope_body = static_cast<ScopeNode const*>(m_ecmascript_code.ptr());
// 3. Let strict be func.[[Strict]].
bool const strict = is_strict_mode();
bool has_parameter_expressions = false;
// 4. Let formals be func.[[FormalParameters]].
auto const& formals = m_formal_parameters;
// FIXME: Maybe compute has duplicates at parse time? (We need to anyway since it's an error in some cases)
// 5. Let parameterNames be the BoundNames of formals.
// 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
bool has_duplicates = false;
HashTable<DeprecatedFlyString> parameter_names;
// NOTE: This loop performs step 5, 6, and 8.
for (auto const& parameter : formals) {
if (parameter.default_value)
has_parameter_expressions = true;
parameter.binding.visit(
[&](Identifier const& identifier) {
if (parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry)
has_duplicates = true;
},
[&](NonnullRefPtr<BindingPattern const> const& pattern) {
if (pattern->contains_expression())
has_parameter_expressions = true;
// NOTE: Nothing in the callback throws an exception.
MUST(pattern->for_each_bound_identifier([&](auto& identifier) {
if (parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry)
has_duplicates = true;
}));
});
}
// 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();
// 8. Let hasParameterExpressions be ContainsExpression of formals.
// NOTE: Already set above.
// 9. Let varNames be the VarDeclaredNames of code.
// 10. Let varDeclarations be the VarScopedDeclarations of code.
// 11. Let lexicalNames be the LexicallyDeclaredNames of code.
// NOTE: Not needed as we use iteration helpers for this instead.
// 12. Let functionNames be a new empty List.
HashTable<DeprecatedFlyString> function_names;
// 13. Let functionsToInitialize be a new empty List.
Vector<FunctionDeclaration const&> functions_to_initialize;
// 14. For each element d of varDeclarations, in reverse List order, do
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
// i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// ii. Let fn be the sole element of the BoundNames of d.
// iii. If functionNames does not contain fn, then
// 1. Insert fn as the first element of functionNames.
// 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
// 3. Insert d as the first element of functionsToInitialize.
// NOTE: This block is done in step 18 below.
// 15. Let argumentsObjectNeeded be true.
auto arguments_object_needed = m_might_need_arguments_object;
// 16. If func.[[ThisMode]] is lexical, then
if (this_mode() == ThisMode::Lexical) {
// a. NOTE: Arrow functions never have an arguments object.
// b. Set argumentsObjectNeeded to false.
arguments_object_needed = false;
}
// 17. Else if parameterNames contains "arguments", then
else if (parameter_names.contains(vm.names.arguments.as_string())) {
// a. Set argumentsObjectNeeded to false.
arguments_object_needed = false;
}
// 18. Else if hasParameterExpressions is false, then
// a. If functionNames contains "arguments" or lexicalNames contains "arguments", then
// i. Set argumentsObjectNeeded to false.
// NOTE: The block below is a combination of step 14 and step 18.
if (scope_body) {
// NOTE: Nothing in the callback throws an exception.
MUST(scope_body->for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) {
if (function_names.set(function.name()) == AK::HashSetResult::InsertedNewEntry)
functions_to_initialize.append(function);
}));
auto const& arguments_name = vm.names.arguments.as_string();
if (!has_parameter_expressions && function_names.contains(arguments_name))
arguments_object_needed = false;
if (!has_parameter_expressions && arguments_object_needed) {
// NOTE: Nothing in the callback throws an exception.
MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& identifier) {
if (identifier.string() == arguments_name)
arguments_object_needed = false;
}));
}
} else {
arguments_object_needed = false;
}
// 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> environment;
// 19. If strict is true or hasParameterExpressions is false, then
if (strict || !has_parameter_expressions) {
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;
@ -471,7 +542,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
}
// 21. For each String paramName of parameterNames, do
for (auto const& parameter_name : parameter_names) {
for (auto const& parameter_name : m_parameter_names) {
// a. Let alreadyDeclared be ! env.HasBinding(paramName).
auto already_declared = MUST(environment->has_binding(parameter_name));
@ -483,7 +554,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
MUST(environment->create_mutable_binding(vm, parameter_name, false));
// ii. If hasDuplicates is true, then
if (has_duplicates) {
if (m_has_duplicates) {
// 1. Perform ! env.InitializeBinding(paramName, undefined).
MUST(environment->initialize_binding(vm, parameter_name, js_undefined(), Environment::InitializeBindingHint::Normal));
}
@ -491,11 +562,11 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
}
// 22. If argumentsObjectNeeded is true, then
if (arguments_object_needed) {
if (m_arguments_object_needed) {
Object* arguments_object;
// a. If strict is true or simpleParameterList is false, then
if (strict || !simple_parameter_list) {
if (m_strict || !simple_parameter_list) {
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
arguments_object = create_unmapped_arguments_object(vm, vm.running_execution_context().arguments);
}
@ -508,7 +579,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
}
// c. If strict is true, then
if (strict) {
if (m_strict) {
// i. Perform ! env.CreateImmutableBinding("arguments", false).
MUST(environment->create_immutable_binding(vm, vm.names.arguments.as_string(), false));
@ -524,7 +595,6 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
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" ».
parameter_names.set(vm.names.arguments.as_string());
}
// 23. Else,
else {
@ -567,7 +637,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
argument_value = js_undefined();
}
Environment* used_environment = has_duplicates ? nullptr : environment;
Environment* used_environment = m_has_duplicates ? nullptr : environment;
if constexpr (IsSame<NonnullRefPtr<Identifier const> const&, decltype(param)>) {
if (param->is_local()) {
@ -576,7 +646,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
}
Reference reference = TRY(vm.resolve_binding(param->string(), used_environment));
// Here the difference from hasDuplicates is important
if (has_duplicates)
if (m_has_duplicates)
return reference.put_value(vm, argument_value);
return reference.initialize_referenced_binding(vm, argument_value);
}
@ -594,7 +664,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
instantiated_var_names.ensure_capacity(scope_body->var_declaration_count());
// 27. If hasParameterExpressions is false, then
if (!has_parameter_expressions) {
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.
@ -605,21 +675,20 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
// an exception should not result from `for_each_var_declared_name`.
// c. For each element n of varNames, do
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
// i. If instantiatedVarNames does not contain n, then
if (!parameter_names.contains(id.string()) && instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) {
// 1. Append n to instantiatedVarNames.
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.local_variables[id.local_variable_index()] = js_undefined();
} else {
// 2. Perform ! env.CreateMutableBinding(n, false).
// 3. Perform ! env.InitializeBinding(n, undefined).
if (id.is_local()) {
callee_context.local_variables[id.local_variable_index()] = js_undefined();
} else {
MUST(environment->create_mutable_binding(vm, id.string(), false));
MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal));
}
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
@ -643,69 +712,61 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
// an exception should not result from `for_each_var_declared_name`.
// e. For each element n of varNames, do
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
// i. If instantiatedVarNames does not contain n, then
if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) {
// 1. Append n to instantiatedVarNames.
for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) {
auto const& id = variable_to_initialize.identifier;
// 2. Perform ! varEnv.CreateMutableBinding(n, false).
MUST(var_environment->create_mutable_binding(vm, id.string(), false));
// NOTE: Following steps were executed in ECMAScriptFunctionObject constructor.
// i. If instantiatedVarNames does not contain n, then
// 1. Append n to instantiatedVarNames.
Value initial_value;
// 2. Perform ! varEnv.CreateMutableBinding(n, false).
MUST(var_environment->create_mutable_binding(vm, id.string(), false));
// 3. If parameterBindings does not contain n, or if functionNames contains n, then
if (!parameter_names.contains(id.string()) || function_names.contains(id.string())) {
// 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.local_variables[id.local_variable_index()];
} else {
initial_value = MUST(environment->get_binding_value(vm, id.string(), false));
}
}
Value initial_value;
// 5. Perform ! varEnv.InitializeBinding(n, initialValue).
if (id.is_local()) {
// NOTE: Local variables are supported only in bytecode interpreter
callee_context.local_variables[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.
// 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.local_variables[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.local_variables[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 (!strict && scope_body) {
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`.
MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) {
auto function_name = function_declaration.name();
if (parameter_names.contains(function_name))
return;
// The spec says 'initializedBindings' here but that does not exist and it then adds it to 'instantiatedVarNames' so it probably means 'instantiatedVarNames'.
if (!instantiated_var_names.contains(function_name) && function_name != vm.names.arguments.as_string()) {
MUST(var_environment->create_mutable_binding(vm, function_name, false));
MUST(var_environment->initialize_binding(vm, function_name, js_undefined(), Environment::InitializeBindingHint::Normal));
instantiated_var_names.set(function_name);
}
function_declaration.set_should_do_additional_annexB_steps();
}));
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<Environment> lex_environment;
// 30. If strict is false, then
if (!strict) {
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
@ -766,7 +827,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
auto private_environment = callee_context.private_environment;
// 36. For each Parse Node f of functionsToInitialize, do
for (auto& declaration : functions_to_initialize) {
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.might_need_arguments_object(), declaration.contains_direct_call_to_eval());