From ae3a7fd4b87e13718b817529c62df8878adbfa3b Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Wed, 5 Jul 2023 02:17:10 +0200 Subject: [PATCH] LibJS: Update bytecode generator to use local variables - Update ECMAScriptFunctionObject::function_declaration_instantiation to initialize local variables - Introduce GetLocal, SetLocal, TypeofLocal that will be used to operate on local variables. - Update bytecode generator to emit instructions for local variables --- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 54 ++++++++++-------- .../Libraries/LibJS/Bytecode/Generator.cpp | 13 ++++- Userland/Libraries/LibJS/Bytecode/Generator.h | 2 + .../Libraries/LibJS/Bytecode/Instruction.h | 3 + Userland/Libraries/LibJS/Bytecode/Op.cpp | 40 ++++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 55 +++++++++++++++++++ Userland/Libraries/LibJS/Forward.h | 1 + .../Runtime/ECMAScriptFunctionObject.cpp | 39 ++++++++----- 8 files changed, 170 insertions(+), 37 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 688b75aa89..1a811f8585 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -221,7 +221,11 @@ Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecod case UnaryOp::Typeof: if (is(*m_lhs)) { auto& identifier = static_cast(*m_lhs); - generator.emit(generator.intern_identifier(identifier.string())); + if (identifier.is_local()) { + generator.emit(identifier.local_variable_index()); + } else { + generator.emit(generator.intern_identifier(identifier.string())); + } break; } @@ -291,7 +295,11 @@ Bytecode::CodeGenerationErrorOr RegExpLiteral::generate_bytecode(Bytecode: Bytecode::CodeGenerationErrorOr Identifier::generate_bytecode(Bytecode::Generator& generator) const { - generator.emit(generator.intern_identifier(m_string)); + if (is_local()) { + generator.emit(local_variable_index()); + } else { + generator.emit(generator.intern_identifier(m_string)); + } return {}; } @@ -415,7 +423,7 @@ Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(By // e. Perform ? PutValue(lref, rval). if (is(*lhs)) { auto& identifier = static_cast(*lhs); - generator.emit(generator.intern_identifier(identifier.string())); + generator.emit_set_variable(identifier); } else if (is(*lhs)) { auto& expression = static_cast(*lhs); @@ -1043,13 +1051,15 @@ static Bytecode::CodeGenerationErrorOr generate_object_binding_pattern_byt if (is_rest) { VERIFY(!initializer); if (name.has>()) { - auto identifier = name.get>()->string(); - auto interned_identifier = generator.intern_identifier(identifier); + auto identifier = name.get>(); + auto interned_identifier = generator.intern_identifier(identifier->string()); generator.emit_with_extra_register_slots(excluded_property_names.size(), value_reg, excluded_property_names); - if (create_variables) + if (create_variables) { + VERIFY(!identifier->is_local()); generator.emit(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit(interned_identifier, initialization_mode); + } + generator.emit_set_variable(*identifier, initialization_mode); return {}; } @@ -1126,19 +1136,19 @@ static Bytecode::CodeGenerationErrorOr generate_object_binding_pattern_byt }; } - auto& identifier = name.get>()->string(); - auto identifier_ref = generator.intern_identifier(identifier); + auto const& identifier = *name.get>(); + auto identifier_ref = generator.intern_identifier(identifier.string()); if (create_variables) generator.emit(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit(identifier_ref, initialization_mode); + generator.emit_set_variable(identifier, initialization_mode); } else if (alias.has>()) { TRY(generator.emit_store_to_reference(alias.get>())); } else { - auto& identifier = alias.get>()->string(); - auto identifier_ref = generator.intern_identifier(identifier); + auto const& identifier = *alias.get>(); + auto identifier_ref = generator.intern_identifier(identifier.string()); if (create_variables) generator.emit(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit(identifier_ref, initialization_mode); + generator.emit_set_variable(identifier, initialization_mode); } } return {}; @@ -1189,7 +1199,7 @@ static Bytecode::CodeGenerationErrorOr generate_array_binding_pattern_byte auto interned_index = generator.intern_identifier(identifier->string()); if (create_variables) generator.emit(interned_index, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit(interned_index, initialization_mode); + generator.emit_set_variable(*identifier, initialization_mode); return {}; }, [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { @@ -1355,7 +1365,7 @@ static Bytecode::CodeGenerationErrorOr assign_accumulator_to_variable_decl return declarator.target().visit( [&](NonnullRefPtr const& id) -> Bytecode::CodeGenerationErrorOr { - generator.emit(generator.intern_identifier(id->string()), initialization_mode); + generator.emit_set_variable(*id, initialization_mode); return {}; }, [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { @@ -2309,7 +2319,7 @@ Bytecode::CodeGenerationErrorOr ClassDeclaration::generate_bytecode(Byteco generator.emit(accumulator_backup_reg); TRY(m_class_expression->generate_bytecode(generator)); - generator.emit(generator.intern_identifier(m_class_expression.ptr()->name()), Bytecode::Op::SetVariable::InitializationMode::Initialize); + generator.emit_set_variable(*m_class_expression.ptr()->m_name, Bytecode::Op::SetVariable::InitializationMode::Initialize); generator.emit(accumulator_backup_reg); return {}; @@ -2464,9 +2474,10 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he auto& variable = variable_declaration.declarations().first(); if (variable->init()) { VERIFY(variable->target().has>()); - auto binding_id = generator.intern_identifier(variable->target().get>()->string()); - TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), binding_id)); - generator.emit(binding_id); + auto identifier = variable->target().get>(); + auto identifier_table_ref = generator.intern_identifier(identifier->string()); + TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), identifier_table_ref)); + generator.emit_set_variable(*identifier); } } else { // 1. Let oldEnv be the running execution context's LexicalEnvironment. @@ -2652,11 +2663,10 @@ static Bytecode::CodeGenerationErrorOr for_in_of_body_evaluation(Bytecode: if (!destructuring) { // 1. Assert: lhs binds a single name. // 2. Let lhsName be the sole element of BoundNames of lhs. - auto lhs_name = variable_declaration.declarations().first()->target().get>()->string(); + auto lhs_name = variable_declaration.declarations().first()->target().get>(); // 3. Let lhsRef be ! ResolveBinding(lhsName). // NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that. - auto identifier = generator.intern_identifier(lhs_name); - generator.emit(identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); + generator.emit_set_variable(*lhs_name, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); } } // i. If destructuring is false, then diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index befaa7d90b..27cefe8b50 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -141,7 +141,7 @@ CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode cons { if (is(node)) { auto& identifier = static_cast(node); - emit(intern_identifier(identifier.string())); + TRY(identifier.generate_bytecode(*this)); return {}; } if (is(node)) { @@ -217,7 +217,7 @@ CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const { if (is(node)) { auto& identifier = static_cast(node); - emit(intern_identifier(identifier.string())); + emit_set_variable(identifier); return {}; } if (is(node)) { @@ -306,6 +306,15 @@ CodeGenerationErrorOr Generator::emit_delete_reference(JS::ASTNode const& return {}; } +void Generator::emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Op::EnvironmentMode mode) +{ + if (identifier.is_local()) { + emit(identifier.local_variable_index()); + } else { + emit(intern_identifier(identifier.string()), initialization_mode, mode); + } +} + void Generator::generate_break() { bool last_was_finally = false; diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index 848e30475a..f2edae60f8 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -83,6 +83,8 @@ public: CodeGenerationErrorOr emit_store_to_reference(JS::ASTNode const&); CodeGenerationErrorOr emit_delete_reference(JS::ASTNode const&); + void emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode = Bytecode::Op::SetVariable::InitializationMode::Set, Bytecode::Op::EnvironmentMode mode = Bytecode::Op::EnvironmentMode::Lexical); + void push_home_object(Register); void pop_home_object(); void emit_new_function(JS::FunctionExpression const&, Optional lhs_name); diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 629a2f95a7..60ba1bba04 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -41,6 +41,7 @@ O(GetObjectPropertyIterator) \ O(GetPrivateById) \ O(GetVariable) \ + O(GetLocal) \ O(GreaterThan) \ O(GreaterThanEquals) \ O(HasPrivateId) \ @@ -87,6 +88,7 @@ O(RightShift) \ O(ScheduleJump) \ O(SetVariable) \ + O(SetLocal) \ O(Store) \ O(StrictlyEquals) \ O(StrictlyInequals) \ @@ -98,6 +100,7 @@ O(ToNumeric) \ O(Typeof) \ O(TypeofVariable) \ + O(TypeofLocal) \ O(UnaryMinus) \ O(UnaryPlus) \ O(UnsignedRightShift) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index d7fc4083c2..d4ce875e0f 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -422,6 +422,17 @@ ThrowCompletionOr GetVariable::execute_impl(Bytecode::Interpreter& interpr return {}; } +ThrowCompletionOr GetLocal::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + if (vm.running_execution_context().local_variables[m_index].is_empty()) { + auto const& variable_name = vm.running_execution_context().function->local_variables_names()[m_index]; + return interpreter.vm().throw_completion(ErrorType::BindingNotInitialized, variable_name); + } + interpreter.accumulator() = vm.running_execution_context().local_variables[m_index]; + return {}; +} + ThrowCompletionOr DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -506,6 +517,12 @@ ThrowCompletionOr SetVariable::execute_impl(Bytecode::Interpreter& interpr return {}; } +ThrowCompletionOr SetLocal::execute_impl(Bytecode::Interpreter& interpreter) const +{ + interpreter.vm().running_execution_context().local_variables[m_index] = interpreter.accumulator(); + return {}; +} + ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -1240,6 +1257,14 @@ ThrowCompletionOr TypeofVariable::execute_impl(Bytecode::Interpreter& inte return {}; } +ThrowCompletionOr TypeofLocal::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto const& value = vm.running_execution_context().local_variables[m_index]; + interpreter.accumulator() = MUST_OR_THROW_OOM(PrimitiveString::create(vm, value.typeof())); + return {}; +} + ThrowCompletionOr ToNumeric::execute_impl(Bytecode::Interpreter& interpreter) const { interpreter.accumulator() = TRY(interpreter.accumulator().to_numeric(interpreter.vm())); @@ -1335,6 +1360,11 @@ DeprecatedString GetVariable::to_deprecated_string_impl(Bytecode::Executable con return DeprecatedString::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); } +DeprecatedString GetLocal::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("GetLocal {}", m_index); +} + DeprecatedString DeleteVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const { return DeprecatedString::formatted("DeleteVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); @@ -1365,6 +1395,11 @@ DeprecatedString SetVariable::to_deprecated_string_impl(Bytecode::Executable con return DeprecatedString::formatted("SetVariable env:{} init:{} {} ({})", mode_string, initialization_mode_name, m_identifier, executable.identifier_table->get(m_identifier)); } +DeprecatedString SetLocal::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("SetLocal {}", m_index); +} + DeprecatedString PutById::to_deprecated_string_impl(Bytecode::Executable const& executable) const { auto kind = m_kind == PropertyKind::Getter @@ -1652,6 +1687,11 @@ DeprecatedString TypeofVariable::to_deprecated_string_impl(Bytecode::Executable return DeprecatedString::formatted("TypeofVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); } +DeprecatedString TypeofLocal::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("TypeofLocal {}", m_index); +} + DeprecatedString ToNumeric::to_deprecated_string_impl(Bytecode::Executable const&) const { return "ToNumeric"sv; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 235c262527..60fa75c8f3 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -484,6 +484,25 @@ private: InitializationMode m_initialization_mode { InitializationMode::Set }; }; +class SetLocal final : public Instruction { +public: + explicit SetLocal(size_t index) + : Instruction(Type::SetLocal) + , m_index(index) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } + + size_t index() const { return m_index; } + +private: + size_t m_index; +}; + class GetVariable final : public Instruction { public: explicit GetVariable(IdentifierTableIndex identifier) @@ -505,6 +524,25 @@ private: Optional mutable m_cached_environment_coordinate; }; +class GetLocal final : public Instruction { +public: + explicit GetLocal(size_t index) + : Instruction(Type::GetLocal) + , m_index(index) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } + + size_t index() const { return m_index; } + +private: + size_t m_index; +}; + class DeleteVariable final : public Instruction { public: explicit DeleteVariable(IdentifierTableIndex identifier) @@ -1338,6 +1376,23 @@ private: IdentifierTableIndex m_identifier; }; +class TypeofLocal final : public Instruction { +public: + explicit TypeofLocal(size_t index) + : Instruction(Type::TypeofLocal) + , m_index(index) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } + +private: + size_t m_index; +}; + } namespace JS::Bytecode { diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 6c73f8418d..96727c646f 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -184,6 +184,7 @@ class HeapBlock; struct ImportEntry; class ImportStatement; class Interpreter; +class Identifier; class Intrinsics; struct IteratorRecord; class MetaProperty; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 68940271ba..8b2ee56294 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -503,10 +503,14 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia if (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_var_declared_name`. - MUST(scope_body->for_each_var_declared_name([&](auto const& name) { - if (!parameter_names.contains(name) && instantiated_var_names.set(name) == AK::HashSetResult::InsertedNewEntry) { - MUST(environment->create_mutable_binding(vm, name, false)); - MUST(environment->initialize_binding(vm, name, js_undefined(), Environment::InitializeBindingHint::Normal)); + MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { + if (!parameter_names.contains(id.string()) && instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) { + if (vm.bytecode_interpreter_if_exists() && 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)); + } } })); } @@ -518,18 +522,23 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia if (scope_body) { // NOTE: Due to the use of MUST with `create_mutable_binding`, `get_binding_value` and `initialize_binding` below, // an exception should not result from `for_each_var_declared_name`. - MUST(scope_body->for_each_var_declared_name([&](auto const& name) { - if (instantiated_var_names.set(name) != AK::HashSetResult::InsertedNewEntry) + MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { + if (instantiated_var_names.set(id.string()) != AK::HashSetResult::InsertedNewEntry) return; - MUST(var_environment->create_mutable_binding(vm, name, false)); + MUST(var_environment->create_mutable_binding(vm, id.string(), false)); Value initial_value; - if (!parameter_names.contains(name) || function_names.contains(name)) + if (!parameter_names.contains(id.string()) || function_names.contains(id.string())) initial_value = js_undefined(); else - initial_value = MUST(environment->get_binding_value(vm, name, false)); + initial_value = MUST(environment->get_binding_value(vm, id.string(), false)); - MUST(var_environment->initialize_binding(vm, name, initial_value, Environment::InitializeBindingHint::Normal)); + if (vm.bytecode_interpreter_if_exists() && 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)); + } })); } } @@ -586,11 +595,15 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) { // NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below, // an exception should not result from `for_each_bound_name`. - MUST(declaration.for_each_bound_name([&](auto const& name) { + MUST(declaration.for_each_bound_identifier([&](auto const& id) { + if (vm.bytecode_interpreter_if_exists() && id.is_local()) { + // NOTE: Local variables are supported only in bytecode interpreter + return; + } if (declaration.is_constant_declaration()) - MUST(lex_environment->create_immutable_binding(vm, name, true)); + MUST(lex_environment->create_immutable_binding(vm, id.string(), true)); else - MUST(lex_environment->create_mutable_binding(vm, name, false)); + MUST(lex_environment->create_mutable_binding(vm, id.string(), false)); })); }));