LibJS: Rewrite eval declaration instantiation using metadata

Change eval_declaration_instantiation to take EvalDeclarationData&
instead of Program const&. The function body now iterates
pre-computed name lists instead of walking the AST.

Both callers (perform_eval and perform_shadow_realm_eval) now build
EvalDeclarationData before calling eval_declaration_instantiation.
This decouples the runtime declaration-instantiation API from AST
types, matching the pattern already used by Script for global
declaration instantiation.
This commit is contained in:
Andreas Kling 2026-02-11 02:13:32 +01:00 committed by Andreas Kling
parent 87b9795d75
commit 680487fa9a
3 changed files with 65 additions and 113 deletions

View file

@ -723,7 +723,8 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
// NOTE: Spec steps are rearranged in order to compute number of registers+constants+locals before construction of the execution context.
// 30. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, privateEnv, strictEval)).
TRY(eval_declaration_instantiation(vm, program, variable_environment, lexical_environment, private_environment, strict_eval));
auto eval_declaration_data = EvalDeclarationData::create(vm, program, strict_eval);
TRY(eval_declaration_instantiation(vm, eval_declaration_data, variable_environment, lexical_environment, private_environment, strict_eval));
// 31. If result.[[Type]] is normal, then
// a. Set result to the result of evaluating body.
@ -827,7 +828,7 @@ EvalDeclarationData EvalDeclarationData::create(VM& vm, Program const& program,
// 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
// 9.1.1.1 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/proposal-explicit-resource-management/#sec-evaldeclarationinstantiation
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& program, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict)
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, EvalDeclarationData& data, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict)
{
auto& realm = *vm.current_realm();
GlobalEnvironment* global_var_environment = variable_environment->is_global_environment() ? static_cast<GlobalEnvironment*>(variable_environment) : nullptr;
@ -839,16 +840,13 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// a. If varEnv is a global Environment Record, then
if (global_var_environment) {
// i. For each element name of varNames, do
TRY(program.for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
auto const& name = identifier.string();
for (auto const& name : data.var_names) {
// 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
if (global_var_environment->has_lexical_declaration(name))
return vm.throw_completion<SyntaxError>(ErrorType::TopLevelVariableAlreadyDeclared, identifier.string());
return vm.throw_completion<SyntaxError>(ErrorType::TopLevelVariableAlreadyDeclared, name);
// 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
return {};
}));
}
}
// b. Let thisEnv be lexEnv.
@ -861,9 +859,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
if (!is<ObjectEnvironment>(*this_environment)) {
// 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.
// 2. For each element name of varNames, do
TRY(program.for_each_var_declared_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
auto const& name = identifier.string();
for (auto const& name : data.var_names) {
// a. If ! thisEnv.HasBinding(name) is true, then
if (MUST(this_environment->has_binding(name))) {
// i. Throw a SyntaxError exception.
@ -873,8 +869,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// In particular it only throw the syntax error if it is not an environment from a catchclause.
}
// b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
return {};
}));
}
}
// ii. Set thisEnv to thisEnv.[[OuterEnv]].
@ -893,44 +888,19 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// FIXME: Add Private identifiers check here.
// 8. Let functionsToInitialize be a new empty List.
Vector<FunctionDeclaration const&> functions_to_initialize;
// 9. Let declaredFunctionNames be a new empty List.
HashTable<Utf16FlyString> declared_function_names;
// 10. For each element d of varDeclarations, in reverse List order, do
TRY(program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr<void> {
auto function_name = function.name();
// 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.
// Note: This is done by for_each_var_function_declaration_in_reverse_order.
// ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
// iii. Let fn be the sole element of the BoundNames of d.
// iv. If fn is not an element of declaredFunctionNames, then
if (declared_function_names.set(function_name) != AK::HashSetResult::InsertedNewEntry)
return {};
for (auto const& function : data.functions_to_initialize) {
// 1. If varEnv is a global Environment Record, then
if (global_var_environment) {
// a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn).
auto function_definable = TRY(global_var_environment->can_declare_global_function(function_name));
auto function_definable = TRY(global_var_environment->can_declare_global_function(function.name));
// b. If fnDefinable is false, throw a TypeError exception.
if (!function_definable)
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalFunction, function_name);
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalFunction, function.name);
}
// 2. Append fn to declaredFunctionNames.
// Note: Already done in step iv.
// 3. Insert d as the first element of functionsToInitialize.
// NOTE: Since prepending is much slower, we just append
// and iterate in reverse order in step 17 below.
functions_to_initialize.append(function);
return {};
}));
}
// 11. NOTE: Annex B.3.2.3 adds additional steps at this point.
// B.3.2.3 Changes to EvalDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation
@ -941,9 +911,9 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
HashTable<Utf16FlyString> hoisted_functions;
// b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within body, do
TRY(program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr<void> {
for (auto& function_declaration : data.annex_b_candidates) {
// i. Let F be StringValue of the BindingIdentifier of f.
auto function_name = function_declaration.name();
auto function_name = function_declaration->name();
// ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for body, then
// Note: This is checked during parsing and for_each_function_hoistable_with_annexB_extension so it always passes here.
@ -955,14 +925,15 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// 3. Assert: The following loop will terminate.
// 4. Repeat, while thisEnv is not the same as varEnv,
bool binding_exists = false;
while (this_environment != variable_environment) {
// a. If thisEnv is not an object Environment Record, then
if (!is<ObjectEnvironment>(*this_environment)) {
// i. If ! thisEnv.HasBinding(F) is true, then
if (MUST(this_environment->has_binding(function_name))) {
// i. Let bindingExists be true.
// Note: When bindingExists is true we skip all the other steps.
return {};
binding_exists = true;
break;
}
}
@ -971,6 +942,9 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
VERIFY(this_environment);
}
if (binding_exists)
continue;
// Note: At this point bindingExists is false.
// 5. If bindingExists is false and varEnv is a global Environment Record, then
if (global_var_environment) {
@ -978,12 +952,12 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
if (!global_var_environment->has_lexical_declaration(function_name)) {
// i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F).
if (!TRY(global_var_environment->can_declare_global_var(function_name)))
return {};
continue;
}
// b. Else,
else {
// i. Let fnDefinable be false.
return {};
continue;
}
}
// 6. Else,
@ -993,7 +967,7 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// 7. If bindingExists is false and fnDefinable is true, then
// a. If declaredFunctionOrVarNames does not contain F, then
if (!declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) {
if (!data.declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) {
// i. If varEnv is a global Environment Record, then
if (global_var_environment) {
// i. Perform ? varEnv.CreateGlobalVarBinding(F, true).
@ -1001,7 +975,6 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
}
// ii. Else,
else {
// i. Let bindingExists be ! varEnv.HasBinding(F).
// ii. If bindingExists is false, then
if (!MUST(variable_environment->has_binding(function_name))) {
@ -1022,107 +995,85 @@ ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& pr
// iii. Let fobj be ! benv.GetBindingValue(F, false).
// iv. Perform ? genv.SetMutableBinding(F, fobj, false).
// v. Return unused.
function_declaration.set_should_do_additional_annexB_steps();
return {};
}));
function_declaration->set_should_do_additional_annexB_steps();
}
}
// 12. Let declaredVarNames be a new empty List.
HashTable<Utf16FlyString> declared_var_names;
// 13. For each element d of varDeclarations, do
TRY(program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) {
// a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// Note: This is handled by for_each_var_scoped_variable_declaration.
for (auto const& name : data.var_scoped_names) {
// 1. If vn is not an element of declaredFunctionNames, then
if (!data.declared_function_names.contains(name)) {
// a. If varEnv is a global Environment Record, then
if (global_var_environment) {
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
auto variable_definable = TRY(global_var_environment->can_declare_global_var(name));
// i. For each String vn of the BoundNames of d, do
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
auto const& name = identifier.string();
// 1. If vn is not an element of declaredFunctionNames, then
if (!declared_function_names.contains(name)) {
// a. If varEnv is a global Environment Record, then
if (global_var_environment) {
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
auto variable_definable = TRY(global_var_environment->can_declare_global_var(name));
// ii. If vnDefinable is false, throw a TypeError exception.
if (!variable_definable)
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalVariable, name);
}
// b. If vn is not an element of declaredVarNames, then
// i. Append vn to declaredVarNames.
declared_var_names.set(name);
// ii. If vnDefinable is false, throw a TypeError exception.
if (!variable_definable)
return vm.throw_completion<TypeError>(ErrorType::CannotDeclareGlobalVariable, name);
}
return {};
});
}));
// b. If vn is not an element of declaredVarNames, then
// i. Append vn to declaredVarNames.
declared_var_names.set(name);
}
}
// 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a global Environment Record and the global object is a Proxy exotic object.
// 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
// 16. For each element d of lexDeclarations, do
TRY(program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
return declaration.for_each_bound_identifier([&](Identifier const& identifier) -> ThrowCompletionOr<void> {
auto const& name = identifier.string();
// i. If IsConstantDeclaration of d is true, then
if (declaration.is_constant_declaration()) {
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
TRY(lexical_environment->create_immutable_binding(vm, name, true));
}
// ii. Else,
else {
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
TRY(lexical_environment->create_mutable_binding(vm, name, false));
}
return {};
});
}));
for (auto const& binding : data.lexical_bindings) {
// i. If IsConstantDeclaration of d is true, then
if (binding.is_constant) {
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
TRY(lexical_environment->create_immutable_binding(vm, binding.name, true));
}
// ii. Else,
else {
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
TRY(lexical_environment->create_mutable_binding(vm, binding.name, false));
}
}
// 17. For each Parse Node f of functionsToInitialize, do
// NOTE: We iterate in reverse order since we appended the functions
// instead of prepending. We append because prepending is much slower
// and we only use the created vector here.
for (auto const& declaration : functions_to_initialize.in_reverse()) {
auto declaration_name = declaration.name();
// NB: We iterate in reverse order since we appended the functions
// instead of prepending during pre-computation.
for (auto const& function_to_initialize : data.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_from_function_data(
realm,
declaration.ensure_shared_data(vm),
function_to_initialize.shared_data,
lexical_environment,
private_environment);
// c. If varEnv is a global Environment Record, then
if (global_var_environment) {
// i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true).
TRY(global_var_environment->create_global_function_binding(declaration_name, function, true));
TRY(global_var_environment->create_global_function_binding(function_to_initialize.name, function, true));
}
// d. Else,
else {
// i. Let bindingExists be ! varEnv.HasBinding(fn).
auto binding_exists = MUST(variable_environment->has_binding(declaration_name));
auto binding_exists = MUST(variable_environment->has_binding(function_to_initialize.name));
// ii. If bindingExists is false, then
if (!binding_exists) {
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
// 2. Perform ! varEnv.CreateMutableBinding(fn, true).
MUST(variable_environment->create_mutable_binding(vm, declaration_name, true));
MUST(variable_environment->create_mutable_binding(vm, function_to_initialize.name, true));
// 3. Perform ! varEnv.InitializeBinding(fn, fo, normal).
MUST(variable_environment->initialize_binding(vm, declaration_name, function, Environment::InitializeBindingHint::Normal));
MUST(variable_environment->initialize_binding(vm, function_to_initialize.name, function, Environment::InitializeBindingHint::Normal));
}
// iii. Else,
else {
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
MUST(variable_environment->set_mutable_binding(vm, declaration_name, function, false));
MUST(variable_environment->set_mutable_binding(vm, function_to_initialize.name, function, false));
}
}
}

View file

@ -116,7 +116,7 @@ struct EvalDeclarationData {
static EvalDeclarationData create(VM&, Program const&, bool strict);
};
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, Program const& program, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict);
ThrowCompletionOr<void> eval_declaration_instantiation(VM& vm, EvalDeclarationData&, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict);
// 7.3.14 Call ( F, V [ , argumentsList ] ), https://tc39.es/ecma262/#sec-call
ALWAYS_INLINE ThrowCompletionOr<Value> call(VM& vm, Value function, Value this_value, ReadonlySpan<Value> arguments_list)

View file

@ -167,7 +167,8 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, Value source, Realm&
TRY(vm.push_execution_context(*eval_context, {}));
// 10. Let result be Completion(EvalDeclarationInstantiation(body, varEnv, lexEnv, null, strictEval)).
auto eval_result = eval_declaration_instantiation(vm, program, variable_environment, lexical_environment, nullptr, strict_eval);
auto eval_declaration_data = EvalDeclarationData::create(vm, program, strict_eval);
auto eval_result = eval_declaration_instantiation(vm, eval_declaration_data, variable_environment, lexical_environment, nullptr, strict_eval);
Completion result;