浏览代码

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
Aliaksandr Kalenik 2 年之前
父节点
当前提交
ae3a7fd4b8

+ 32 - 22
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -221,7 +221,11 @@ Bytecode::CodeGenerationErrorOr<void> UnaryExpression::generate_bytecode(Bytecod
     case UnaryOp::Typeof:
         if (is<Identifier>(*m_lhs)) {
             auto& identifier = static_cast<Identifier const&>(*m_lhs);
-            generator.emit<Bytecode::Op::TypeofVariable>(generator.intern_identifier(identifier.string()));
+            if (identifier.is_local()) {
+                generator.emit<Bytecode::Op::TypeofLocal>(identifier.local_variable_index());
+            } else {
+                generator.emit<Bytecode::Op::TypeofVariable>(generator.intern_identifier(identifier.string()));
+            }
             break;
         }
 
@@ -291,7 +295,11 @@ Bytecode::CodeGenerationErrorOr<void> RegExpLiteral::generate_bytecode(Bytecode:
 
 Bytecode::CodeGenerationErrorOr<void> Identifier::generate_bytecode(Bytecode::Generator& generator) const
 {
-    generator.emit<Bytecode::Op::GetVariable>(generator.intern_identifier(m_string));
+    if (is_local()) {
+        generator.emit<Bytecode::Op::GetLocal>(local_variable_index());
+    } else {
+        generator.emit<Bytecode::Op::GetVariable>(generator.intern_identifier(m_string));
+    }
     return {};
 }
 
@@ -415,7 +423,7 @@ Bytecode::CodeGenerationErrorOr<void> AssignmentExpression::generate_bytecode(By
                 // e. Perform ? PutValue(lref, rval).
                 if (is<Identifier>(*lhs)) {
                     auto& identifier = static_cast<Identifier const&>(*lhs);
-                    generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(identifier.string()));
+                    generator.emit_set_variable(identifier);
                 } else if (is<MemberExpression>(*lhs)) {
                     auto& expression = static_cast<MemberExpression const&>(*lhs);
 
@@ -1043,13 +1051,15 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
         if (is_rest) {
             VERIFY(!initializer);
             if (name.has<NonnullRefPtr<Identifier const>>()) {
-                auto identifier = name.get<NonnullRefPtr<Identifier const>>()->string();
-                auto interned_identifier = generator.intern_identifier(identifier);
+                auto identifier = name.get<NonnullRefPtr<Identifier const>>();
+                auto interned_identifier = generator.intern_identifier(identifier->string());
 
                 generator.emit_with_extra_register_slots<Bytecode::Op::CopyObjectExcludingProperties>(excluded_property_names.size(), value_reg, excluded_property_names);
-                if (create_variables)
+                if (create_variables) {
+                    VERIFY(!identifier->is_local());
                     generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
-                generator.emit<Bytecode::Op::SetVariable>(interned_identifier, initialization_mode);
+                }
+                generator.emit_set_variable(*identifier, initialization_mode);
 
                 return {};
             }
@@ -1126,19 +1136,19 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
                 };
             }
 
-            auto& identifier = name.get<NonnullRefPtr<Identifier const>>()->string();
-            auto identifier_ref = generator.intern_identifier(identifier);
+            auto const& identifier = *name.get<NonnullRefPtr<Identifier const>>();
+            auto identifier_ref = generator.intern_identifier(identifier.string());
             if (create_variables)
                 generator.emit<Bytecode::Op::CreateVariable>(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false);
-            generator.emit<Bytecode::Op::SetVariable>(identifier_ref, initialization_mode);
+            generator.emit_set_variable(identifier, initialization_mode);
         } else if (alias.has<NonnullRefPtr<MemberExpression const>>()) {
             TRY(generator.emit_store_to_reference(alias.get<NonnullRefPtr<MemberExpression const>>()));
         } else {
-            auto& identifier = alias.get<NonnullRefPtr<Identifier const>>()->string();
-            auto identifier_ref = generator.intern_identifier(identifier);
+            auto const& identifier = *alias.get<NonnullRefPtr<Identifier const>>();
+            auto identifier_ref = generator.intern_identifier(identifier.string());
             if (create_variables)
                 generator.emit<Bytecode::Op::CreateVariable>(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false);
-            generator.emit<Bytecode::Op::SetVariable>(identifier_ref, initialization_mode);
+            generator.emit_set_variable(identifier, initialization_mode);
         }
     }
     return {};
@@ -1189,7 +1199,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
                 auto interned_index = generator.intern_identifier(identifier->string());
                 if (create_variables)
                     generator.emit<Bytecode::Op::CreateVariable>(interned_index, Bytecode::Op::EnvironmentMode::Lexical, false);
-                generator.emit<Bytecode::Op::SetVariable>(interned_index, initialization_mode);
+                generator.emit_set_variable(*identifier, initialization_mode);
                 return {};
             },
             [&](NonnullRefPtr<BindingPattern const> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
@@ -1355,7 +1365,7 @@ static Bytecode::CodeGenerationErrorOr<void> assign_accumulator_to_variable_decl
 
     return declarator.target().visit(
         [&](NonnullRefPtr<Identifier const> const& id) -> Bytecode::CodeGenerationErrorOr<void> {
-            generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(id->string()), initialization_mode);
+            generator.emit_set_variable(*id, initialization_mode);
             return {};
         },
         [&](NonnullRefPtr<BindingPattern const> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
@@ -2309,7 +2319,7 @@ Bytecode::CodeGenerationErrorOr<void> ClassDeclaration::generate_bytecode(Byteco
     generator.emit<Bytecode::Op::Store>(accumulator_backup_reg);
 
     TRY(m_class_expression->generate_bytecode(generator));
-    generator.emit<Bytecode::Op::SetVariable>(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<Bytecode::Op::Load>(accumulator_backup_reg);
     return {};
@@ -2464,9 +2474,10 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
             auto& variable = variable_declaration.declarations().first();
             if (variable->init()) {
                 VERIFY(variable->target().has<NonnullRefPtr<Identifier const>>());
-                auto binding_id = generator.intern_identifier(variable->target().get<NonnullRefPtr<Identifier const>>()->string());
-                TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), binding_id));
-                generator.emit<Bytecode::Op::SetVariable>(binding_id);
+                auto identifier = variable->target().get<NonnullRefPtr<Identifier const>>();
+                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<void> 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<NonnullRefPtr<Identifier const>>()->string();
+            auto lhs_name = variable_declaration.declarations().first()->target().get<NonnullRefPtr<Identifier const>>();
             // 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<Bytecode::Op::SetVariable>(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

+ 11 - 2
Userland/Libraries/LibJS/Bytecode/Generator.cpp

@@ -141,7 +141,7 @@ CodeGenerationErrorOr<void> Generator::emit_load_from_reference(JS::ASTNode cons
 {
     if (is<Identifier>(node)) {
         auto& identifier = static_cast<Identifier const&>(node);
-        emit<Bytecode::Op::GetVariable>(intern_identifier(identifier.string()));
+        TRY(identifier.generate_bytecode(*this));
         return {};
     }
     if (is<MemberExpression>(node)) {
@@ -217,7 +217,7 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
 {
     if (is<Identifier>(node)) {
         auto& identifier = static_cast<Identifier const&>(node);
-        emit<Bytecode::Op::SetVariable>(intern_identifier(identifier.string()));
+        emit_set_variable(identifier);
         return {};
     }
     if (is<MemberExpression>(node)) {
@@ -306,6 +306,15 @@ CodeGenerationErrorOr<void> 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<Bytecode::Op::SetLocal>(identifier.local_variable_index());
+    } else {
+        emit<Bytecode::Op::SetVariable>(intern_identifier(identifier.string()), initialization_mode, mode);
+    }
+}
+
 void Generator::generate_break()
 {
     bool last_was_finally = false;

+ 2 - 0
Userland/Libraries/LibJS/Bytecode/Generator.h

@@ -83,6 +83,8 @@ public:
     CodeGenerationErrorOr<void> emit_store_to_reference(JS::ASTNode const&);
     CodeGenerationErrorOr<void> 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<IdentifierTableIndex> lhs_name);

+ 3 - 0
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)            \

+ 40 - 0
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -422,6 +422,17 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
     return {};
 }
 
+ThrowCompletionOr<void> 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<ReferenceError>(ErrorType::BindingNotInitialized, variable_name);
+    }
+    interpreter.accumulator() = vm.running_execution_context().local_variables[m_index];
+    return {};
+}
+
 ThrowCompletionOr<void> DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
@@ -506,6 +517,12 @@ ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpr
     return {};
 }
 
+ThrowCompletionOr<void> SetLocal::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    interpreter.vm().running_execution_context().local_variables[m_index] = interpreter.accumulator();
+    return {};
+}
+
 ThrowCompletionOr<void> GetById::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
@@ -1240,6 +1257,14 @@ ThrowCompletionOr<void> TypeofVariable::execute_impl(Bytecode::Interpreter& inte
     return {};
 }
 
+ThrowCompletionOr<void> 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<void> 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;

+ 55 - 0
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<void> 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<EnvironmentCoordinate> mutable m_cached_environment_coordinate;
 };
 
+class GetLocal final : public Instruction {
+public:
+    explicit GetLocal(size_t index)
+        : Instruction(Type::GetLocal)
+        , m_index(index)
+    {
+    }
+
+    ThrowCompletionOr<void> 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<void> 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 {

+ 1 - 0
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;

+ 26 - 13
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -503,10 +503,14 @@ ThrowCompletionOr<void> 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<void> 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<void> 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));
         }));
     }));